What's New in Edge Rails: Skinny on Scopes

Posted by ryan
at 10:16 PM on Friday, February 26, 2010

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!

'What's New in Edge Rails' Moves to EdgeRails.info

Posted by ryan
at 9:06 AM on Monday, February 08, 2010

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.

What's New in Edge Rails: Set Flash in redirect_to

Posted by ryan
at 12:39 PM on Sunday, December 20, 2009

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

What's New in Edge Rails: Independent Model Validators

Posted by ryan
at 10:13 PM on Monday, August 10, 2009

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

What's New in Edge Rails: Default RESTful Rendering

Posted by ryan
at 9:39 AM on Monday, August 10, 2009

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, invoke render (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 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

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

What's New in Edge Rails: Cleaner RESTful Controllers w/ respond_with

Posted by ryan
at 9:52 PM on Wednesday, August 05, 2009

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

    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.

tags: ruby, rubyonrails

What's New in Edge Rails: Database Seeding

Posted by ryan
at 8:44 AM on Wednesday, May 13, 2009

This feature is schedule for: Rails v3.0

I’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):

1
2
[: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.

tags: ruby, rubyonrails

What's New in Edge Rails: Touching

Posted by ryan
at 7:51 AM on Monday, April 20, 2009

This feature was released in Rails v2.3.3

There 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
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)!

tags: ruby, rubyonrails

What's New in Edge Rails: Batched Find

Posted by ryan
at 4:30 PM on Monday, February 23, 2009

This feature is scheduled for: Rails v2.3

ActiveRecord got a little batch-help today with the addition of ActiveRecord::Base#find_each and ActiveRecord::Base#find_in_batches. The former lets you iterate over all the records in cursor-like fashion (only retrieving a set number of records at a time to avoid cramming too much into memory):

1
2
3
Article.find_each { |a| ... } # => iterate over all articles, in chunks of 1000 (the default)
Article.find_each(:conditions => { :published => true }, :batch_size => 100 ) { |a| ... }
  # iterate over published articles in chunks of 100

You’re not exposed to any of the chunking logic – all you need to do is iterate over each record and just trust that they’re only being retrieved in manageable groups.

find_in_batches performs a similar function, except that it hands back each chunk array directly instead of just a stream of individual records:

1
2
3
4
Article.find_in_batches { |articles| articles.each { |a| ... } }
  # => articles is array of size 1000
Article.find_in_batches(batch_size => 100 ) { |articles| articles.each { |a| ... } }
  # iterate over all articles in chunks of 100

find_in_batches is also kind enough to observe good scoping practices:

1
2
3
4
5
6
class Article < ActiveRecord::Base
  named_scope :published, :conditions => { :published => true }
end

Article.published.find_in_batches(:batch_size => 100 ) { |articles| ... }
  # iterate over published articles in chunks of 100

One quick caveat exists: you can’t specify :order or :limit in the options to find_each or find_in_batches as those values are used in the internal looping logic.

Batched finds are best used when you have a potentially large dataset and need to iterate through all rows. If done using a normal find the full result-set will be loaded into memory and could cause problems. With batched finds you can be sure that only 1000 * (each result-object size) will be loaded into memory.

tags: ruby, rubyonrails

Rails 2.3 Released - Summary of Features

Posted by ryan
at 7:43 PM on Sunday, February 01, 2009

Here’s a list of the major new features in Rails v2.3 (most recent first):

Rails 2.3 Features

The next scheduled release appears to be the 3.0 release, the merger of Rails and Merb. The core team has alluded to at least a preview release to be ready by RailsConf 2009. Seems a bit aggressive for all the internal tinkering in store, but stay here for for the latest.

You should also check out the Rails 2.3 release notes for the ‘official’ rundown of new features for 2.3.

tags: ruby, rubyonrails

What's New in Edge Rails: Nested Object Forms

Posted by ryan
at 9:59 AM on Sunday, February 01, 2009

This feature is scheduled for: Rails v2.3

We were all teased a few months ago about the possibility of finally solving the nested model/complex forms problem in Rails, but were then cruelly notified that it wasn’t quite ready for prime time. But our day has come – the most requested feature for Rails 2.3, the ability to handle multiple models in a single form, is here.

This API update was added after the original commit. I’ve updated the examples here to account for this.

This feature has already been written about on the Rails Blog quite well by Eloy Duran, the committer of this fine feature, so I’ll try not to replicate what’s already out there. However, here’s a basic rundown of what you need to do to get your models nested-form capable.

Step 1: Notify Your Model of Nest-able Associations

