inherited_resources

  • 所有者: activeadmin/inherited_resources
  • 平台:
  • 許可證: MIT License
  • 分類:
  • 主題:
  • 喜歡:
    0
      比較:

Github星跟蹤圖

Notice

Inherited Resources is no longer actively maintained by the original author and
has been transferred to the ActiveAdmin organization for maintenance. New feature
requests are not encouraged.

If you are not already using Inherited Resources we suggest instead using Rails'
respond_with feature alongside the responders gem.

Inherited Resources

Version
CircleCI
Tidelift

Inherited Resources speeds up development by making your controllers inherit
all restful actions so you just have to focus on what is important. It makes
your controllers more powerful and cleaner at the same time.

In addition to making your controllers follow a pattern, it helps you to write better
code by following fat models and skinny controllers convention. There are
two screencasts available besides this README:

Installation

You can let bundler install Inherited Resources by adding this line to your application's Gemfile:

gem 'inherited_resources'

And then execute:

$ bundle install

Or install it yourself with:

$ gem install inherited_resources

HasScope

Since Inherited Resources 1.0, has_scope is not part of its core anymore but
a gem dependency. Be sure to check the documentation to see how you can use it:

And it can be installed as:

$ gem install has_scope

Responders

Since Inherited Resources 1.0, responders are not part of its core anymore,
but is set as Inherited Resources dependency and it's used by default by
InheritedResources controllers. Be sure to check the documentation to see
how it will change your application:

And it can be installed with:

$ gem install responders

Using responders will set the flash message to :notice and :alert. You can change
that through the following configuration value:

InheritedResources.flash_keys = [ :success, :failure ]

Notice the CollectionResponder won't work with InheritedResources, as
InheritedResources hardcodes the redirect path based on the current scope (like
belongs to, polymorphic associations, etc).

Basic Usage

To use Inherited Resources you just have to inherit (duh) it:

class ProjectsController < InheritedResources::Base
end

And all actions are defined and working, check it! Your projects collection
(in the index action) is still available in the instance variable @projects
and your project resource (all other actions) is available as @project.

The next step is to define which mime types this controller provides:

class ProjectsController < InheritedResources::Base
  respond_to :html, :xml, :json
end

You can also specify them per action:

class ProjectsController < InheritedResources::Base
  respond_to :html, :xml, :json
  respond_to :js, :only => :create
  respond_to :iphone, :except => [ :edit, :update ]
end

For each request, it first checks if the "controller/action.format" file is
available (for example "projects/create.xml") and if it's not, it checks if
the resource respond to :to_format (in this case, :to_xml). Otherwise returns 404.

Another option is to specify which actions the controller will inherit from
the InheritedResources::Base:

class ProjectsController < InheritedResources::Base
  actions :index, :show, :new, :create
end

Or:

class ProjectsController < InheritedResources::Base
  actions :all, :except => [ :edit, :update, :destroy ]
end

In your views, you will get the following helpers:

resource        #=> @project
collection      #=> @projects
resource_class  #=> Project

As you might expect, collection (@projects instance variable) is only available
on index actions.

If for some reason you cannot inherit from InheritedResources::Base, you can
call inherit_resources in your controller class scope:

class AccountsController < ApplicationController
  inherit_resources
end

One reason to use the inherit_resources macro would be to ensure that your controller
never responds with the html mime-type. InheritedResources::Base already
responds to :html, and the respond_to macro is strictly additive.
Therefore, if you want to create a controller that, for example, responds ONLY via :js,
you will have to write it this way:

class AccountsController < ApplicationController
  respond_to :js
  inherit_resources
end

Overwriting defaults

Whenever you inherit from InheritedResources, several defaults are assumed.
For example you can have an AccountsController for account management while the
resource is a User:

class AccountsController < InheritedResources::Base
  defaults :resource_class => User, :collection_name => 'users', :instance_name => 'user'
end

In the case above, in your views you will have @users and @user variables, but
the routes used will still be accounts_url and account_url. If you plan also to
change the routes, you can use :route_collection_name and :route_instance_name.

Namespaced controllers work out of the box, but if you need to specify a
different route prefix you can do the following:

class Administrators::PeopleController < InheritedResources::Base
  defaults :route_prefix => 'admin'
end

Then your named routes will be: admin_people_url, admin_person_url instead
of administrators_people_url and administrators_person_url.

