REST Controller for RAILS

Time to continue the REST and Rails theme.

Rails’ most egregious violation of REST is that it exposes object methods
in URIs:

 http://myrailsapp.com/users/show
 http://myrailsapp.com/users/list
 http://myrailsapp.com/users/update  

Remember, one of the fundamental ideas of REST is to manipulate resources (represented by URIs) via the use of a limited set of verbs. For our purposes, the verbs that matter are HTTP’s GET, POST, PUT and DELETE.

Rails does not do this, instead it exposes public object methods on the Internet. This is no different than what DCOM, CORBA, RMI, XML-RPC, SOAP and your own home-grown RPC (remote procedure call) system do. And yes, every good developer has written an RPC system or two. In fact, I think that’s the main problem. Every developer I’ve ever met who writes a distributed computing system always implements some sort of RPC mechanism. I know I did it the first time. Its seems perfectly logical – let’s hide away the nasty details of the network so the developer doesn’t have to worry about it. Of course it doesn’t work, but that doesn’t mean that 9 out of 10 developers will tell you it does.

The good news of course is that RAILS is built on top of the Web, which is one of the most successful distributed computing system created and is not an RPC system. In addition, RAIL’s RPC implementation is much simpler to use than the ones I mentioned above and it strongly encourages you to create nice looking URIs which is a significant benefit.

So at this point, unless you’re a RESTafarian, you’re probably asking yourself “so what?” since RAILS is a very productive framework. We’ll let’s go through what would have to change to see if it has any interest to you.

Let’s start with the fundamental problem – Rails equates each URI with a single action as opposed to a single resource that can be manipulated via HTTP verbs. For example, let’s say we have a products table with an id field and a name field. Then let’s run the rails scaffold generator. If you look at the generated code, you’ll see its creates a number of public methods that manipulate several resources. More concretely, the following table shows how the generated methods map to resources
and HTTP verbs.

Mapping RAILS to REST

Resource
GET
POST
PUT
DELETE

products
list
create

each product
show

update
destroy

creator
new

editor
edit

Thus, I’ve identified four types of resources:

  • The resource that represents the collection
    of all products
  • Any number of resources that identify individual products
  • A creator resource
  • An editor resource

The creator and editor resources might be confusing. An analogy I’ve heard before that helps me is to imagine your resource is a Microsoft Word document. Does that mean that Microsoft Word itself is part of your resource? No, that doesn’t make sense. Instead, Microsoft Word is a resource that you can use to manipulate your document resource. Thus, the creator and editor resources provide a representation (i.e., an HTML document) that allows you to create new product resources or edit existing product resources.

So how would these map to URIs. Well, that’s pretty easy:

http://myrailsapp/product
http://myrailsapp/product/member/:id
http://myrailsapp/product/creator
http://myrailsapp/product/editor/:id  

Instead of the standard RAILS route of :controller/:action/:id we are now doing :controller/:resource/:id. In addition, sInce http://myrailsapp/product/member/:id is so common, I tend to shorten it using routes to:

 http://myrailsapp/product/:id 

Still with me? If so, then is it possible to fit these ideas into RAILS
which did not start with REST in mind? Well, I know of at least two previous attempts,
both which provided inspiration but in the end aren’t satisfactory. I think
this third attempt falls into the same camp, but hopefully will keep
the discussion going and throw another idea or two into the pot.

So here’s my take – feel free to download this plugin if
you’d like experiment with live code:

class ProductController < ApplicationController
  include RestController

  def get
    @product_pages, @products = paginate :products, :per_page => 10
  end

  def post
    @product = Product.new(params[:product])
    begin
      @product.save!
      flash[:notice] = 'Product was successfully created.'
      redirect_to :resource => :collection
    rescue => e
      flash[:product] = @product
      redirect_to :resource => :creator
    end
  end
  
  resource :Member do
    def get
      @product = Product.find(params[:id])
    end

    def put
      @product = Product.find(params[:id])
      begin
        @product.update_attributes(params[:product])
        flash[:notice] = 'Product was successfully updated.'
        redirect_to :id => @product
      rescue => e
        # Send the current invalid values to the editor via the flash
        flash[:product] = @product
        redirect_to :resource => :editor, :id => @product
      end
    end

    def delete
      Product.find(params[:id]).destroy
      redirect_to :id => nil, :resource => nil
    end
  end

  resource :Creator do
    def get
      @product = flash[:product] ||  Product.new
    end
  end
  
  resource :Editor do
    def get
      @product = flash[:product] || Product.find(params[:id])
    end
  end