The first step is to tell your models which of their associations will be able to receive nested attributes. For all associations you want exposed in nested forms you’ll need to use accepts_nested_attributes_for:

1
2
3
4
5
6
7
8
9
class Person < ActiveRecord::Base

  validates_presence_of :name

  has_many :children, :class_name => 'Person'
  accepts_nested_attributes_for :children, :allow_destroy => true
    # can also be used on has_one etc.. associations

end

With this bit in place, you can now directly create, edit and delete children from a person:

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
# Add a new child to this person
@person.children_attributes = [ { :name => 'Son' } ]
@person.children #=> [ <#Person: name: 'Son'> ]
@person.children.clear

# Add two new children to this person
@person.children_attributes =
  [ { :name => 'Son' }, { :name => 'Daughter' } ]
@person.save
@person.children #=> [ <#Person: name: 'Son'>, <#Person: name: 'Daughter'> ]

# Edit the son (assuming id == 1)
@person.children_attributes = [ { :id => 1, :name => 'Lad' } ]
@person.save
  #=> the son's name is now 'Lad'

# Edit the daughter (id == 2) and add a new offspring
@person.children_attributes =
  [ { :id => 2, :name => 'Lassie' }, { :name => 'Pat' } ]
@person.save
  #=> the daughter's name is now 'Lassie' and there's a new offspring called 'Pat'

# Remove Pat (id = 3), we don't like him/her
@person.children_attributes = [ :id => 3, '_delete' => '1' } ]  # !!! In Rails 3 this is now "_destroy" !!!
@person.save
  #=> Pat is now deleted
You’ll want to take away a few things from these examples.
  • To support both the creation of new objects and the editing of existing ones we have to use an array of hashes for one-to-many associations or a single hash for one-to-one associations. If no :id property exists then it is assumed to represent a nested model to create.
  • To delete an existing nested model, use this format: [ :id => pk, '_delete' => '1' } ] where the value of _delete evaluates to anything true. You must also set the accepts_nested_attributes_for option :allow_destroy to true as that capability is turned off by default.
  • In Rails 3 the aforementioned _delete param is now called _destroy

While this may appear a bit hackish when you’re used to dealing with the pleasantries of a rich object model and with ActiveRecord’s associations, this provides the foundation for a seamless transition to the view where you need to create your nested model forms…

Step 2: Create a Nested Model Form

In the view, simply use fields_for on these nested models to expose the fields for each such model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<% form_for @person do |person_form| %>

  <%= person_form.label :name %>
  <%= person_form.text_field :name %>

  <% person_form.fields_for :children do |child_form| %>

    <%= child_form.label :name %>
    <%= child_form.text_field :name %>

    <% unless child_form.object.new_record? %>
      <% # Don't forget to name both of these '_destroy' in Rails 3 %>
      <%= child_form.check_box '_delete' %>
      <%= child_form.label '_delete', 'Remove' %>
    <% end %>

  <% end %>

  <%= submit_tag %>
<% end %>

This will create a form with all the form fields necessary for submitting to a RESTful controller, transparently pushing your children_attributes onto the person.

If there are any validation errors on a child, they will be added to person.errors, and nothing will save if any of the children fail (i.e. fully transactional).

A few notes that might be useful to you:
  • Using fields_for on a has_many association automatically executes once for each nested model present, so think of yourself as being inside a loop when building your child_form
  • If you ever need to change behavior based on the nested model currently in scope, it can be accessed via child_form.object. In this example we use child_form.object.new_record? to determine whether or not to display the delete checkbox (as that only makes sense on an existing record).

Step 3: In Your Controllers … Do Nothing

The third step should be the easiest, because we’re all dealing with purely RESTful controllers, right? The beauty of this solution is that it takes your controllers out of the mix and makes standard for submissions work perfectly with no interference at the controller level. Just so there’s no confusion, here’s how your create and update actions will look:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PersonController < ApplicationController

  def create
    @person = Person.new(params[:person])
    @person.save ? redirect_to(person_path(@person)) : render(:action => :new)
  end

  def update
    @person = Person.find(params[:id])
    @person.update_attributes(params[:person]) ?
      redirect_to(person_path(@person)) : render(:action => :edit)
  end

end

Not a peep of those pesky nested models – with the rich support for nested objects at the model layer, it just works!

Extras

As with most powerful features, there are few little tweaks you may find yourself needing.

Default Create Form Fields

Often times you’ll want to have the form displayed with empty fields for easily creating a new nested model. For example, when a user goes to create a new person I want there to be fields for creating a new child already displayed.

Since the person object is brand new they have an empty children collection and no child_form fields will be displayed. There are two ways to get around this:

