Tuesday, May 06, 2008

A normal Rails un-RESTful page as a resource... or can it?

An index page comes with Rails scaffolding. It is used to show a list of the "thing" you are CRUD-ing on. However, all too often we are tasked to show some pages that we aren't doing any CRUD operations on. Just a few examples:

  • Dashboard page after user logs in

  • The "Forgot your password?" page

  • Tabs or subtabs of multiple lists

  • List page showing multiple lists of different entities

A normal dashboard page isn't something you would normally CRUD on. It is more like a place holder page that a user sees the first thing after s/he logs in, showing many items of all sorts valuable to a user. In many Rails app, depending on what those items are, the dashboard page will end up being rendered by one of the items' index action. Worse yet, next time when you actually need that index page by that model, you have to call it something else, maybe 'list', and then play with the routes to get it wired up correctly.

class CustomersController < ApplicationController

# The dashboard page
def index
@customers = Customer.find :all
@products = Product.find :all
@tasks_of_the_day = Task.find :all
end

# A list of customers
def list
@customers = Customer.find :all
end

end

ActionController::Routing::Routes.draw do |map|

map.resources :customers, :products, :tasks
map.with_options :controller => 'customer' do |r|
r.dashboard '/customers/list', :action => 'list'
end

end


Here's a suggestion: how about put it in views/dashboards/show.html.erb, and, while you are at it, give it a DashboardsController? Then, put it in your routes.rb as a (singular) map.resource :dashboard, like such:

class DashboardsController < ApplicationController

def show
@customers = Customer.find :all
@products = Product.find :all
@tasks_of_the_day = Task.find :all
end

end

ActionController::Routing::Routes.draw do |map|

map.resources :customers, :products, tasks
map.resource :dashboard

end


By rendering the dashboard page in a completely different controller, you now have a very readable GET dashboard_path named route (GET: http://localhost:3000/dashboards), and you will not contaminate the index action of your other models' controllers with instance variables of all kinds. You also have a more readable routes.rb file.

One of the examples above for non-RESTful pages is the "Forgot My Password" page. Can you think of a good way to do it the Rails REST-ful way? Please go to Seeing Rails Resources Clearly to share some of your thoughts.

4 comments:

Anonymous said...

This general discussion is a large part of my upcoming talk at Railsconf - I'm glad to see people thinking about these issues.

Incidentally, I generally model password actions as a PasswordReminder or PasswordReset resource (depending on the underlying logic)

Anonymous said...

I don't actually find that this makes sense to me.

To this end, i have written an article about why being RESTful has gone too far, and that actually, things which aren't resources shouldn't be RESTfulised.

The article is here.

Dan Rosenstark said...

Nice stuff, and it really opened my head up on this topic. It's true: Rails does not insist on ANY connection between your models (even though some may be tableless :), and your resources. The resources are defined to make the presentation layer of the app comprehensible. Then the controllers decide what models to talk to.

Anyway, here's my rambling on the same topic. Thanks again!

http://compileyouidontevenknowyou.blogspot.com/2009/07/adding-non-rest-actions-to-rails-apps.html

Anonymous said...

mr.chu...it appears you are a great technical expert...have you ever owned an apple computer?...to bite an apple..to byte an apple....daemon message failures..tree console...root.... -were to have created atom and ev..whoops...not evil...im...concerned...about cern...by the way...a robot...would be far superior...but doesnt have to be proven i suppose