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.
Plus, making your controllers follow a pattern, it helps you to write better code by following fat models and skinny controllers convention. There is a screencast made by Fabio Akita about its features:
akitaonrails.com/2009/09/01/screencast-real-thin-restful-controllers-with-inherited-resources
Inherited Resources master branch is now supports Rails 3 and is NOT backward compatible.
You can install it as:
sudo gem install inherited_resources --version=1.1.0
If you want to use the Rails 2.3.x version, you should install:
sudo gem install inherited_resources --version=1.0.3
Or checkout from the v1.0 branch:
http://github.com/josevalim/inherited_resources/tree/v1.0
Since Inherited Resources 1.0, has_scope is not part of its core anymore. However, if you are using has_scope in your application, Inherited Resources will handle all the required hooks automatically.
has_scope gem is available at:
http://github.com/plataformatec/has_scope
And can be installed as:
sudo gem install has_scope
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:
http://github.com/plataformatec/responders
And it can be installed as:
sudo 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 ]
Rspec monkey patches a couple of controller methods in a way that Controller specs (with integrate views true or false) and Inherited Resources are not compatible.
However, since your controllers inherit from InheritedResources::Base, they are already unit-tested in the plugin, so there is no need to test them again in Rspec.
You should test things like url redirection and associations in your integration specs.
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 based 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 checkes 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
Whenever you inherit from InheritedResources, several defaults are assumed. For example you can have an AccountsController to account management while the resource is an 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 @projects ||= 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 doing:
class ProjectsController < InheritedResources::Base protected def begin_of_association_chain @current_user end end
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
Even more, since most of the times when you change a create, update or destroy action is because you want to to change to where it redirects, 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, that simple! The nice part is since you already set the instance variable @project, it will not build a project again.
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 to 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.
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
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
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.
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 CommentsConroller < InheritedResources::Base belongs_to :project belongs_to :task end
In other words, the code above is the same as calling nested_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.
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 ProjectsController < 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
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 association as singleton, you just have to give the :singleton option.
class ManagersController < InheritedResources::Base belongs_to :project, :singleton => true # singleton_belongs_to :project end
It will deal with everything again and hide the action :index from you.
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).
If you discover any bugs or want to drop a line, join us in the mailing list:
groups.google.com/group/inherited_resources
Copyright © 2009 José Valim blog.plataformatec.com.br See the attached MIT License.