You can build a new nested object on the controller side (i.e. in the new action):

1
2
3
4
5
  def new
    @person = Person.new
    @person.children.build
    # ...
  end

which will cause there to be empty child_form fields displayed as desired. Or you can do it on the view side with a view helper:

1
2
3
4
5
6
7
module ApplicationHelper
  def setup_person(person)
    returning(person) do |p|
      p.children.build if p.children.empty?
    end
  end
end

Which can then be used within form_for to setup the person to the correct form state:

1
2
3
<% form_for setup_person(@person) do |person_form| %>
  <!-- ... -->
<% end %>

I prefer this view-helper approach as it really is a view concern (whether or not to display the form fields to create a new nested object by default).

Specify When Nested Models get Built

If you do have empty nested model form fields displayed by default, you’ll run into the issue where the user submits the form with no values filled in and you have to decide if you want to treat that as somebody trying to create a new nested item with no values, or if that means that no new nested item was submitted. Quite often you just want to ignore the submissions with no nested field values filled out.

Although I would have expected this to be default behavior, you need to manually specify that submissions with empty nested values are ignored using the :reject_if option of accepts_nested_attributes_for:

Note: You now have the option to use :all_blank to ignore the item if all properties are blank. See below for an updated example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person < ActiveRecord::Base

  validates_presence_of :name

  has_many :children, :class_name => 'Person'

  # This will prevent children_attributes with all empty values to be ignored
  accepts_nested_attributes_for :children,
    :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }

  # This does the same thing using :all_blank
  accepts_nested_attributes_for :children, :reject_if => :all_blank

end
1
2
3
@person.children_attributes = [ { :name => '' } ]
@person.save
@person.children.count #=> 0

This is also useful if you have boolean values in your nested model fields (since, as a checkbox, ‘0’ will be submitted if there’s no value):

1
2
3
4
5
6
7
8
9
10
11
12
class Person < ActiveRecord::Base

  validates_presence_of :name, :bad

  has_many :children, :class_name => 'Person'

  # This will prevent empty checkboxes submitted for a child to be
  # construed as a submission
  accepts_nested_attributes_for :children,
    :reject_if => proc { |attrs| attrs['bad'] == '0' && attrs['name'].blank? }

end
1
2
3
@person.children_attributes = [ { :name => '', :bad => '0' } ]
@person.save
@person.children.count #=> 0

Dynamically Adding Nested Form Fields

If you want to allow the addition of a large number of nested models via your HTML forms, one option is to just have several empty nested forms displayed by default. This is a little unappealing, however. The far slicker option is to use javascript to dynamically display the new nested form on the user’s request.

Eloy has a great example application setup on GitHub outlining how this works, and I’ll let you take a peek over there to see how to wire up your dynamic nested form additions.

Eloy’s example app is also a great place to see how the whole thing works, end-to-end. By far the best resource out there.

Conclusion

So there it is, Rails’ most requested new feature in the flesh and blood. I’m not sure if my experiences are indicative of everybody else’s, but this is a godsend for me. Many thanks to all the folks involved with this functionality (if you don’t know who they are, check out the next section which links to a bunch of great resources). A really great effort by the community.

Resources

tags: ruby, rubyonrails

What's New in Edge Rails: HTTP Digest Authentication

Posted by ryan
at 9:03 PM on Thursday, January 29, 2009

This feature is scheduled for: Rails v2.3

Long ago, in your mother’s version of rails, we got a http basic authentication plugin. That functionality has since been rolled into Rails core, but it was always lacking HTTP digest authentication. Until this commit, that is.

For those that may now know the difference, basic authentication only base 64 encodes the authenticating username and password (making it easily decoded) whereas digest authentication sends an MD5 hash of your username and password. To simplify, digest is more secure than basic.

To request digest authentication in Rails, you’ll need to be able to retrieve the cleartext password for a given user (so the framework can hash and compare it using the nonce it created specifically for that request). This commit now allows you to also use a specific hashed format of the password. Here’s how this works if you have access to a cleartext password:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ArticlesController < ApplicationController

  before_filter :digest_authenticate

  def digest_authenticate

    # Given this username, return the cleartext password (or nil if not found)
    authenticate_or_request_with_http_digest("Articles Administration") do |username|
      User.find_by_username(username).try(cleartext_password)
    end
  end

end

Most of us will want to do something with the result of the authentication and can do so with the boolean return value of authenticate_or_request_with_http_digest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ArticlesController < ApplicationController

  before_filter :digest_authenticate

  def digest_authenticate

    success = authenticate_or_request_with_http_digest("Admin") do |username|
      (@user = User.find_by_username(username)).try(cleartext_password)
    end

    # If authentication succeeds, log the user in.  If not, kick back out a failure
    # message as the response body
    if success
      session[:user_id] = @user.id
    else
      request_http_digest_authentication("Admin", "Authentication failed")
    end
  end

