Thursday, March 27, 2008

params[:fu] #5 ) Update multiple models in update action atomically.

Updating multiple models is hard? It sounds complicated, but with Rails it actually isn't, if you know how to take advantage of it. Knowing what you know about Rails params, let's take a look at today's topic: the update action.

<% form_for @reader do |f| %>
<%= f.text_field :name %
>

<% for @subscription in @subscriptions %>
<p
>
<% fields_for @subscription do |ff| %>
<%= ff.collection_select :magazine_id, Magazine.find(:all), :id, :name, {}, :index =
> @subscription.id %>
<% end %
>
</p>
<% end %>

<%= f.submit 'Save' %>
<% end %>


generates:

<p>
<select id="subscription_4_magazine_id" name="subscription[4][magazine_id]">
<option value="101">PC Magazine</option>
<option value="102">IT Pro<
/option>
<option value="103" selected="selected">WIRED</option>
<
/select>
</p>

<p>
<select id="subscription_5_magazine_id" name="subscription[5][magazine_id]">
<option value="101">PC Magazine<
/option>
<option value="102">IT Pro</option>
<option value="103" selected="selected">WIRED<
/option>
</select>
<
/p>

... (and more)


Processing ReadersController#update (for 127.0.0.1 at 2008-01-14 21:12:56) [PUT]
Parameters: { "commit" => "Update",
"reader" => { "name" => "stephen chu" },
"subscriptions" => { "4" => { "magazine_id" => "101" },
"6" => { "magazine_id" => "102" },
"7" => { "magazine_id" => "103" } },
"authenticity_token" => "238ba79b8282882ba01d840352616c2cc79280f0",
"action" => "create",
"controller" => "readers" }


See the pattern? The POST-ed parameters are of the same structure as in one of our last example, hash of hashes. The sub-hashes are keying off of the subscription id, because this time around we are updating existing subscriptions. So last time we used params[:subscriptions].values, what would it look like this time? Let take a look.

def update
@reader = Reader.find params[:id]
@reader.attributes = params[:reader]
@reader.subscriptions.update params[:subscription].keys, params[:subscription].values

if @reader.save
flash[:notice] = 'Reader was successfully updated.'
redirect_to @reader
else
flash[:notice] = 'Failed.'
end
end


Again, another ActiveRecord model method utilizes the array of hashes pattern! The update method source code on RDoc looks like it is just updating one at a time. But a peek at the source code says otherwise:

def update(id, attributes)
if id.is_a?(Array)
idx = -1
id.collect { |id| idx += 1; update(id, attributes[idx]) }
else
object = find(id)
object.update_attributes(attributes)
object
end
end


Again, the update method recognizes array! So, where to get the arrays that we will use in our controller action? They come from .keys and .values of course:

params[:subscriptions].keys    # => [ "4", "6", "7" ]
params[:subscriptions].values # => [ { "magazine_id" => "101" }, { "magazine_id" => "102" }, ... ]


So in essence, our controller code is free from all those ugly params-munging activities. Remember, controller actions should not shuffle around their params, or otherwise it fails to abide the "Skinny Controller, Fat Model" principle, and they will stink.

Now, if you are thinking about by using update, we run the risk of not atomically saving all of our models should any of our models fail validation, you are correct. This is where rescue_from in controller saves the day. Just transact our update action using AR transaction, and re-render the edit page should it catches ActiveRecord::RecordInvalid error, you should be able to make your update action atomic. Given how lean our controller action looks like, having a transaction block that wraps around our code is not so much a nuisance anymore.

This also wraps up our params[:fu] series. Remember, how you assemble your views form elements have a lot to do with how thin and skinny your controllers look like. Thanks for reading!

Wednesday, March 26, 2008

params[:fu] #4 ) Use the magical <association_name>_ids=([...array of ids]) association proxy method.

In the last blog entry, we looked at an example of having to build a new parent model and build its join models all in two lines of code. But if you are trying to just assign a list of has_many or has_and_belongs_to_many models, you don't have to use the fields_for and index trick. You can just use an association proxy method. Let's check it out:

class Reader < ActiveRecord::Base
has_and_belongs_to_many :blogs
end

<% form_for @reader do |f| %>
<%= f.text_field :name %
>

<%= f.collection_select :blog_ids, Blog.find(:all), :id, :name, {}, :multiple => true %>

<%= f.submit 'Create' %
>
<% end %>


generates:

<select id="reader_blog_ids" multiple="multiple" name="reader[blog_ids][]">
<option value="1">Martin Fowler</option>
<option value="2">Stephen Chu<
/option>
</select>


