InfoQ

News

Rails: Resource_controller Plugin Puts Controllers on a Diet

Posted by Rick DeNatale on Jan 31, 2008

Community
Ruby
Topics
REST ,
Ruby on Rails
Tags
Rails ,
Ruby on Rails ,
Rails Plugins
One guiding principle advocated by many Rails gurus is "fat models, and skinny controllers". Rails controllers should only have enough code to mediate between models and views. The business logic belongs in the models, not in controllers and views. With the current state of support for REST in Rails 2.0, controllers become quite similar, with seven basic actions (index, get, create, update, destroy, new, and edit), which tend to have very similar implementations. In Rails 2.0 pro-forma controllers can easily be generated using, say script/generate scaffold standard, which generates a RESTful chunk of code including a controller which looks like this:
class StandardsController < ApplicationController
# GET /standards
# GET /standards.xml
def index
@standards = Standard.find(:all)

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @standards }
end
end

# GET /standards/1
# GET /standards/1.xml
def show
@standard = Standard.find(params[:id])

respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @standard }
end
end

# GET /standards/new
# GET /standards/new.xml
def new
@standard = Standard.new

respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @standard }
end
end

# GET /standards/1/edit
def edit
@standard = Standard.find(params[:id])
end

# POST /standards
# POST /standards.xml
def create
@standard = Standard.new(params[:standard])

respond_to do |format|
if @standard.save
flash[:notice] = 'Standard was successfully created.'
format.html { redirect_to(@standard) }
format.xml { render :xml => @standard, :status => :created, :location => @standard }
else
format.html { render :action => "new" }
format.xml { render :xml => @standard.errors, :status => :unprocessable_entity }
end
end
end

# PUT /standards/1
# PUT /standards/1.xml
def update
@standard = Standard.find(params[:id])

respond_to do |format|
if @standard.update_attributes(params[:standard])
flash[:notice] = 'Standard was successfully updated.'
format.html { redirect_to(@standard) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @standard.errors, :status => :unprocessable_entity }
end
end
end

# DELETE /standards/1
# DELETE /standards/1.xml
def destroy
@standard = Standard.find(params[:id])
@standard.destroy

respond_to do |format|
format.html { redirect_to(standards_url) }
format.xml { head :ok }
end
end
end

Other than the specific names, all generated controllers look very much like this.

Using generated controllers is quite easy, and in many cases, little or no change needs to be made to the generated code, particularly if you take the "skinny controllers" mantra to heart.

On the other hand, another Ruby/Rails mantra is "don't repeat yourself", and having all that almost duplicate code, even if you didn't write it yourself, is a violation of the DRY principle.

Enter:  resource_controller.  James Golick offered up a new plugin for rails called resource_controller which allows the same controller shown above to be written as:

class StandardsController < ApplicationController
resource_controller
end

Well, there is a little bit of a white lie here, this won't give you the standard xml response capability, but that's easy to get back with a few lines:

class StandardsController < ApplicationController
resource_controller

index.wants.xml { render :xml => @standards }
[new, show].each do |action|
action.wants.xml { render :xml => @standard })
end
create.wants.xml { render :xml => @standard, :status => :created, :location => @standard }
[update, destroy].each do |action|
action.wants.xml { head :ok }
end
[create_fails, update_fails].each do |action|
action.wants.xml { render :xml => @standard.errors, :status => :unprocessable_entity }
end
end

This plugin makes writing controllers look more like writing models, with declarative class methods like resource_controller, and "callbacks" like action.wants. The plugin automatically gives the controller the right instance variable for each action, in this case either @standards for the index action or @standard for the others.

There are some common patterns in Rails which force changing controller code. One of these is nesting resources. It's easy to set up the routes in the config/routes.rb file

  map.resources :organization, :has_many => :standards 

But once you've done this, you need to change the controller to fetch and use the parent resource and use it appropriately for each action. The resource_controller plugin simplifies this. After the above routing change all that's needed is to add a declarative call to our controller

class StandardsController < ApplicationController
resource_controller
belongs_to :organization
end

The belongs_to declaration enables the controller to be used with the nested resource. Now when a controller action is reached through a nested resource URL, like /organization/1234/standards, the controller will automatically create an instance variable named @organization and set it appropriately, and will use the standards association of the parent object to find, and build instances of the model Standard.

