Agile Web Development

Build it. Launch it. Love it.

resources_controller

With resources_controller (http://svn.ardes.com/rails_plugins/resources_controller) you can quickly add an ActiveResource compliant controller for your your RESTful models.

Here are some examples - for more on how to use RC go to the Usage section at the bottom, for syntax head to resources_controller_for

Example 1: Super simple usage

Here’s a simple example of how it works with a Forums has many Posts model:

  class ForumsController < ApplicationController
    resources_controller_for :forums
  end

Your controller will get the standard CRUD actions, @forum will be set in member actions, @forums in index.

Example 2: Specifying enclosing resources

  class PostsController < ApplicationController
    resources_controller_for :posts, :in => :forum
  end

As above, but the controller will load @forum on every action, and use @forum to find and create @posts

Wildcard enclosing resources

All of the above examples will work for any routes that match what it specified

             PATH                     RESOURCES CONTROLLER WILL DO:

 Example 1  /forums                   @forums = Forum.find(:all)

            /users/2/forums           @user = User.find(2)
                                      @forums = @user.forums.find(:all)

 Example 2  /posts                    @posts = Post.find(:all)

            /forums/2/posts           @forum = Forum.find(2)
                                      @posts = @forum.posts.find(:all)

            /sites/4/forums/3/posts   @site = Site.find(4)
                                      @forum = @site.forums.find(3)
                                      @posts = @forum.posts.find(:all)

            /users/2/posts/1          This won't work as the controller specified
                                      that :posts are :in => :forum

It is up to you which routes to open to the controller (in config/routes.rb). When you do, RC will use the route segments to drill down to the specified resource. This means that if User 3 does not have Post 5, then /users/3/posts/5 will raise a RecordNotFound Error. You dont’ have to write any extra code to do this oft repeated controller pattern.

With RC, your route specification flows through to the controller - no need to repeat yourself.

If you don’t want to have RC match wildcard resources just pass :load_enclosing => false

  resources_controller_for :posts, :in => :forum, :load_enclosing => 'false'

Example 3: Singleton resource

Here’s an example of a singleton, the account pattern that is so common.

  class AccountController < ApplicationController
    resources_controller_for :account, :class => User, :singleton => true do
      @current_user
    end
  end

Your controller will use the block to find the resource. The @account will be assigned to @current_user

Example 4: Allowing PostsController to be used all over

First thing to do is remove :in => :forum

  class PostsController < ApplicationController
    resources_controller_for :posts
  end

This will now work for /users/2/posts.

Example 4 and a bit: Mapping non standard resources

How about /account/posts? The account is found in a non standard way - RC won’t be able to figure out how tofind it if it appears in the route. So we give it some help.

(in PostsController)

  map_resource :account, :singleton => true, :class => User, :find => :current_user

Now, if :account apears in any part of a route (for PostsController) it will be mapped to (in this case) the current_user method of teh PostsController.

To make the :account mapping available to all, just chuck it in ApplicationController

This will work for any resource which can’t be inferred from its route segment name

  map_resource :peeps, :source => :users
  map_resource :posts, :class => BadlyNamedPostClass

Example 5: Singleton association

Here’s another singleton example - one where it corresponds to a has_one or belongs_to association

  class ImageController < ApplicationController
    resources_controller_for :image, :singleton => true
  end

When invoked with /users/3/image RC will find @user, and use @user.image to find the resource, and @user.build_image, to create a new resource.

Putting it all together

An exmaple app

config/routes.rb:

 map.resource :account do |account|
   account.resource :image
   account.resources :posts
 end

 map.resources :users do |user|
   user.resource :image
   user.resources :posts
 end

 map.resources :forums do |forum|
   forum.resources :posts
   forum.resource :image
 end

app/controllers:

 class ApplicationController < ActionController::Base
   map_resource :account, :singleton => true, :find => :current_user

   def current_user # get it from session or whatnot
 end

 class ForumsController < AplicationController
   resources_controller_for :forums
 end

 class PostsController < AplicationController
   resources_controller_for :posts
 end

 class UsersController < AplicationController
   resources_controller_for :users
 end

 class ImageController < AplicationController
   resources_controller_for :image, :singleton => true
 end

 class AccountController < ApplicationController
   resources_controller_for :account, :singleton => true, :find => :current_user
 end

This is how the app will handle the following routes:

 PATH                   CONTROLLER    WHICH WILL DO:

 /forums                forums        @forums = Forum.find(:all)

 /forums/2/posts        posts         @forum = Forum.find(2)
                                      @posts = @forum.forums.find(:all)

 /forums/2/image        image         @forum = Forum.find(2)
                                      @image = @forum.image

 /image

 /posts

 /users/2/posts/3       posts         @user = User.find(2)
                                      @post = @user.posts.find(3)

 /users/2/image POST    image         @user = User.find(2)
                                      @image = @user.build_image(params[:image])

 /account               account       @account = self.current_user

 /account/image         image         @account = self.current_user
                                      @image = @account.image

 /account/posts/3 PUT   posts         @account = self.current_user
                                      @post = @account.posts.find(3)
                                      @post.update_attributes(params[:post])

Views

Ok - so how do I write the views?

For most cases, just in exactly the way you would expect to. RC sets the instance variables to what they should be.

But, in some cases, you are going to have different variables set - for example

  /users/1/posts    =>  @user, @posts
  /forums/2/posts   =>  @forum, @posts

Here are some options (all are appropriate for different circumstances):

  • test for the existence of @user or @forum in the view, and display it differently
  • have two different controllers UserPostsController and ForumPostsController, with different views (and direct the routes to them in routes.rb)
  • use enclosing_resource - which always refers to the… immediately enclosing resource.

Using the last technique, you might write your posts index as follows (here assuming that both Forum and User have .name)

  Posts for <%= link_to enclosing_resource_path, "#{enclosing_resource_name.humanize}: #{enclosing_resource.name}" %>

  <%= render :partial => 'post', :collection => @posts %>

Notice enclosing_resource_name - this will be something like ‘user’, or ‘post’. Also enclosing_resource_path - in RC you get all of the named route helpers relativised to the current resource and enclosing_resource. See NamedRouteHelper for more details.

This can useful when writing the _post partial:

    <%= post.name %>
    <%= link_to 'edit', edit_resource_path(tag) %>
    <%= link_to 'destroy', resource_path(tag), :method => :delete %>

when viewed at /users/1/posts it will show

   Cool post
   edit
   delete

 ...

when viewd at /forums/1/posts it will show

   Other post
   edit
   delete

 ...

This is like polymorphic urls, except that RC will just use whatever enclosing resources are loaded to generate the urls/paths.

To use RC, there are just three class methods on controller to learn.

resources_controller_for , , <&block>

ClassMethods#nested_in , , <&block>

map_resource , , <&block>

Customising finding and creating

If you want to implement something like query params you can override find_resources. If you want to change the way your new resources are created you can override new_resource.

  class PostsController < ApplicationController
    resources_controller_for :posts

    def find_resources
      resource_service.find :all, :order => params[:sort_by]
    end

    def new_resource
      returning resource_service.new(params[resource_name]) do |post|
        post.ip_address = request.remote_ip
      end
    end
  end

In the same way, you can override find_resource.

Writing controller actions

You can make use of RC internals to simplify your actions.

Here’s an example where you want to re-order an acts_as_list model. You define a class method on the model (say order_by_ids which takes and array of ids). You can then make use of resource_service (which makes use of awesome rails magic) to send correctly scoped messages to your models.

Here’s how to write an order action

  def order
    resource_service.order_by_ids["things_order"]
  end

the route

  map.resources :things, :collection => {:order => :put}

and the view can conatin a scriptaculous drag and drop with param name ‘things_order’

When this controller is invoked of /things the :order_by_ids message will be sent to the Thing class, when it’s invoked by /foos/1/things, then :order_by_ids message will be send to Foo.find(1).things association

Vitals

Home http://blog.ardes.com/resources_controller
Repository http://svn.ardes.com/rails_plugins/resources_controller/
License Rails' (MIT)
Tags Tag_red ActionController activerecord ActiveResource ActiveResource cacher generator REST
Rating (23 votes)
Owner Ian White
Created 1 February 2007

Comments

  • Avatar
    viktor tron
    6 November 2007

    This is a very well maintained, rails2 and REST compliant plugin. well done The correct path to the blog is http://blog.ardes.com/resources_controller

Add a comment