Processing ReadersController#create (for 127.0.0.1 at 2008-01-14 21:12:56) [POST]
Parameters: { "commit" => "Create",
"reader" => { "name" => "stephen chu"
"blog_ids" => ["1", "2"] },
"authenticity_token" => "238ba79b8282882ba01d840352616c2cc79280f0",
"action" => "create",
"controller" => "readers" }



And now, in my controller action, the creation of the reader only takes one line of code:

def create
@reader = Reader.new(params[:reader])


... thanks to the following has_many (or habtm) association assignment method:

@reader.blog_ids = [...many ids...]


So, stop doing any more params-munging code in your controller action like these:

def create
...
params[:blog_ids].each do |id|
@reader.blogs << Blog.find(id)
end
end


Get on the Rails bandwagon!

Tuesday, March 25, 2008

params[:fu] #3 ) Using fields_for and the index option to create a new parent model with multiple child models.

Alright, the last couple days were easy. Today, let's take a look at a slightly more complicated example, but one that occurs on almost every single Rails project out there: saving multiple models on one POST. Let's say your new form allows you to create a new reader and attach 3 subscriptions to it. Here's the code:

class Reader < ActiveRecord::Base
has_many :subscriptions
has_many :magazines, :through => :subscriptions
validates_associated :subscriptions
end

<% form_for @reader do |f| %>
<%= f.text_field :name %
>

<% (1..3).each do |index| %>
<% fields_for :subscriptions do |ff| %
>
<p><%= ff.collection_select :magazine_id, Magazine.find(:all), :id, :name, {}, :index => index %></p>
<% end %>
<% end %
>

<%= f.submit 'Create' %>
<% end %>


generates:

<select id="subscriptions_1_magazine_id" name="subscriptions[1][magazine_id]">
<option value="101">PC Magazine</option>
<option value="102">IT Pro<
/option>
<option value="103">WIRED</option>
<
/select>

<select id="subscriptions_2_magazine_id" name="subscriptions[2][magazine_id]">
<option value="101">PC Magazine</option>
<option value="102">IT Pro<
/option>
<option value="103">WIRED</option>
<
/select>


... (and more)

Processing ReadersController#create (for 127.0.0.1 at 2008-01-14 21:12:56) [POST]
Parameters: { "commit" => "Create",
"reader" => { "name" => "stephen chu" },
"subscriptions" => { "1" => { "magazine_id" => "101" },
"2" => { "magazine_id" => "102" },
"3" => { "magazine_id" => "103" } },
"authenticity_token" => "238ba79b8282882ba01d840352616c2cc79280f0",
"action" => "create",
"controller" => "readers" }


By using the :index html option in our form builder generated fields, we are essentially inserting a unique index key to the posted value of that field. Why should we care? Well, here is how the controller code would look like:

def create
@reader = Reader.new params[:reader]
@subscriptions = @reader.subscriptions.build params[:subscriptions].values
if @reader.save
flash[:success] = 'Good.'
else
flash[:error] = 'Bad.'
end
end


I just persisted a reader with multiple subscriptions, and the notable differences I added in the create action, was these characters: params[:subscriptions].values. Nothing much changed from the last has_one :computer example besides association related differences. There is no looping, map/collect-ing, gsub-ing, etc., in my action while creating and attaching these multiple subscriptions onto the new reader. The controller action is just doing its same-old routine: receives posted parameters, and shove them into the corresponding models. So how is this done?

Looking at the source of the #build method on the association proxy classes (e.g. HasManyAssociation), you will notice something interesting:

def build(attributes = {})
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr) }
else
build_record(attributes) { |record| set_belongs_to_association_for(record) }
end
end


The method recognizes array! If you pass in an array of hashes, it will process them one by one! So how do we get an array of hashes? In our case the way to get array of hashes is by calling Hash#values, because they are organized in sub-hashes. Calling .values will yield us the following array:

params[:subscriptions].values  # => [ { "magazine_id" => "101" }, { "magazine_id" => "102" }, ... ]


By organizing your params on your view in ways that they can be directly consumed by your models, you end up with a lot less code to write. No more bastardizing your action code!

(back to the TOC of the params[:fu] series)

Monday, March 24, 2008

params[:fu] #2 ) Put attributes into a different params key using fields_for if they belong to different models.

fields_for is very useful for separating out your params:

<% form_for @reader do |f| %>
<%= f.text_field :name %
>

<% fields_for :favourite_computer, @computer do |ff| %>
<%= ff.text_field :name %
>
<% end %>
<%= f.submit 'Create' %
>
<% end %>