end

Some things to notice about the code.

First, the REST support is added in via a mixin.

Second, we assume the controller itself represents the product collection resource and therefore define the get and post methods directly on it.

Third, other resources are demarcated by the resource keyword. Each resource can expose up to four methods – get, post, put and delete. However, in general only a subset of these methods makes sense for a given resource.

So how does this work under the hood? First, the resource keyword creates a Ruby module on the fly and includes it in the ProductController at compile time. As part of the merging process, the resource methods are renamed to include the resource name. Thus, the get method for the member resource becomes get_member. This is not ideal, but is needed to avoid name conflicts when multiple resources are included in a controller.

I also played with picking the correct resource at runtime and then merging
the resource into an instance of the productsController using Ruby’s extend
mechanism. That turned out to be fairly difficult to accomplish since
it interfered with the way that RAILS combines multiple modules into the
Base class in ActiveController (in particular I had problems with the flash). I also wondered about the performance implications, but didn’t do any testing
to see if there really were any.

Now let me tell you what I don’t like about the solution. The main issue is the method renaming. You have to know about it since you need to create templates called get.rhtml, get_memeber.rhtml, etc. It also comes into play if you want to turn on or off filters.

The second issue, umm….cough…., is that a pure REST solution does not work with HTML forms since browsers don’t support PUT and DELETE. You can get around this by using XmlHttpRequest which doe support PUT and DELETE. If you need to use PUT and DELETE in forms, then sadly you’ll have to add an action parameter to the URL like this – “action=put.” For those of you are annoyed about this like I am, first write your favorite browser maker a letter and then let the W3C have a piece of your mind since amazingly enough the HTML specification does not even mention the use of PUT and DELETE on forms. Hopefully, the WHATWG forum will solve this
issue in the future.

The last obvious issue you may have caught is look closely at the top level post method (or put). If the post fails we have to store the ill-formed product into the flash and redirect back to the editor since its at a different URL.

If you’d like to play with this you can download the plugin. Even better, also download the content negotiation plugin and you’ll have a RESTified version of RAILS.

  1. March 28, 2006

    Hi Charlie,

    I’m glad you and Peter are working on this, the different perspectives are interesting to read.

    From what I can tell its only the three of us looking at RESTful per-HTTP method dispatching in Rails — would you (and Peter) be interested in collaborating?

    On your specific implementation: my original Rails Rest Controller has a nearly identical API to yours. (with small cosmetic differences of course) You referenced my third interation in your post, but the first two are somewhat similar to yours. Here’s the announcement for RestController #1:

    http://microformats.org/discuss/mail/microformats-rest/2005-November/000042.html

    Starting last fall I began developing an application around my RestController, and I found one specific problem that I think you’ll run into soon since our APIs are similar.

    The problem is that for any given Resource, there will be a part of code where you instantiate the models. The code will be nearly identical for each per-method handler. (or should be, since the HTTP methods should be able to act on the same models) Your example does show a similar pattern in the Member resource, but it really won’t become prominent until you’re using multiple models in a single resource.

    To get around this I thought about making a special handler for initialization. Instead I decided to just
    use a plain rails action, and make the per-method handlers just be blocks that are executed when the request method matches, like so:

      def collection
        conditions < < @books = Book.find(:all)
    
        resource.post do
          @book = @books.build(params[:book])
          if @book.save
            render_post_success :action => 'by_id', :id => @book
          else
            render :action => 'new', :status => HTTP::Status::BAD_REQUEST
          end
        end
      end
    

    The advantage to this is that I can wrap a block within an if statement, and restrict access to method handlers on a per user basis. Its also less of a jump for normal rails developers and that problem you noted where the templates need to be named according to the request method goes away too.

    Also since each method is defined in a block, the API allows you to define response caching on a per-method basis, something I’ve found really helpful in practice.

    After developing with the above style for the last couple of months it seems more natural than my old approach.

    Reply
  2. Charlie Savage –
    March 29, 2006

    Hi Dan,

    Thanks for the very interesting comment – and it would be great to collaborate. When I wrote this post, I wasn’t aware that you had written a new version of your rest controller. You’re idea is quite intriguing, let me digest it a bit and then I’ll post another article.

    Reply

Leave a Reply

Your email address will not be published.

Top