Working with the relationship model

— June 30, 2006 at 14:46 PDT


There's been a lot of buzz this week about DHH's keynote at RailsConf last weekend. I've been meaning to write something but, but my laziness has triumphed and a few others have already written up pretty good summaries of the state of dealing with A World of Resources. In particular, Ryan Daigle has a nice summary that covers all the basics, and Jim Greer's post is a must-read too.

I like the direction of CRUDdying up relationships. I've said for a while that reifying relationships is a win, as any time you can turn a concept into an object it gives you more leverage to work with it. The nice thing about DHH's approach is that is not only turns abstract relationships into concrete objects, but it also uses polymorphism to establish standard CRUD behavior for all those objects.

But embracing CRUD takes a shift in perspective. Actions become nouns that are tossed at other nouns. Others have already observed that this is like the Command pattern. There's a lot of richness to that pattern that can be played with, such as rewinding and replaying commands.

However, the other perspective shift is how you work with the relationship model objects. Here's an example from a conversation on an email list that took place earlier today.

class User < ActiveRecord::Base
  has_many :registrations
  has_many :products, :through => :registrations, :uniq => true
end
class Product < ActiveRecord::Base
  has_many :registrations
  has_many :users, :through => :registrations, :uniq => true
end
class Registration < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
end

Then a view template to show all the registations for a user's products.

<% @user_products.each do |product| %>
<tr>
  <td><%= product.name %></td>
  <td><%= product.description %></td>
  <td width="250">
    <% product.registrations.each do |reg| %>
      <% reg.serial_number %> <br />
    <% end %>
  </td>
</tr>
<% end %>

However, that will show all the registrations for a product, not just those for the user in question. The goal is to show all the registrations for all the products of that type that a user has registered. Here are some ways to get that:

regs = Registration.find(:all, :conditions => ["user_id = ? AND product_id = ?", user, product])
regs = Registration.find_all_by_user_id_and_product_id(user.id, product.id)

regs = product.registrations.find(:all, ["user_id = ?", user])
regs = product.registrations.find_all_by_user_id(user.id)

I like the last of these best, but it's still a bit icky having to pass the id explicitly. We can clean that up and make it more idiomatic by using an extension:

class Product < ActiveRecord::Base
  has_many :registrations do
    def find_all_by_user(user)
      find_all_by_user_id(user.id)
    end
  end
  has_many :users, :through => :registrations, :uniq => true
end

Then we get to fix the view thusly:

<% @user_products.each do |product| %>
<tr>
  <td><%= product.name %></td>
  <td><%= product.description %></td>
  <td width="250">
    <% product.registrations.find_all_by_user(@user).each do |reg| %>
      <% reg.serial_number %> <br />
    <% end %>
  </td>
</tr>
<% end %>

It may seem like a minor point, but it's an important one.

Now I think I need to look into dynamic finders and see if I can make them work with belongs_to associations too.

1 commentassociations, rails, sightings

Comments
  1. Damien Tanner2006-07-01 17:33:56

    Cool stuff. Although I think a more in depth example would help.

    "product.registrations.findallbyuser(@user)" instead of "product.registrations.findallby_userid(user.id)" isn't much of a difference ;)

Sorry, comments for this article are closed.