Processing ReadersController#create (for 127.0.0.1 at 2008-01-14 21:12:56) [POST]
Parameters: { "commit" => "Create",
"reader" => { "name" => "stephen chu" },
"favourite_computer" => { "name" => "macbook pro" },
"authenticity_token" => "238ba79b8282882ba01d840352616c2cc79280f0",
"action" => "create",
"controller" => "readers" }


def create
@reader = Reader.new params[:reader]
@computer = @reader.build_computer params[:favourite_computer]
if @reader.save
flash[:success] = 'Good.'
else
flash[:error] = 'Bad.'
end
end

class Reader < ActiveRecord::Base
has_one :computer
validates_associated :computer
end


Here, I am on a page that creates the reader and its favorite computer. Two models sounds hard? Not really, if you key your params to a different key for each of the model, your controller is still nice and thin. You do need to add one line to the action to create the computer though, but that maps nicely to the fact that you are operating on two models from the page post. I would not want to add more than one line of code to create one additional model, so forget those params-munging/looping.

Also, you can see that I am using validates_associated here. The point being I do not want to call save or valid? once for each model in my action, making my controller code fat.

Also, fields_for is not only just a helper method, but also a method available on your FormBuilder as well (you know, the f in your form_for is an instance of a FormBuilder. To see an example of how to call it off of a form builder, check out this blog entry.

(back to the TOC of the params[:fu] series)

Sunday, March 23, 2008

params[:fu] #1 ) Wrap all relevant attributes in a single params key.

This is the simplest form of using form_for and params:

<% form_for @reader do |f| %>
<%= f.text_field :name %
>
<%= f.text_field :birth_place %>

<%
= f.submit 'Create' %>
<% end %
>


Processing ReadersController#create (for 127.0.0.1 at 2008-01-14 21:12:56) [POST]
Parameters: { "commit" => "Create",
"reader" => { "name" => "stephen chu", "birth_place" => "hong kong" },
"authenticity_token" => "238ba79b8282882ba01d840352616c2cc79280f0",
"action" => "create",
"controller" => "readers"}


def create
@reader = Reader.create! params[:reader]
end


By doing this, you eliminate the need to explicitly spell out each parameters posted, like the ugly Reader.new :name => params[:name], :birth_place => params[:birth_place].

Notice, Active Record is extremely hash-happy. Let's take a few examples and see how it is so hash-hungry (or rather, hash-happy).

@reader = Reader.new params[:reader]  # => { 'first_name' => 'stephen', 'last_name' => 'chu' }

@readers = Reader.find(:all, :conditions => params[:search]) # => { 'name' => 'chu' }

@reader.attributes = params[:reader] # => { 'first_name' => 'martin', 'last_name' => 'fowler' }

@reader.update_attributes(params[:reader]) # => { 'birth_place' => 'hong kong' }


Notice the find with condition one. No one says I cannot have a hash or form_for that must map to a database table!

All of the above will work in just one line of code, and I am sure you can find more examples as well. Remember, hashes are one of the most underrated Rails toolset to reduce code you need to write. Stay tuned on how we can optimize the use of these AR one-liners.

(back to the TOC of the params[:fu] series)

Boost your Rails Controller params[:fu]

In many ways, in a Rails controller POST action, what gets POST-ed in params are often overlooked. In many straight-forward tutorials, we slap a forms_for @customer in our view, and then the params will come out to be directly consumable by our ActiveRecord model. Unfortunately, many new Rails developers cannot see beyond the usefulness of hashing the posted parameters accordingly. In the next few blog posts, I intend to show you that if you take the time to assemble your params the way Rails likes it, you can dramatically reduce the amount of code you have to write in your controller actions and models, simply because Rails takes care of it for you. The end result is getting it done in less code, fewer tests, less trouble.

Here it goes, the params[:fu] series:



update:

These tips have been chosen as the 8th runner-up in the Railscasts 100th episode contest. And for that, let me add another parmas[:fu] bonus to this list to all readers:



Enjoy!

Wednesday, March 12, 2008

Rails composed_of &block conversion

If you had read Domain Driven Design, you will remember the author, Eric Evans, talks about various types of model objects in your domain model. Two of which are Entity Objects and Value Objects.

Entity objects are those that we care about their identity. For example, on Amazon.com, two users obviously have different identities, despite the possibility that they have the same name. Value objects, on the other hand, are those that we only care about their internal states' equality. Two money objects each has amount 100 and currency 'US' are treated as equal in the entire application. These two types of objects are similar in that they can all be defined as a class, but their usage is quite different.

Usually, a big bulk of your domain objects will be Entity objects. Almost all of your ActiveRecord::Base subclasses belong to that category, since each object has a different 'id' to uniquely identify themselves.

Rails provides composed_of as a way for your Value Objects to be referenced by an ActiveRecord Entity object. Rails 2.0 even added some syntactic sugar to composed_of, allowing you to pass a block for value object conversion. For Rails 1.x users, you can achieve the same with the plugin called composed_of_conversion. Let's see how to beautify your Rails code.


class Account < ActiveRecord::Base
composed_of :balance, :class_name => 'Money', :mapping => [%w(balance amount), %w(currency currency)] do |params|
Money.new params[:balance], params[:currency]
end
end

class Money
attr_reader :amount, :currency

def initialize(amount, currency)
@amount, @currency = amount, currency
end
end

class AccountsController < ApplicationController
def create
@account = Account.new params[:account]

if @account.save
flash[:notice] = 'New account created...'
redirect_to @author
else
render :action => "new"
end
end
end


Notice how my @account creation does not require any additional Controller code for value object creation and assignment, keeping my controller away from snacks and candy (i.e. thin). But since I have redefined Account#balance and Account#balance= through the use of composed_of, what should my form_for fields in my view look like? The answer lies with a use of fields_for.

<% form_for(@account) do |f| %>

<% f.fields_for :balance, @account.balance do |ff| %
>
<p>
Balance: <%= ff.text_field :balance %>
</p>
<p>
Currency: <%
= ff.text_field :currency %>
</p
>
<% end %>

<p
><%= f.submit "Create" %></p>

<% end %>



POST Parameters:
{
"commit" => "Create",
"account" => { "balance" => { "balance"=>"400", "currency"=>"us" } }
}


By calling f.fields_for on the form builder (and not just fields_for), you are creating a new hash scope 'balance' under params[:account]. Since your composed_of is called balance, only the sub-hash 'balance' will be used in your composed_of block conversion. Then, your model will use the sub-hash to convert it into the Money object you want.

At this point, your Money value object need not be confined with a constructor that takes parameters by ordinal position anymore. You can have it take a hash just like any ActiveRecord::Base classes, so that any future addition of parameters to Money does not require changes to your conversion block. Just drop an html element using your fields_for form builder and it will just work.

Why do this? Now, you have a different class to handle money related operations, such as addition, subtraction, validations, money comparison, and even formatting your money amounts everywhere. Should you have another model that needs money, you know where to share code from.

Also, do not get into thinking that a value object must abstract at least 2 columns on a table. It doesn't have to be. A value object can abstract just one column but contains its own validation and formatting logic and just be fine.

Value objects are very important in any application, even though a lot of times they are simple. Make good use of them and your code quality will improve. Now it's time to roll up your sleeves and clean up those value object creation code inside your controllers!

Monday, March 10, 2008

A little #to_s can do wonder in DRY-ing up your views

Do you do this every day?

<h1>Show - Blog <%= @blog.name %></h1>
<dl>
<dt>Author:</dt>
<dd><%
= @blog.author.full_name %></dd>
</dl>

<ul>
<% for comment in @blog.comments do %>
<li>Posted by <%= comment.created_by.full_name %>: <%= comment.description %><
/li>
<% end %>
</ul
>


I am lazy. Having to spell out in my views what field to render for every object when it is that field 90% of the time in my application gives me too much typing. When I render a @comment, you bet I want its description field most of the time; similarly, when I render a @user, you bet I want his/her full_name.

Don't forget all these erb tags at the end of day only render #to_s anyways, so you can just define your default field there to get rid of some typing as well:

class Blog < ActiveRecord::Base
def to_s
name
end
end

class User < ActiveRecord::Base
def to_s
first_name + ' ' + last_name
end
end

class Comment < ActiveRecord::Base
def to_s
description
end
end


Now, let's see how our view looks, 90% of the time:

<h1>Show - Blog <%= @blog %></h1>
<dl>
<dt>Author:</dt>
<dd><%
= @blog.author %></dd>
</dl>

<ul>
<% for comment in @blog.comments do %>
<li>Posted by <%= comment.created_by %>: <%= comment %><
/li>
<% end %>
</ul
>


See, it's a lot DRY-er and less verbose.

You will also notice that by doing this I eliminated a need to use a possible Object#try(:method) case:

<dd><%= @blog.author.try(:full_name) %></dd>


is no longer necessary, because a nil @blog.author will automatically give an empty string on the view. The end result is we all get to be lazier every day :-)