If you want to customize how resources are retrieved you can overwrite
collection and resource methods. The first is called on index action and the
second on all other actions. Let's suppose you want to add pagination to your
projects collection:

class ProjectsController < InheritedResources::Base
  protected
    def collection
      get_collection_ivar, set_collection_ivar(end_of_association_chain.paginate(:page => params[:page]))
    end
end

The end_of_association_chain returns your resource after nesting all associations
and scopes (more about this below).

InheritedResources also introduces another method called begin_of_association_chain.
It's mostly used when you want to create resources based on the @current_user and
you have urls like "account/projects". In such cases you have to do
@current_user.projects.find or @current_user.projects.build in your actions.

You can deal with it just by doing:

class ProjectsController < InheritedResources::Base
  protected
    def begin_of_association_chain
      @current_user
    end
end

Overwriting actions

Let's suppose that after destroying a project you want to redirect to your
root url instead of redirecting to projects url. You just have to do:

class ProjectsController < InheritedResources::Base
  def destroy
    super do, format, format.html { redirect_to root_url }
    end
  end
end

You are opening your action and giving the parent action a new behavior. On
the other hand, I have to agree that calling super is not very readable. That's
why all methods have aliases. So this is equivalent:

class ProjectsController < InheritedResources::Base
  def destroy
    destroy! do, format, format.html { redirect_to root_url }
    end
  end
end

Since most of the time when you change a create, update or destroy
action you do so because you want to change its redirect url, a shortcut is
provided. So you can do:

class ProjectsController < InheritedResources::Base
  def destroy
    destroy! { root_url }
  end
end

If you simply want to change the flash message for a particular action, you can
pass the message to the parent action using the keys :notice and :alert (as you
would with flash):

class ProjectsController < InheritedResources::Base
  def create
    create!(:notice => "Dude! Nice job creating that project.")
  end
end

You can still pass the block to change the redirect, as mentioned above:

class ProjectsController < InheritedResources::Base
  def create
    create!(:notice => "Dude! Nice job creating that project.") { root_url }
  end
end

Now let's suppose that before create a project you have to do something special
but you don't want to create a before filter for it:

class ProjectsController < InheritedResources::Base
  def create
    @project = Project.new(params[:project])
    @project.something_special!
    create!
  end
end

Yes, it's that simple! The nice part is since you already set the instance variable
@project, it will not build a project again.

Same goes for updating the project:

class ProjectsController < InheritedResources::Base
  def update
    @project = Project.find(params[:id])
    @project.something_special!
    update!
  end
end

Before we finish this topic, we should talk about one more thing: "success/failure
blocks". Let's suppose that when we update our project, in case of failure, we
want to redirect to the project url instead of re-rendering the edit template.

Our first attempt to do this would be:

class ProjectsController < InheritedResources::Base
  def update
    update! do, format, unless @project.errors.empty? # failure
        format.html { redirect_to project_url(@project) }
      end
    end
  end
end

Looks too verbose, right? We can actually do:

class ProjectsController < InheritedResources::Base
  def update
    update! do, success, failure, failure.html { redirect_to project_url(@project) }
    end
  end
end

Much better! So explaining everything: when you give a block which expects one
argument it will be executed in both scenarios: success and failure. But if you
give a block that expects two arguments, the first will be executed only in
success scenarios and the second in failure scenarios. You keep everything
clean and organized inside the same action.

Smart redirects

Although the syntax above is a nice shortcut, you won't need to do it frequently
because (since version 1.2) Inherited Resources has smart redirects. Redirects
in actions calculates depending on the existent controller methods.

Redirects in create and update actions calculates in the following order: resource_url,
collection_url, parent_url (which we are going to see later), and root_url. Redirect
in destroy action calculate in following order collection_url, parent_url, root_url.

Example:

class ButtonsController < InheritedResources::Base
  belongs_to :window
  actions :all, :except => [:show, :index]
end

This controller redirect to parent window after all CUD actions.

Success and failure scenarios on destroy

The destroy action can also fail, this usually happens when you have a
before_destroy callback in your model which returns false. However, in
order to tell InheritedResources that it really failed, you need to add
errors to your model. So your before_destroy callback on the model should
be something like this:

def before_destroy
  if cant_be_destroyed?
    errors.add(:base, "not allowed")
    false
  end
end

Belongs to

Finally, our Projects are going to get some Tasks. Then you create a
TasksController and do:

class TasksController < InheritedResources::Base
  belongs_to :project
end