Note that the same controller also works if the URL is not nested, so we can have another mapping in routes.rb which allows access to standards outside of an organization:

map.resources :standard
map.resources :organization, :has_many :standards

And the resource controller will automatically work in either context.

The plugin also handles namespaced controllers, polymorphic nested resources (similar and related to polymorphic associations in ActiveRecord) and other magic. You also get URL and path helper functions which work in the context of the URL in the request.

Resource_controller looks like a useful plugin, and it will no doubt get even better as it matures. The details are on James Golicks blog. There's also a rapid-fire screencast by Fabio Akita, which really shows what the plugin can do in action.
YAGN XML by James Golick Posted Jan 31, 2008 1:52 PM
make_resourceful by Geoffrey Grosenbach Posted Jan 31, 2008 6:53 PM
Re: make_resourceful by J Aaron Farr Posted Jan 31, 2008 10:51 PM
How to cleanly map nested resources like that please? by Raphaël Valyi Posted Feb 1, 2008 6:14 AM
RESTful controllers diets in general by Juan Maria Martinez Arce Posted Feb 1, 2008 6:50 AM
Kudos do James by Fabio Akita Posted Feb 8, 2008 8:21 PM
Re: Kudos do James by Fabio Akita Posted Feb 8, 2008 8:24 PM
Re: Kudos do James by Ben Scofield Posted Feb 12, 2008 7:10 PM
An alternative plugin by Philippe Lachaise Posted Feb 14, 2008 8:22 AM
  1. Back to top

    YAGN XML

    Jan 31, 2008 1:52 PM by James Golick

    Thanks for the write up!

    I wanted to mention that the reason I don't include XML in r_c, by default, is that I think it's a case of YAGNI. I would venture that there are a lot of rails apps in production with the XML format enabled, but completely untested, and missing some business logic, or exposing fields they aren't meant to be exposing, or worse...

    That said, in a future version of r_c, there'll likely be something to turn your example of how to turn XML back on in to a one-liner.

  2. Back to top

    make_resourceful

    Jan 31, 2008 6:53 PM by Geoffrey Grosenbach

    I've been using Hampton Catlin's make_resourceful plugin for a while. This is definitely the direction that we need to go to reduce repetitive code.

    Models already have many acts_as_plugins, but there's room for simplification through behaviors in controllers, too.

  3. Back to top

    Re: make_resourceful

    Jan 31, 2008 10:51 PM by J Aaron Farr

    There's also acts_as_resource. Definitely a clear pattern here.

  4. Guys,

    I had lot's of trouble mapping nested resources this way:
    /:country/top_level_resource_controller/:year/:month/:day/:text_id/second_level_nested_resource_controller/:id

    Any idea or pointer on how to automate such an URL pattern, especially for URL generation? Would those frameworks help?

    In route.rb, instead of resources, I had to use:
    map.connect ':country/top_level_resource_controller/:year/:month/:day/:text_id', :controller => 'top_level_resource_controller', :action => 'show'
    for every action requiring an item id.
    And then it was easier for nested resources:
    map.resources :second_level_nested_resource, :path_prefix => '/:locale/top_level_resource_controller/:year/:month/:day/:text_id'

    But the URL generation to link between actions was totally hard coded in helpers. How to make it better?


    Why I choosed those URL patterns?
    1) I need the country in the URL (to use the same app for several countries), but I don't want to make it a resources in order to keep the URL simple and pertinent.
    2) I need to split the id of my top_level_resource in :year, :month, :day and :text_id because I use static page cache, I have many of those resources, and I need a fast access. So I needed to split the cache in hierarchical folders, not everything in a single folder. Moreover, I'm using the :text_id rather than the natural :id key to optimize SEO.
    3) finally I have normal nested resources inside that stuff, just like second_level_nested_resource

    Any pointer to make URL generation clean please? Thanks in advance,

    Raphaël Valyi.

  5. Back to top

    RESTful controllers diets in general

    Feb 1, 2008 6:50 AM by Juan Maria Martinez Arce

    This is an issue that has been around since the REST support addition to Rails, and it's something that has been worked out by the community with a very interested amount of solutions. I believe Hampton Catlin's make_resourceful plugin is maybe the most elegant solution for this "dryfication" issue, in fact this plugin inspired me to build my own (administrate_me, you can check it at administrateme.googlecode.com) which I'm still using to carried out my projects.

    From the experience I extract this idea: any of this plugin for RESTful controllers code reduction will be useful if It helps you to not enclose your project between extra and not wanted complexity walls.

  6. Back to top

    Kudos do James

    Feb 8, 2008 8:21 PM by Fabio Akita

    I am very happy for being able to help a little bit on delivering the message. Home my screencast helps. James' plugin is amazing and I can see a bright future for it as it is easily extensible.

  7. Back to top

    Re: Kudos do James

    Feb 8, 2008 8:24 PM by Fabio Akita

    By the way, I am not very active on the Rails Core list. Didn't anyone suggest such a simplification on the default Restful model in Rails? Either Caitlin's or James' approach could be merged back to the Edge so everybody gains a default way, top to bottom (controller to view) with easily manageable controllers+views being nested wherever you want at your hearts content.

  8. Back to top

    Re: Kudos do James

    Feb 12, 2008 7:10 PM by Ben Scofield

    Not sure if anyone's suggested this, but I'd argue that adopting m_r or resource_controller as out-of-the-box Rails behavior would be a bad idea. These plugins can be very useful, but there's a lot of magic in them - and that's not always a good thing. Granted, m_r is much worse about this than James's plugin, but they both still hide a great deal of code that doesn't actually belong in the framework.

    I talked a bit about this a few months ago, if anyone would like to see some more conversation on the topic.

  9. Back to top

    An alternative plugin

    Feb 14, 2008 8:22 AM by Philippe Lachaise

    DRYing up restful controller has also been my concern for a while

    Here's a plugin of mine that adress this same need :
    miceplugins.lachaise.org/mice_restful/trunk (SVN : "scrip/plugin install" should work)

    Doc is the code (sorry about that) but some in-depth explanations are to be found her (in french, sorry again, but contains code examples) :
    blog.lachaise.org/?p=3

    At its simplest, this single statement,
    restful_methods
    will generate for you all the basic code (format both html and xml) but quite a few more complex real-life cases are handled.

    In particular two-levels resource nesting is supported.

    e.g

    restful_methods :only => [:current_parent, :index, :show],
    :parent_model => :item,
    :include => { :catalog_item => { :catalog => :shop } },
    :batch => :batch_handler,
    :batch_redirect => [:organization_item_path, { :organization_id => :@current_organization, :list_id => :'@current_item.list', :id => :@current_item } ],
    :before_index => :before_index


    Anyway, it it happens to be as useful for someone as it has been for my (rather big and com plex) application I would gladly share information with anyone interested and may consider turn it this code into a properly packaged plugin.