end

If you don’t want to store clear text passwords you can return an MD5 hash from the authenticate_or_request_with_http_digest block as long as it’s in the format username:realm:password. You can get a password hash by using Digest::MD5::hexdigest.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User < ActiveRecord::Base

  attr_accessor :password
  validates_presence_of :username, :crypted_password
  before_save :hash_password

  ...

  def hash_password
    if password_changed?
      self.crypted_password =
        Digest::MD5::hexdigest([username, "UserRealm", password].join(":"))
    end
  end
end

and then in your controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ArticlesController < ApplicationController

  before_filter :digest_authenticate

  def digest_authenticate

    # Just return the crypted (hashed) version of the password if it's in the supported
    # format.  Note that the realm here "UserRealm" should match the middle
    # argument of your password hash
    success = authenticate_or_request_with_http_digest("UserRealm") do |username|
      (@user = User.find_by_username(username)).try(crypted_password)
    end

    ...
  end

end

So there you have it, digest authentication in edge Rails.

tags: ruby, rubyonrails

Need Nested Model Support in Rails? Vote for it!

Posted by ryan
at 11:10 AM on Wednesday, January 07, 2009

Nested Model support in Rails is currently scheduled for Rails 2.3. Yay!

The Rails team has recently organized its community around the Rails Activists group and provided several ways for you to speak your mind about the framework – and one of the best ways to impact the maturation of the framework is to vote for features you’d like to see in upcoming releases.

While I don’t usually like to use this site as a bully pulpit, I have to say that the one feature I’d love to see make it’s way into Rails is nested model mass assignment support. The team teased us with the foundation of an initial implementation but later yanked it since it didn’t quite cover all the cases.

I’ve setup a suggestion on uservoice for exactly this feature. If you’d like to see this support in Rails vote for it now! to be heard.

By the way, I totally acknowledge that I am being less than useful here only in complaining and not actually contributing. Guilty as charged – but to be honest, it’s a hairy problem with a lot of edge cases and sometimes I’m just not that smart.

So be heard with your vote (for this feature or any other!)

tags: ruby, rubyonrails

What's New in Edge Rails: Dynamic Scope Methods

Posted by ryan
at 9:24 AM on Monday, December 29, 2008

This feature is scheduled for: Rails v2.3/3.0

For quite some time now you’ve been able to perform simple queries using dynamic find_all_by_xx_and_yy methods:

1
2
Article.find_by_published_and_user_id(true, 1)
  #=> "SELECT * FROM articles WHERE published = 1 AND user_id = 1"

These dynamic finders provide an easy way to quickly encapsulate non-reused query conditions (for commonly used query logic you should consider using named scopes). The downside, however, is that you can’t chain query conditions when using these dynamic finders.

With the recent addition of dynamic scopes, however, you now have a way to both quickly specify query logic and chain further conditions. The naming works in the same manner as dynamic finders and the chaining works in the same fashion as conventional named scopes:

1
2
Article.scoped_by_published_and_user_id(true, 1).find(:all, :limit => 5)
  #=> "SELECT * FROM articles WHERE published = 1 AND user_id = 1 LIMIT 5"

Note how you can hang further chainable query methods off the dynamic scope here? You could also have preceded the dynamic scope with another scope, or even another dynamic scope:

1
2
Article.scoped_by_published(true).scoped_by_user_id(1)
  #=> "SELECT * FROM articles WHERE published = 1 AND user_id = 1"

This is really just another tool to put in your toolbox based on the powerful named_scope functionality of ActiveRecord.

tags: ruby, rubyonrails

What's New in Edge Rails: Merb!

Posted by ryan
at 3:16 PM on Tuesday, December 23, 2008

This feature is scheduled for: Rails v3.0

Wow, from the bombshell department comes the news that Rails and Merb will be merging to form Rails 3. It seems the Rails and Merb teams have acknowledged that there is good in both frameworks and that they can be combined into a singular web framework.

To be honest I don’t love the idea of having less choice in Ruby web-framework world. However, I do think this bodes well for Rails. We can only hope innovation stays alive within the combined team (and that their perspectives only enhance each other and don’t outright conflict).

Read what Yehuda, now a Rails core team member, Ezra, Carl and Matt have to say as well.

Little ‘ole Rails is definitely growing up – lots of big stuff coming in Rails 3…

tags: ruby, rubyonrails