belongs_to accepts several options to be able to configure the association.
For example, if you want urls like "/projects/:project_title/tasks", you can
customize how InheritedResources find your projects:

class TasksController < InheritedResources::Base
  belongs_to :project, :finder => :find_by_title!, :param => :project_title
end

It also accepts :route_name, :parent_class and :instance_name as options.
Check the lib/inherited_resources/class_methods.rb
for more.

Nested belongs to

Now, our Tasks get some Comments and you need to nest even deeper. Good
practices says that you should never nest more than two resources, but sometimes
you have to for security reasons. So this is an example of how you can do it:

class CommentsController < InheritedResources::Base
  nested_belongs_to :project, :task
end

If you need to configure any of these belongs to, you can nest them using blocks:

class CommentsController < InheritedResources::Base
  belongs_to :project, :finder => :find_by_title!, :param => :project_title do
    belongs_to :task
  end
end

Warning: calling several belongs_to is the same as nesting them:

class CommentsController < InheritedResources::Base
  belongs_to :project
  belongs_to :task
end

In other words, the code above is the same as calling nested_belongs_to.

Polymorphic belongs to

We can go even further. Let's suppose our Projects can now have Files, Messages
and Tasks, and they are all commentable. In this case, the best solution is to
use polymorphism:

class CommentsController < InheritedResources::Base
  belongs_to :task, :file, :message, :polymorphic => true
  # polymorphic_belongs_to :task, :file, :message
end

You can even use it with nested resources:

class CommentsController < InheritedResources::Base
  belongs_to :project do
    belongs_to :task, :file, :message, :polymorphic => true
  end
end

The url in such cases can be:

/project/1/task/13/comments
/project/1/file/11/comments
/project/1/message/9/comments

When using polymorphic associations, you get some free helpers:

parent?         #=> true
parent_type     #=> :task
parent_class    #=> Task
parent          #=> @task

Right now, Inherited Resources is limited and does not allow you
to have two polymorphic associations nested.

Optional belongs to

Later you decide to create a view to show all comments, independent if they belong
to a task, file or message. You can reuse your polymorphic controller just doing:

class CommentsController < InheritedResources::Base
  belongs_to :task, :file, :message, :optional => true
  # optional_belongs_to :task, :file, :message
end

This will handle all those urls properly:

/comment/1
/tasks/2/comment/5
/files/10/comment/3
/messages/13/comment/11

This is treated as a special type of polymorphic associations, thus all helpers
are available. As you expect, when no parent is found, the helpers return:

parent?         #=> false
parent_type     #=> nil
parent_class    #=> nil
parent          #=> nil

Singletons

Now we are going to add manager to projects. We say that Manager is a singleton
resource because a Project has just one manager. You should declare it as
has_one (or resource) in your routes.

To declare an resource of current controller as singleton, you just have to give the
:singleton option in defaults.

class ManagersController < InheritedResources::Base
  defaults :singleton => true
  belongs_to :project
  # singleton_belongs_to :project
end

So now you can use urls like "/projects/1/manager".

In the case of nested resources (when some of the can be singletons) you can declare it separately

class WorkersController < InheritedResources::Base
  #defaults :singleton => true #if you have only single worker
  belongs_to :project
  belongs_to :manager, :singleton => true
end

This is correspond urls like "/projects/1/manager/workers/1".

It will deal with everything again and hide the action :index from you.

Namespaced Controllers

Namespaced controllers works out the box.

class Forum::PostsController < InheritedResources::Base
end

Inherited Resources prioritizes the default resource class for the namespaced controller in
this order:

Forum::Post
ForumPost
Post

URL Helpers

When you use InheritedResources it creates some URL helpers.
And they handle everything for you. :)

# /posts/1/comments
resource_url               # => /posts/1/comments/#{@comment.to_param}
resource_url(comment)      # => /posts/1/comments/#{comment.to_param}
new_resource_url           # => /posts/1/comments/new
edit_resource_url          # => /posts/1/comments/#{@comment.to_param}/edit
edit_resource_url(comment) # => /posts/1/comments/#{comment.to_param}/edit
collection_url             # => /posts/1/comments
parent_url                 # => /posts/1

# /projects/1/tasks
resource_url               # => /projects/1/tasks/#{@task.to_param}
resource_url(task)         # => /projects/1/tasks/#{task.to_param}
new_resource_url           # => /projects/1/tasks/new
edit_resource_url          # => /projects/1/tasks/#{@task.to_param}/edit
edit_resource_url(task)    # => /projects/1/tasks/#{task.to_param}/edit
collection_url             # => /projects/1/tasks
parent_url                 # => /projects/1

