What's New in Edge Rails: Skinny on Scopes
I go into a detailed explanation of using ActiveRecord scopes in Rails 3 over on EdgeRails.info.
I won’t be cross-posting for too much long, so update your feed to the new EdgeRails feed to keep abreast of the latest and greatest!
- Tags: Edge Rails
- Meta: permalink
'What's New in Edge Rails' Moves to EdgeRails.info
For awhile I’ve wanted to move the “What’s New in Edge Rails” series to its own site to reflect the fact that it is an independent and self-sustaining series and not some small figment of my mind anymore. I started writing the What’s New series about four years ago and it’s clear it needs to be treated like a first-class citizen. While the move is still a work in progress, I’m proud to say that EdgeRails.info is now live and is where all future What’s New in Edge Rails content will be published (including some Rails 3 updates).
I won’t repeat too much here, but one of the big changes is that I want to take a much more community driven approach to bringing you the latest in updates to the framework and will be harnessing a GitHub-centric process towards letting you both contribute and update posts.
So update your feed and head over to EdgeRails.info. I’m all ears, so flame away if you’re feeling so inclined. And thanks for all your contributions, comments and feedback that past four years – they’ve made the work worthwhile and I hope I can continue the momentum on the new site.
I’ll probably give EdgeRails.info a few weeks to stand on its own before flipping the DNS switch, at which point all links to articles here will be redirected to EdgeRails.
See you on the flip side, home-slice.
- Tags: Edge Rails
- Meta: permalink
What's New in Edge Rails: Set Flash in redirect_to
This feature is schedule for: Rails v2.3 stable
Rails’ 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
:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 |
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!
tags: ruby, rubyonrails
- Tags: Edge Rails
- Meta: permalink
What's New in Edge Rails: Independent Model Validators
This feature is schedule for: Rails v3.0
ActiveRecord 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:
1 2 3 4 5 6 |
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:
1 2 3 |
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:
1 2 3 4 5 6 7 |
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:
1 2 3 |
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
.
1 2 3 4 |
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
:
1 2 3 |
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.
tags: ruby, rubyonrails
- Tags: Edge Rails
- Meta: permalink
What's New in Edge Rails: Default RESTful Rendering
This feature is schedule for: Rails v3.0
A 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 serialized 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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
:html
format was requested:- If it was a
GET
request, invokerender
(which will display the view template for the current action) - If it was a
POST
request and the resource has validation errors,render
:new
(so the user can fix their errors) - If it was a
PUT
request 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.
:xml
or:json
)- If it was a
GET
request, invoke the:to_format
method on the resource and send that back - If the resource has validation errors, send back the errors in the requested format with the
:unprocessable_entity
status code - If it was a
POST
request, invoke the:to_format
method on the resource and send that back with the:created
status and the:location
of the new created resource - Else, send back the
:ok
response 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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.
tags: ruby, rubyonrails
- Tags: Edge Rails
- Meta: permalink
What's New in Edge Rails: Cleaner RESTful Controllers w/ respond_with
This feature is schedule for: Rails v3.0
REST 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*
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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
):
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
):
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
class UsersController < ApplicationController::Base def index @users = User.all |