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

Get Your iPhone and Rails Apps Talkin'

Posted by ryan
at 8:06 AM on Thursday, February 05, 2009

The iPhone is cool and sexy. Rails is cool and sexy. Shouldn’t they be a little more compatible?

We think so and have released the first version of ObjectiveResource, our port of Rails’ ActiveResource to Objective-C for the iPhone. We’ve used it extensively on some of our iphone projects and have gotten a good response from others that have stumbled upon it.

Basically, it provides an easy way to consume and integrate with Rails’ standard XML and JSON RESTful web-services (just as ActiveResource does for Ruby).

iPhoneOnRails.com is where we’re going to keep the development progress and planning for the framework as well as the various resources we’ve established for those that have questions and concerns. So have a look, play with the source yourself, join the mailing list and keep track at the iPhoneOnRails blog.

And let us know if you’re using ObjectiveResource and want to be featured on the site, or have a quote about using the framework you want us to put up.

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