# /users
resource_url               # => /users/#{@user.to_param}
resource_url(user)         # => /users/#{user.to_param}
new_resource_url           # => /users/new
edit_resource_url          # => /users/#{@user.to_param}/edit
edit_resource_url(user)    # => /users/#{user.to_param}/edit
collection_url             # => /users
parent_url                 # => /

Those urls helpers also accepts a hash as options, just as in named routes.

# /projects/1/tasks
collection_url(:page => 1, :limit => 10) #=> /projects/1/tasks?page=1&limit=10

In polymorphic cases, you can also give the parent as parameter to collection_url.

Another nice thing is that those urls are not guessed during runtime. They are
all created when your application is loaded (except for polymorphic
associations, that relies on Rails' polymorphic_url).

Custom actions

Since version 1.2, Inherited Resources allows you to define custom actions in controller:

class ButtonsController < InheritedResources::Base
  custom_actions :resource => :delete, :collection => :search
end

This code creates delete and search actions in controller (they behaves like show and
index actions accordingly). Also, it will produce delete_resource_{path,url} and
search_resources_{path,url} url helpers.

What about views?

Sometimes just DRYing up the controllers is not enough. If you need to DRY up your views,
check this Wiki page:

https://github.com/activeadmin/inherited_resources/wiki/Views-Inheritance

Notice that Rails 3.1 ships with view inheritance built-in.

Some DSL

For those DSL lovers, InheritedResources won't leave you alone. You can overwrite
your success/failure blocks straight from your class binding. For it, you just
need to add a DSL module to your application controller:

class ApplicationController < ActionController::Base
  include InheritedResources::DSL
end

And then you can rewrite the last example as:

class ProjectsController < InheritedResources::Base
  update! do, success, failure, failure.html { redirect_to project_url(@project) }
  end
end

Strong Parameters

If your controller defines a method named permitted_params, InheritedResources
will call it where it would normally call params. This allows for easy
integration with the strong_parameters gem:

def permitted_params
  params.permit(:widget => [:permitted_field, :other_permitted_field])
end

Remember that if your field is sent by client to server as an array, you have to write :permitted_field => [], not just :permitted_field.

Note that this doesn't work if you use strong_parameters' require method
instead of permit, because whereas permit returns the entire sanitized
parameter hash, require returns only the sanitized params below the parameter
you required.

If you need params.require you can do it like this:

def permitted_params
  {:widget => params.fetch(:widget, {}).permit(:permitted_field, :other_permitted_field)}
end

Or better yet just override #build_resource_params directly:

def build_resource_params
  [params.fetch(:widget, {}).permit(:permitted_field, :other_permitted_field)]
end

Instead you can stick to a standard Rails 4 notation (as rails scaffold generates) and write:

def widget_params
  params.require(:widget).permit(:permitted_field, :other_permitted_field)
end

In such case you should remove #permitted_params method because it has greater priority.

Funding

If you want to support us financially, you can help fund the project
through a Tidelift subscription
. By buying a Tidelift subscription
you make sure your whole dependency stack is properly maintained, while also
getting a comprehensive view of outdated dependencies, new releases, security
alerts, and licensing compatibility issues.

Bugs and Feedback

If you discover any bugs, please describe it in the issues tracker, including Rails and InheritedResources versions.

Questions are better handled on StackOverflow.

MIT License. Copyright (c) 2009-2017 José Valim.

Security contact information

Please use the Tidelift security contact to report a security vulnerability.
Tidelift will coordinate the fix and disclosure.

主要指標

概覽
名稱與所有者activeadmin/inherited_resources
主編程語言Ruby
編程語言Ruby (語言數: 4)
平台
許可證MIT License
所有者活动
創建於2009-02-04 21:11:19
推送於2025-06-05 07:05:06
最后一次提交2025-06-05 09:05:04
發布數35
最新版本名稱v2.1.0 (發布於 2024-12-07 21:28:56)
第一版名稱v1.0.0 (發布於 )
用户参与
星數2.7k
關注者數46
派生數386
提交數1.1k
已啟用問題?
問題數310
打開的問題數1
拉請求數448
打開的拉請求數0
關閉的拉請求數194
项目设置
已啟用Wiki?
已存檔?
是復刻?
已鎖定?
是鏡像?
是私有?