Educational Content

Brian Marick on 4 Challenges and 5 Guiding Values of Agile Software Development

Brian Marick takes us through a quick tour of the most important values and challenges to adopting Agile successfully (they aren't the typical challenges and values we hear in the community).

Are You a Software Architect?

The line between development and architecture is tricky. Does it exist at all? Is an ivory tower actually needed? There's a balance in the middle, but how do you move from developer to architect?

Agile – A Way of Life and Pragmatic Use of Authority

The word 'authority' sometimes produces an allergic response in hard-line agilists. Freedom and authority – both are bad if misused and both are good if used in right spirit for a noble cause.

Getting Started with Grails, Second Edition

"Getting Started with Grails" brings you up to speed on this modern web framework. Companies as varied as LinkedIn, Wired, and Taco Bell are all using Grails. Are you ready to get started as well?

Using ITIL V3 as a Foundation for SOA Governance

Those familiar with only ITIL V2 often scoff at the thought that ITIL could serve as a governance framework for SOA. With ITIL V3, the focus of the framework shifted towards service-orientation.

Adrian Colyer on AspectJ, tc Server and dm Server

SpringSource CTO Adrian Colyer discusses AspectJ, SpringSource's dm Server and tc Server products, OSGi and Scrum.

Adam Wiggins on Heroku

Heroku's Adam Wiggins talks about Rails, Background Jobs, Add-Ons, Ruby, and how Heroku manages to work around Ruby's inefficiencies using Erlang and other languages.

SOA as an Architectural Pattern: Best Practices in Software Architecture

For Grady Booch the foundation of a good architecture is patterns, SOA being just one of many patterns. In this Second Life presentation, Booch attempts to bring more clarity on what architecture is.