The Skinny on Scopes (Formerly named_scope)
Rails 3.0
The source for the examples contained in this article are located at: http://github.com/rwdaigle/edgerails-support/tree/master/the-skinny-on-scopes-formerly-named-scope/
I remember my heart fluttering with a boyish crush the first time I saw Nick Kallen’s has_finder functionality make it into Rails in the form of named_scope. named_scope quickly made its way into my toolset as a great way to encapsulate reusable, chainable bits of query logic. While it had its downsides (namely its lack of first-class chain support for the likes of :joins and :include) it redefined how I thought about structuring my model logic. Once you taste the chainable goodness of named_scope you never go back.
So here we are with Rails 3 completely refactoring the internals of ActiveRecord - what’s up with our beloved named_scope? Well, the simple answer is that it’s been renamed to scope and you can use it just as you’re used to … but that’s taking the easy way out. Let’s see what else we can do with scope in Rails 3.
Basic Usage
Let’s assume a standard Post model with published_at datetime field along with title and body (to follow along in code, see the accompanying project in github).
In Rails 2.x here’s how we’d have to define the self-explanatory published and recent named scopes:
class Post < ActiveRecord::Base
named_scope :published, lambda {
{ :conditions =>
["posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now]
}
}
named_scope :recent, :order => "posts.published_at DESC"
end
The reason we need to use a lambda here is that it delays the evaluation of the Time.zone.now argument to when the scope is actually invoked. Without the lambda the time that would be used in the query logic would be the time that the class was first evaluated, not the scope itself.
With Rails 3 the bulk of ActiveRecord is now based on the Relation class. Think of relation as named_scope on steroids, weaving chainable query logic into the very fabric of ActiveRecord.
You can see how to use the individual where, order etc… commands of Relation on Pratik’s great writeup of the new query interface as well in this Railscast. Understanding these are important as the new scope is built upon them.
Let’s see how - here’s how the two named scopes from our previous Post example will look in Rails 3:
class Post < ActiveRecord::Base
scope :published, lambda {
where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now)
}
scope :recent, order("posts.published_at DESC")
end
While the bulk of the logic is the same - the SQL string portions - you start to see how scopes use the new query interface directly to create reusable query logic versus constructing an options hash as was done in Rails 2. This really is our first glimpse of how much more flexible the new query interface allows our scopes to be. No longer are they a slightly different construct than your normal query methods. They are now built upon the very same query methods that you would use were you to execute a query directly. This consistency is now prevalent all throughout ActiveRecord.
And there’s more…
Scope Reusability
Suppose we want to update our recent scope to only include published posts. We’ve already defined what published means and shouldn’t have to redefine it to create a new scope. Well, you can also chain scopes within scope definitions themselves as we’ll do here with the new recent and published_since scopes.
class Post < ActiveRecord::Base
scope :published, lambda {
where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now)
}
scope :published_since, lambda { |ago|
published.where("posts.published_at >= ?", ago)
}
scope :recent, published.order("posts.published_at DESC")
end
Ok, now we’re getting warmed up.
Dynamic Scope Construction
I’ve been in love with the scoped the anonymous named_scope constructor in Rails 2.3 for sometime now, using it to create dynamic and chainable scopes on an as-needed basis. One use-case you see a lot for this type of functionality is for creating a search method that you can still append other query manipulations onto.
For instance, to search our posts we can create this method which will return a scope for your caller to further filter (notice the use of scoped to start the chain off with an innocuous scope upon which others can be appended):
class Post < ActiveRecord::Base
class << self
# Search the title and body fields for the given string.
# Start with an empty scope and build on it for each attr.
# (Allows for easy extraction of searchable fields definition
# in the future)
def search(q)
[:title, :body].inject(scoped) do |combined_scope, attr|
combined_scope.where("posts.#{attr} LIKE ?", "%#{q}%")
end
end
end
end
The use of inject here somewhat obfuscates the intent of the method if you’re not used to looking at such iterations - here’s an easier to follow version with the searchable fields more hard coded (which actually doesn’t even use an anonymous scope to get bootstrapped):
class Post < ActiveRecord::Base
class << self
# The less-slick but, perhaps, more obvious version
def search(q)
query = "%#{q}%"
where("posts.title LIKE ?", query).where("posts.body LIKE ?", query)
end
end
end
Since we’re building upon the chainable goodness of then new query interface (think of scopes now as named bundles of the new ActiveRelation construct), we can do the following with the search method:
# What's in the db, titles ~= publish date
Post.all.collect(&:title) #=> ["1 week from now", "Now", "1 week ago", "2 weeks ago"]
Post.published.collect(&:title) #=> ["Now", "1 week ago", "2 weeks ago"]
# Search combinations
Post.search('1').collect(&:title) #=> ["1 week from now", "1 week ago"]
Post.search('1').published.collect(&:title) #=> ["1 week ago"]
Post.search('w').published_since(10.days.ago).collect(&:title) #=> ["Now", "1 week ago"]
Post.search('w').order('created_at DESC').limit(2).collect(&:title) #=> ["2 weeks ago", "1 week ago"]
You can imagine a scenario where more complex query-string support could be built, all using anonymous scopes.
Feels great, huh? It also feels a lot like the utility_scopes gem I released awhile ago which was my attempt to package up the chainable goodness of named_scope for common query operations. Rest-assured, there’s a much smoother implementation under the covers here in Rails 3 than just some hacks on top of named_scope
Cross-Model Scopes
Scopes are great for operating solely on the columns of a singular class’s table, but they can also be used to package cross-model queries (i.e. any SQL that would require a join). Let’s add in users (who can author and comment on posts) to the mix and write some scopes on User that will fetch only users that have authored published posts as well as users that have commented on a post:
class User < ActiveRecord::Base
has_many :posts, :foreign_key => :author_id
has_many :comments
# Get all users that have published a post
scope :published, lambda {
joins("join posts on posts.author_id = users.id").
where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now).
group("users.id")
}
# Get all users that have commented on a post
scope :commented, joins("join comments on comments.user_id = users.id").group("users.id")
end
Also, as Steffen pointed out in the comments, ActiveRelation is smart enough to know how to do a join based on an association definition, allowing us to collapse the joins relations from SQL strings to an association reference:
class User < ActiveRecord::Base
# Get all users that have published a post
scope :published, lambda {
joins(:posts). # No need to write your own SQL
where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now).
group("users.id")
}
# Get all users that have commented on a post
scope :commented, joins(:comments).group("users.id") # Just reference :comments
end
It’s a good practice to always refer to the full table_name.column_name when building scopes versus just the column_name itself (i.e.: posts.published_at vs. just published_at in the example above). This allows for unambiguous column references - especially important when building cross-model scopes where columns from more than one table are joined.
To be extra-flexible you can always invoke table_name in place of the hard-coded table name, though. to confess, this is a step I rarely take the time to implement myself: where("#{table_name}.published_at IS NOT NULL")
Since we’ve got the full arsenal of ActiveRelation operators at our disposal in scopes, we can do joins and group bys within scopes that will be safely chained in complex queries - something where the old named_scope crapped the bed:
# Get all users that have a post published
User.published.collect(&:username) #=> ["tim", "dave"]
User.published.to_sql
#=> SELECT "users".* FROM "users" join posts on posts.author_id = users.id
# WHERE (posts.published_at IS NOT NULL AND posts.published_at <= '2010-02-22 21:33:00.892308')
# GROUP BY users.id
# Get all users that have commented on a post
User.commented.collect(&:username) #=> ["ryan", "john", "tim", "dave"]
User.commented.to_sql
#=> SELECT "users".* FROM "users" join comments on comments.user_id = users.id
# GROUP BY users.id
# Combine them to get all authors that have also commented
User.published.commented.collect(&:username) #=> ["tim", "dave"]
User.published.commented.to_sql
#=> SELECT "users".* FROM "users"
# join posts on posts.author_id = users.id
# join comments on comments.user_id = users.id
# WHERE (posts.published_at IS NOT NULL AND posts.published_at <= '2010-02-22 21:33:00.892308')
# GROUP BY users.id
As I’ve done here, use scope#to_sql to peek at what SQL the scope will execute. Very useful for debugging purposes.
Scope-based Model CRUD
Since ActiveRelation lets you invoke all the builder/update/destroy methods on a relation that you’re used to using directly against your models, that power is also available at the end of a scope/scope-chain. Let’s play around with our post scopes and use them to do more than just query:
# Increment the views_count for all published posts
Post.published.collect(&:views_count) #=> [59, 71, 42]
Post.published.update_all("views_count = views_count + 1")
Post.published.collect(&:views_count) #=> [60, 72, 43]
# Nobody cares about unpublished posts
Post.unpublished.size #=> 1
Post.unpublished.destroy_all
Post.unpublished.size #=> 0
You can also create a new model from existing scopes - suppose we have a (very contrived) scoped that gets only posts of certain title:
class Post < ActiveRecord::Base
# Ludacris
scope :titled_luda, where(:title => 'Luda')
end
We can use this scope to directly build instances (as well as create, new, create! etc…):
Post.titled_luda.size #=> 0
Post.titled_luda.build
#=> #<Post id: nil, title: "Luda", ...>
In order to use the creation/builder methods on a scope, the scope should directly define attribute equality using a `where` relation and the hash form of the attribute values, as was done above.
Specifying where("title = 'Luda'") would not have propagated the attribute values to newly constructed instances.
Scopes really can be thought of now as named packages of both query and construction logic. Very powerful.
Crazy Town
One thing that’s always bugged me is how the logic for what makes a Post published is split between scopes in both the Post class and the User class. To refresh our collective memories:
class Post < ActiveRecord::Base
scope :published, lambda {
where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now)
}
end
And:
class User < ActiveRecord::Base
scope :published, lambda {
joins(:posts).
where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now).
group("users.id")
}
end
Most good developers will immediately cringe at the duplication of the where("posts.published_at IS NOT NULL AND posts.published_at <= ?", Time.zone.now) relation.
Thanks to a tip by Railscast’s Ryan Bates, there’s a pretty slick way to refer to, and combine, scope logic: the merge method, aliased as ‘&’. Let’s look at how we can use scope#& to refer to the query logic of the Post.published scope from within our User.published scope:
class User < ActiveRecord::Base
scope :published, lambda {
joins(:posts).group("users.id") & Post.published
}
end
Just so we’re clear what happens when you merge relations/scopes with the & operator, let’s look at the resulting SQL:
User.published.to_sql
#=> SELECT users.* FROM "users"
# INNER JOIN "posts" ON "posts"."author_id" = "users"."id"
# WHERE (posts.published_at IS NOT NULL AND posts.published_at <= '2010-02-27 02:55:45.063181')
# GROUP BY users.id
Notice how the conditions defined within Post.published are merged into the joins and group relations of the User.published scope? Very nice. And merging works with all the mergeable relations, not just where conditions we used here.
Summary
This post somewhat glosses over the new query interface for ActiveRecord in Rails 3 to get to the meat of using scopes. However, none of the scoped yumminess could have happened without the slick new underpinnings of ActiveRecord. So, if you’re still a little confused about all this, definitely read some more about ActiveRecord before jumping into scopes. Once you do have that foundation, however, you will use scopes on a very regular basis.
The following resources were instrumental in the research, creation and construction of this article. They may also provide a different angle should you be left wanting after reading this post:
Notes From The First Rails Online Conference
Rails 3.0
Earlier today the inaugural Rails Online Conf occurred and, Webex being a bag of hurt aside, it was a great experience. Basically, we got a high-level rundown of most of the big changes (internal and external) in Rails 3 from an all-star lineup of Railstuds. The slides are up on the site but if you want to save a single click-through, here they are again:
- Yehuda gives us the Rails 3 overview
- Gregg Pollack bats second with a spirited highlight reel of Rails 3 goodies
- Jeremy McAnally gives us the lowdown on upgrading from Rails 2 to 3
- Ryan Tomayko talks about Rack and Rails use of Rack to become more modular and extensible
Don’t forget to check out Jeremy’s upcoming Rails 3 Upgrade Handbook if you’re in the market for test driving all the sweetness Rails 3 has to offer. Jeremy’s a great writer and this 120-page manual is sure to impress.
The RailsConf program committee of Chad and Ben deserves a big “thanks” for putting on the online conf. My hope is that mini-events like this become more frequent in the future.
If you missed out on this event and aren’t a fan of visuals, you can always download the audio recording when it comes out.
Rails 3 Resources
Rails 3.0
While we get some momentum back here at EdgeRails.info I thought it’d be a good time to spotlight all the other great posts out there regarding new features in Rails 3. There are a lot of changes, both internally and externally, so buckle up:
Overviews
Here are some good overview posts that will wet your appetite for the juicier technical details below…
Yehuda, da man, has a few good writeups:
Jeremy McAnally has done a lot of work writing about Rails 3 (finally, dude, where ya been?) and has these writeups:
ActiveRecord/ActiveModel
ActiveRecord, while no longer the only persistence framework in town thanks to Rails’ new ORM agnosticism, has gotten a lot of love. Not only have validations and other non core-persistence functionality been pulled out into a new ActiveModel component but ActiveRecord itself now has a much more flexible, chainable and flexible API. You will like this.
A little about ActiveModel:
- Yehuda gives the lowdown on ActiveModel
- I talk a little bit more about the new independent validators
- Validations get sexy
- Some more validation goodness
And some meat and potatoes on ActiveRecord.
- Pratik talks about the new query interface
- Nick Kallen lays some heavy details about the making of the new query interface
ActionController
RESTful support has made it’s way from the routing layer and is now embedded deep into ActionController now:
- I run down the new respond_to RESTful response mechanism
- Jose Valim, the author, goes into more detail about the new RESTful controller support
- Jose adds some examples on the rails blog
- Yehuda explains the new render options
Routing
Routing, one of the hairier, and slicker, parts of the Rails stack has a slightly modified API and a very direct way to address rack mini-apps. Read about it here:
- Mikel Lindsaar continues his assault on the Rails scene with this great example of how to run a Sinatra app within Rails
- Yehuda gives a great overview of the new routing config
- Rizwan Reza gets down and dirty with some example routing blocks
- Yehuda talks about support for generic actions
ActionMailer
Even the black sheep of the family gets some attention now and then:
Gem Bundler
Rails 3 comes with a whole new way of specifying, loading and managing gem dependencies: the new gem bundler which was developed in parallel with the new version of Rails and replaces what you’re used to seeing as a bunch of config.gem statements:
- Mikel (you should now be on a first name basis with him by now) has a very complete rundown of the new bundler features
- Yehuda, one of the bundler project committers, gives a nice narrative of the bundler features
- Yehuda continues with some real world examples
Railties
Like Thanksgiving leftovers, railties are a grab-bag of goodies:
- Nick Quaranto talks about the new Rails module (no more RAILS_XXX constants)
- Paul Barry talks about creating customized generators
- Amp your generators using Thor
- Ben Scofield shows you how to create module app templates
There’s a new kid in town in Rails 3 - instrumentation. There’s not been much written about it yet but here are few nibbles to get you going:
- Gavin Stark writes about using instrumentation to log SQL calls
- The subscriber class is the main facilitator of instrumentation so give it a look
Plugins
A whole lot of effort was put into making the internal API of Rails much cleaner. Cleaner to the point that monkey-patching should be a thing of the past. One of the main benefactors of this are plugins, which have a whole new way of integrating with Rails apps:
- Yehuda gives you the lowdown on the official rails blog
- Yehuda gives you the behind the scenes take on plugins
- Nicolas Merouze shows you plugin developers out there how to upgrade your goodies
- EngineYard has set up a plugins listing site to publicize which are (and are not) Rails 3 compatible
Upgrading
Whew! If you’ve gotten to this point you may be wondering how to stitch this all together and make your now crusty Rails 2.3 app a good looking Rails 3 stud? Thankfully, you’re not alone.
Jeremy McAnally has been busting his hump to bring you the latest on upgrading:
And everybody’s favorite screencaster live codes an upgrade.
Rails 3 represents a huge amount of work, both in internal and external improvements. A big congrats to the whole team and to the community for providing such great content about these changes.
Let me know if I missed any great posts that you’ve found helpful, I know there are at least a few out there…
EdgeRails.Info Is Live
As I mentioned back on my personal site, the continuation and evolution of the What’s New in Edge Rails series continues here as EdgeRails.info. I’ve long wanted to give the Edge Rails series it’s own dedicated site and have finally pulled my act together to get this party started.
But, rather than just continue publishing my soliloquies under a new domain, I wanted to alter the direction and expand the site to accept direct input and contributions from the community. I feel like this mirrors the growth of the Rails framework itself, growing from a single person into a much more community-driven effort. Also, to be honest, it’s a lot of work to keep up with the sometimes frenzied pace of contributions to the edge rails branch of the repository and far be it for me to not admit when I’m the one holding things up. (I.e., keeping up with the firehouse that has been Rails 3 has been an impossible task for me). I also envision more than just play-by-plays of Rails commits - perhaps including some well-written tutorials, timely news etc…?
So how can you contribute? Well, I’m still figuring out the exact details, but the full content of the site is available on github and I fully intend to allow interested parties in using the reach of the site to publish both their own posts of new features they’re interested in as well as fixes to any mistakes in my posts (past and present). It will be a fully distributed and github-powered publishing and editorial process.
If you’re the motivated type and familiar with forking on github, feel free to dive right in and send contributions my way. If you’re a little on the shy side then hold tight, I’ll be putting some more detailed instructions up soon.
I also wanted to have the new site reflect the vitality of the Rails community by pulling in community activity from around the web and have added an “edgerails” tweets and recent Rails commits feed to the sidebar area. I’ll also be on the lookout for other relevant activity that can be pulled in here to enhance the tutorial nature of the main content.
I’d like to give a shout out to Brandon Mathis who pulled the initial site design together for me and whose Octopress framework is powering this Jekyll-generated static site. If you’re looking for a sharp designer who is great to talk to, designs clean and beautiful sites and understands reusability better than most developers, then Brandon is your man. Get in touch with him - you won’t be disappointed.
I’m stoked to start a new chapter in the Edge Rails series, let’s see where this party takes us, shall we?
Set Flash in redirect_to
Rails 2.3Rails’ flash is a convenient way of passing objects (though mostly used for message strings) across http redirects. In fact, every time you set a flash parameter the very next step is often to perform your redirect w/ redirect_to:
class UsersController < ApplicationController
def create
@user = User.create(params[:user])
flash[:notice] = "The user was successfully created"
redirect_to user_path(@user)
end
end
I know I hate to see two lines of code where one makes sense – in this case what you’re saying is to “redirect to the new user page with the given notice message” – something that seems to make more sense as a singular command.
DHH seems to agree and has added :notice, :alert and :flash options to redirect_to to consolidate commands. :notice and :alert automatically sets the flash parameters of the same name and :flash let’s you get as specific as you want. For instance, to rewrite the above example:
class UsersController < ApplicationController
def create
@user = User.create(params[:user])
redirect_to user_path(@user), :notice =>"The user was successfully created"
end
end
Or to set a non :alert/:notice flash:
class UsersController < ApplicationController
def create
@user = User.create(params[:user])
redirect_to user_path(@user), :flash => { :info => "The user was successfully created" }
end
end
I’ve become accustomed to setting my flash messages in :error, :info and sometimes :notice making the choice to provide only :alert and :notice accessors fell somewhat constrained to me, but maybe I’m loopy in my choice of flash param names.
Whatever your naming scheme, enjoy the new one-line redirect!
Independent Model Validators
Rails 3.0ActiveRecord validations, ground zero for anybody learning about Rails, got a lil’ bit of decoupling mojo today with the introduction of validator classes. Until today, the only options you had to define a custom validation was by overriding the validate method or by using validates_each, both of which pollute your models with gobs of validation logic.
ActiveRecord Validators
Validators remedy this by containing granular levels of validation logic that can be reused across your models. For instance, for that classic email validation example we can create a single validator:
class EmailValidator < ActiveRecord::Validator
def validate()
record.errors[:email] << "is not valid" unless
record.email =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
end
Each validator should implement a validate method, within which it has access to the model instance in question as record. Validation errors can then be added to the base model by adding to the errors collection as in this example.
So how do you tell a validator to operate on a model? validates_with that takes the class of the validator:
class User < ActiveRecord::Base
validates_with EmailValidator
end
Validation Arguments
This is all well and good, but is a pretty brittle solution in this example as the validator is assuming an email field. We need a way to pass in the name of the field to validate against for a model class that is unknown until runtime. We can do this by passing in options to validates_with which are then made available to the validator at runtime as the options hash. So let’s update our email validator to operate on an email field that can be set by the model requiring validation:
class EmailValidator < ActiveRecord::Validator
def validate()
email_field = options[:attr]
record.errors[email_field] << "is not valid" unless
record.send(email_field) =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
end
end
And to wire it up from the user model:
class User < ActiveRecord::Base
validates_with EmailValidator, :attr => :email_address
end
Any arguments can be passed into your validators by hitching a ride onto this options hash of validates_with.
Options & Notes
There are also some built-in options that you’ll be very familiar with, namely :on, :if and :unless that define when the validation will occur. They’re all the same as the options to built-in validations like validates_presence_of.
class User < ActiveRecord::Base
validates_with EmailValidator, :if => Proc.new { |u| u.signup_step > 2 },
:attr => :email_address
end
It’s also possible to specify more than one validator with validates_with:
class User < ActiveRecord::Base
validates_with EmailValidator, ZipCodeValidator, :on => :create
end
While this might seem like a pretty minor update, it allows for far better reusability of custom validation logic than what’s available now. So enjoy.
Default RESTful Rendering
Rails 3.0A few days ago I wrote about the new respond_with functionality of Rails 3. It’s basically a clean way to specify the resource to send back in response to a RESTful request. This works wonders for successful :xml and :json requests where the default response is to send back the integerized form of the resource, but still presents a lot of cruft when handling user-invoked :html requests (i.e. ‘navigational’ requests) and requests where error handling is required. For instance, consider your standard create action:
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
def create
@user = User.new(params[:user])
# Have to always override the html format to properly
# handle the redirect
if @user.save
flash[:notice] = "User was created successfully."
respond_with(@user, :status => :created, :location => @user) do |format|
format.html { redirect_to @user }
end
# Have to send back the errors collection if they exist for xml, json and
# redirect back to new for html.
else
respond_with(@user.errors, :status => :unprocessable_entity) do |format|
format.html { render :action => :new }
end
end
end
end
Even with the heavy lifting of respond_with you can see that there’s still a lot of plumbing left for you to do – plumbing that is mostly the same for all RESTful requests. Well José and the Rails team have a solution to this and have introduced controller responders.
Controller Responders
Controller responders handle the chore of matching the HTTP request method and the resource format type to determine what type of response should be sent. And since REST is so well-defined it’s very easy to establish a default responder to handle the basics.
Here’s what a controller utilizing responder support (now baked into respond_with) looks like:
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
def index
respond_with(@users = User.all)
end
def new
respond_with(@user = User.new)
end
def create
respond_with(@user = User.create(params[:user]))
end
def edit
respond_with(@user = User.find(params[:id]))
end
def update
@user = User.find(params[:id])
@user.update_attributes(params[:user])
respond_with(@user)
end
end
The built-in responder performs the following logic for each action:
- If the
:htmlformat was requested:- If it was a
GETrequest, invokerender(which will display the view template for the current action) - If it was a
POSTrequest and the resource has validation errors,render:new(so the user can fix their errors) - If it was a
PUTrequest and the resource has validation errors,render:edit(so the user can fix their errors) - Else, redirect to the resource location (i.e.
user_url)
- If it was a
- If another format was requested, (i.e.
:xmlor:json)- If it was a
GETrequest, invoke the:to_formatmethod on the resource and send that back - If the resource has validation errors, send back the errors in the requested format with the
:unprocessable_entitystatus code - If it was a
POSTrequest, invoke the:to_formatmethod on the resource and send that back with the:createdstatus and the:locationof the new created resource - Else, send back the
:okresponse with no body
- If it was a
Wading through this logic tree you can see that the default logic for each RESTful action is appropriately handled, letting your controller actions focus exclusively on resource retrieval and modification. And with that cruft out of the way your controllers will start to look even more similar – I suspect we’ll be seeing a solution for this coming around the bend shortly as well…?
So, just to recap the basics, here are a few action implementations side by side (the first being before responders and the latter being after):
# Old
def index
@users = User.all
respond_to do |format|
format.html
format.xml { render :xml => @users }
format.json { render :json => @users }
end
end
# New
def index
respond_with(@users = User.all)
end
# Old
def create
@user = User.new(params[:user])
if @user.save
flash[:notice] = "User successfully created"
respond_to do |format|
format.html { redirect_to @user }
format.xml { render :xml => @user, :status => :created,
:location => user_url(@user) }
format.json { render :json => @users, :status => :created,
:location => user_url(@user) }
end
else
respond_to do |format|
format.html { render :new }
format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
format.json { render :json => @user.errors, :status => :unprocessable_entity }
end
end
end
# New
def create
@user = User.new(params[:user])
flash[:notice] = "User successfully created" if @user.save
respond_with(@user)
end
Oh yeah, that’s getting real lean.
Overriding Default Behavior
If you need to override the default behavior of a particular format you can do so by passing a block to respond_with (as I wrote about in the original article):
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
# Override html format since we want to redirect to the collections page
# instead of the user page.
def create
@user = User.new(params[:user])
flash[:notice] = "User successfully created" if @user.save
respond_with(@user) do |format|
format.html { redirect_to users_url }
end
end
end
Nested Resources
It’s quite common to operate on resources within a nested resource graph (though I prefer to go one level deep, at most). For such cases you need to let respond_with know of the object hierarchy (using the same parameters as polymorphic_url):
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
# In this case, users exist within a company
def create
@company = Company.find(params[:company_id])
@user = @company.users.build(params[:user])
flash[:notice] = "User successfully created" if @user.save
# Ensure that the new user location is nested within @company,
# for html format (/companies/1/users/2.html) as well as
# resource formats (/companies/1/users/2)
respond_with(@company, @user)
end
end
If you have a singleton resource within your resource graph just use a symbol instead of an actual object instance. So to get /admin/users/1 you would invoke respond_with(:admin, @user).
Custom Responders
While there’s no facility to provide your own responder classes, it will no doubt be added shortly. If you look at the current responder class definition, it’s a very simple API essentially only requiring a call method (more intuitively take a look at the :to_html and :to_format methods).
Stay tuned here for further refinements to this very handy functionality – you’re going to see a lot more tightening in the coming weeks.
Cleaner RESTful Controllers w/ respond_with
Rails 3.0REST is a first-class citizen in the Rails world, though most of the hard work is done at the routing level. The controller stack has some niceties revolving around mime type handling with the respond_to facility but, to date, there’s not been a lot built into actionpack to handle the serving of resources. The addition of respond_with (and this follow-up) takes one step towards more robust RESTful support with an easy way to specify how resources are delivered. Here’s how it works:
Basic Usage
In your controller you can specify what resource formats are supported with the class method respond_*to*. Then, within your individual actions, you tell the controller the resource or resources to be delivered using respond_*with*:
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
def index
respond_with(@users = User.all)
end
def create
@user = User.create(params[:user])
respond_with(@user, :location => users_url)
end
end
This will match each supported format with an appropriate response. For instance, if the request is for /users.xml then the controller will look for a /users/index.xml.erb view template to render. If such a view template doesn’t exist then it tries to directly render the resource in the :xml format by invoking to_xml (if it exists). Lastly, if respond_with was invoked with a :location option the request will be redirected to that location (as in the case of the create action in the above example).
So here’s the equivalent implementation without the use of respond_with (assuming no index view templates):
class UsersController < ApplicationController::Base
def index
@users = User.all
respond_to do |format|
format.html
format.xml { render :xml => @users }
format.json { render :json => @users }
end
end
def create
@user = User.create(params[:user])
respond_to do |format|
format.html { redirect_to users_url }
format.xml { render :xml => @user }
format.json { render :json => @user }
end
end
end
You can see how much boilerplate response handling is now handled for you especially if it’s multiplied over the other default actions. You can pass in :status and :head options to respond_with as well if you need to send these headers back on resources rendered directly (i.e. via to_xml):
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
def index
respond_with(@users = User.all, :status => :ok)
end
end
Per-Action Overriding
It’s also possible to override standard resource handling by passing in a block to respond_with specifying which formats to override for that action:
class UsersController < ApplicationController::Base
respond_to :html, :xml, :json
# Override html format since we want to redirect to a different page,
# not just serve back the new resource
def create
@user = User.create(params[:user])
respond_with(@user) do |format|
format.html { redirect_to users_path }
end
end
end
:except And :only Options
You can also pass in :except and :only options to only support formats for specific actions (as you do with before_filter):
class UsersController < ApplicationController::Base
respond_to :html, :only => :index
respond_to :xml, :json, :except => :show
...
end
The :any Format
If you’re still want to use respond_to within your individual actions this update has also bundled the :any resource format that can be used as a wildcard match against any unspecified formats:
class UsersController < ApplicationController::Base
def index
@users = User.all
respond_to do |format|
format.html
format.any(:xml, :json) { render request.format.to_sym => @users }
end
end
end
So all in all this is a small, but meaningful, step towards robust controller-level REST support. I should point out that the contributor of this patch is José Valim who has authored the very robust inherited_resources framework that already has support for respond_with-like functionality and many more goodies. If you’re on the search for a solid RESTful controller framework to accompany Rails’ native RESTful routing support I would suggest you take a look at his fine work.
Database Seeding
Rails 2.3I’m not sure if this was ever stated explicitly has a preferred practice or not, but for the longest time many of us have recognized that using migrations as a way to populate the database with a base configuration dataset is wrong. Migrations are for manipulating the structure of your database, not for the data within it and certainly not for simple population tasks.
Well, this practice now has a formal support in Rails with the addition of the database seeding feature. Quite simply this is a rake task that sucks in the data specified in a db/seeds.rb. Here are the details:
Specify Seed Data
Add or open the db/seeds.rb file and put in model creation statements (or any ruby code) for the data you need to be present in order for your application to run. I.e. configuration and default data (and nothing more):
[:admin, :user].each { |r| Role.create(:name => r) }
User.create(:login => 'admin', :role => Role.find_by_name('admin'))
Load the Data
Once that is in place you can run one of two rake tasks that will populate the database with this data:
rake db:seed which will only populate the db with this data and rake db:setup which will create the db, load the schema and then load the seed data. This is the task you’ll want to use if you’re starting in a fresh environment.
So, quit overloading your migrations with seed data and use this new facility. But, don’t go overboard and use seeds.rb for test or staging datasets – it should only be used for the base data that is necessary for your app to run.
Touching
Rails 2.3.3There are often times when you want an update made to one object to be reflected up the object graph as an update of an associated parent object. For instance, if a new comment is created on an article, you may very well want to mark the article as being updated. With the new touch feature of ActiveRecord, this is a whole lot easier. Using our previous example, here’s is how it works:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
# Make create/update/deletes of a comment mark its
# parent article as updated
belongs_to :article, :touch => true
end
# Adding a new comment marks the article as being updated
article.updated_at #=> "Mon Apr 20 07:42:53 -0400 2009"
article.comments.create(:body => "New comment")
article.updated_at #=> "Mon Apr 20 07:43:27 -0400 2009"
# Same for updates/deletes
article.comments.first.destroy
article.updated_at #=> "Mon Apr 20 07:45:23 -0400 2009"
This is a great way to keep tightly coupled domain models in-sync without resorting to a potential maze of callback logic.
Also, if you have a timestamp field named something other than the standard updated_at or updated_on you can explicitly specify that field as the value to the :touch option and it will get marked instead:
class Article < ActiveRecord::Base
has_many :comments
validates_presence_of :last_updated_at # non-standard
end
class Comment < ActiveRecord::Base
belongs_to :article, :touch => :last_updated_at
end
# Adding a new comment marks the article as being updated
article.last_updated_at #=> "Mon Apr 20 07:42:53 -0400 2009"
article.comments.create(:body => "New comment")
article.last_updated_at #=> "Mon Apr 20 07:43:27 -0400 2009"
Also, somewhat conveniently, you can invoke touch directly on a model to update its timestamp outside any association callbacks:
article.updated_at #=> "Mon Apr 20 07:42:53 -0400 2009"
article.touch
article.updated_at #=> "Mon Apr 20 07:43:27 -0400 2009"
So, touch away (in a non-creepy kind of way)!