tag:blog.hasmanythrough.com,2006-02-27:/tag/associationshas_many :through - associations2012-01-20T11:03:07-08:00tag:blog.hasmanythrough.com,2006-02-27:Article/1402012-01-20T11:03:07-08:002012-01-20T17:33:06-08:00Modularized Association Methods in Rails 3.2Josh Susser<p>Happy Friday! It's Rails 3.2 day! The <a href="http://weblog.rubyonrails.org/2012/1/20/rails-3-2-0-faster-dev-mode-routing-explain-queries-tagged-logger-store">official release announcement</a> mentions a few of the big changes, but I'd like to take a moment to highlight a relatively <a href="https://github.com/rails/rails/pull/3636">small change</a> I was responsible for, one that I hope may make your life a little easier.</p>
<p>From the ActiveRecord <a href="https://github.com/rails/rails/blob/712b0b99a273c49fb4fad48ae61b4ce252ec0562/activerecord/CHANGELOG.md">CHANGELOG</a>:</p>
<pre><code>Generated association methods are created within a separate module to allow overriding and
composition using `super`. For a class named `MyModel`, the module is named
`MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
the `generated_attributes_methods` module defined in ActiveModel, so association methods
override attribute methods of the same name. *Josh Susser*
</code></pre>
<p>The point of this change is to allow more flexibility in working with associations in your model classes. When you define an association, ActiveRecord automagically generates some methods for you to work with the association. For example, a <code>has_many :patches</code> association generates the methods <code>patches</code> and <code>patches=</code> (and a few others).</p>
<p>Previously, those association methods were inserted directly into your model class. This change moves those methods into their own module which is then included in your model class. Your model gets the same methods through inheritance, but also gets to override those methods and still call them using <code>super</code>. Let's take a look at two ways this makes things easier for you.</p>
<p>Sometimes you want to replace the standard generated association methods. That's always been easy to do simply by defining new methods in your model class. The only wrinkle was that you had to make sure you defined your method <em>after</em> you set up the association, or calling <code>has_many</code> would overwrite your method, since last writer wins. That was usually not a problem, but sometimes plugins or other monkey patching extensions could add an association after your model's class was defined, which wouldn't give you a chance to add your method afterwards. With this change, you don't have to worry about those order dependencies anymore. Since those methods are generated in their own module, the order doesn't matter. This is a pretty small issue all told and I doubt it affected many people, but it's still worth mentioning.</p>
<p>The real reason for this change is being able to compose your own methods with the standard generated methods. Before this change, you'd have to use <code>alias_method_chain</code> or some other fancy footwork to layer your own logic on top of the standard association functionality. Either that or you'd have to somehow duplicate the standard behavior in your own method. Ick. Now you can compose methods using inheritance and <code>super</code>, the way Alan Kay intended you to. Here's the example from the docs:</p>
<pre><code>class Car < ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
</code></pre>
<p>If you're familiar with ActiveRecord it's probably fairly obvious what's going on there, but I'll spell it out for the new kids. When you define the <code>belongs_to :owner</code> association, that generates a standard <code>owner=</code> method, and puts it in the module named <code>Car::GeneratedFeatureMethods</code>, which is the closest ancestor of class <code>Car</code>. If you're curious what this looks like, fire up the rails console and type <code>Car.ancestors</code> to see the class's inheritance chain. (Or use your own app and model, since that will be much easier than making up a new app just to see that one thing.)</p>
<p>In this Car class, you can see that changing owners keeps track of the old owner, so the new owner knows who to call when he can't figure out how to open the trunk. The generated <code>owner=</code> method does a fair amount of stuff including managing counter caches, running callbacks, setting inverse associations, etc. Skipping that could break a number of things, so after saving the old owner, you also want to run the generated method. Since it's in a module that Car inherits from, you only have to call <code>super</code> to get that to run. No muss, no fuss!</p>
<p>One more step towards simpler OOP in Rails! Thanks to my fellow Ruby Rogues <a href="http://about.avdi.org/">Avdi Grimm</a> and <a href="http://blog.grayproductions.net/">James Edward Gray II</a> for complaining about the old state of things enough to motivate me to finally go fix this.</p><p>Happy Friday! It's Rails 3.2 day! The <a href="http://weblog.rubyonrails.org/2012/1/20/rails-3-2-0-faster-dev-mode-routing-explain-queries-tagged-logger-store">official release announcement</a> mentions a few of the big changes, but I'd like to take a moment to highlight a relatively <a href="https://github.com/rails/rails/pull/3636">small change</a> I was responsible for, one that I hope may make your life a little easier.</p>
<p>From the ActiveRecord <a href="https://github.com/rails/rails/blob/712b0b99a273c49fb4fad48ae61b4ce252ec0562/activerecord/CHANGELOG.md">CHANGELOG</a>:</p>
<pre><code>Generated association methods are created within a separate module to allow overriding and
composition using `super`. For a class named `MyModel`, the module is named
`MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
the `generated_attributes_methods` module defined in ActiveModel, so association methods
override attribute methods of the same name. *Josh Susser*
</code></pre>
<p>The point of this change is to allow more flexibility in working with associations in your model classes. When you define an association, ActiveRecord automagically generates some methods for you to work with the association. For example, a <code>has_many :patches</code> association generates the methods <code>patches</code> and <code>patches=</code> (and a few others).</p>
<p>Previously, those association methods were inserted directly into your model class. This change moves those methods into their own module which is then included in your model class. Your model gets the same methods through inheritance, but also gets to override those methods and still call them using <code>super</code>. Let's take a look at two ways this makes things easier for you.</p>
<p>Sometimes you want to replace the standard generated association methods. That's always been easy to do simply by defining new methods in your model class. The only wrinkle was that you had to make sure you defined your method <em>after</em> you set up the association, or calling <code>has_many</code> would overwrite your method, since last writer wins. That was usually not a problem, but sometimes plugins or other monkey patching extensions could add an association after your model's class was defined, which wouldn't give you a chance to add your method afterwards. With this change, you don't have to worry about those order dependencies anymore. Since those methods are generated in their own module, the order doesn't matter. This is a pretty small issue all told and I doubt it affected many people, but it's still worth mentioning.</p>
<p>The real reason for this change is being able to compose your own methods with the standard generated methods. Before this change, you'd have to use <code>alias_method_chain</code> or some other fancy footwork to layer your own logic on top of the standard association functionality. Either that or you'd have to somehow duplicate the standard behavior in your own method. Ick. Now you can compose methods using inheritance and <code>super</code>, the way Alan Kay intended you to. Here's the example from the docs:</p>
<pre><code>class Car < ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
</code></pre>
<p>If you're familiar with ActiveRecord it's probably fairly obvious what's going on there, but I'll spell it out for the new kids. When you define the <code>belongs_to :owner</code> association, that generates a standard <code>owner=</code> method, and puts it in the module named <code>Car::GeneratedFeatureMethods</code>, which is the closest ancestor of class <code>Car</code>. If you're curious what this looks like, fire up the rails console and type <code>Car.ancestors</code> to see the class's inheritance chain. (Or use your own app and model, since that will be much easier than making up a new app just to see that one thing.)</p>
<p>In this Car class, you can see that changing owners keeps track of the old owner, so the new owner knows who to call when he can't figure out how to open the trunk. The generated <code>owner=</code> method does a fair amount of stuff including managing counter caches, running callbacks, setting inverse associations, etc. Skipping that could break a number of things, so after saving the old owner, you also want to run the generated method. Since it's in a module that Car inherits from, you only have to call <code>super</code> to get that to run. No muss, no fuss!</p>
<p>One more step towards simpler OOP in Rails! Thanks to my fellow Ruby Rogues <a href="http://about.avdi.org/">Avdi Grimm</a> and <a href="http://blog.grayproductions.net/">James Edward Gray II</a> for complaining about the old state of things enough to motivate me to finally go fix this.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1042008-02-27T14:14:57-08:002008-02-27T14:14:57-08:00count vs length vs sizeJosh Susser<p>In Ruby, #length and #size are synonyms and both do the same thing: they tell you how many elements are in an array or hash. Technically #length is the method and #size is an alias to it.</p>
<p>In ActiveRecord, there are several ways to find out how many records are in an association, and there are some subtle differences in how they work.</p>
<ul>
<li>post.comments.count - Determine the number of elements with an SQL COUNT query. You can also specify conditions to count only a subset of the associated elements (e.g. <code>:conditions => {:author_name => "josh"}</code>). If you set up a counter cache on the association, #count will return that cached value instead of executing a new query.</li>
<li>post.comments.length - This always loads the contents of the association into memory, then returns the number of elements loaded. Note that this won't force an update if the association had been previously loaded and then new comments were created through another way (e.g. <code>Comment.create(...)</code> instead of <code>post.comments.create(...)</code>).</li>
<li>post.comments.size - This works as a combination of the two previous options. If the collection has already been loaded, it will return its length just like calling #length. If it hasn't been loaded yet, it's like calling #count.</li>
</ul>
<p>That's I always have to look up these differences, so now I have them in one place so I don't have to think about it anymore.</p>
<p>By the way, today is my blog's second birthday. I just couldn't let that go by without a post!</p><p>In Ruby, #length and #size are synonyms and both do the same thing: they tell you how many elements are in an array or hash. Technically #length is the method and #size is an alias to it.</p>
<p>In ActiveRecord, there are several ways to find out how many records are in an association, and there are some subtle differences in how they work.</p>
<ul>
<li>post.comments.count - Determine the number of elements with an SQL COUNT query. You can also specify conditions to count only a subset of the associated elements (e.g. <code>:conditions => {:author_name => "josh"}</code>). If you set up a counter cache on the association, #count will return that cached value instead of executing a new query.</li>
<li>post.comments.length - This always loads the contents of the association into memory, then returns the number of elements loaded. Note that this won't force an update if the association had been previously loaded and then new comments were created through another way (e.g. <code>Comment.create(...)</code> instead of <code>post.comments.create(...)</code>).</li>
<li>post.comments.size - This works as a combination of the two previous options. If the collection has already been loaded, it will return its length just like calling #length. If it hasn't been loaded yet, it's like calling #count.</li>
</ul>
<p>That's I always have to look up these differences, so now I have them in one place so I don't have to think about it anymore.</p>
<p>By the way, today is my blog's second birthday. I just couldn't let that go by without a post!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/972007-10-30T18:55:00-07:002008-01-24T00:19:36-08:00Self-referential has_many :through associationsJosh Susser<p>This article updates <a href="http://blog.hasmanythrough.com/2006/4/21/self-referential-through">a previous version</a> for the Rails 2.0 way of things. Since there's not much difference, I decided to fix up the example code to be more understandable. After all, not everyone is a discrete math geek.</p>
<p>This example updates the one from the previous article. The only significant difference is that you don't need to specify the <code>:foreign_key</code> when using the <code>:class_name</code> option in a <code>belongs_to</code> association. In Rails 2.0, the key is inferred from the association name instead of the class name. I also included the <code>:dependent</code> option because I feel it's too often overlooked.</p>
<p>These classes could be used to model a food chain. Spider eats fly, bird eats spider, cat leaves bird on pillow as gift...</p>
<pre><code>create_table :animals do |t|
t.string :species
end
create_table :hunts do |t|
t.integer :predator_id
t.integer :prey_id
t.integer :capture_percent
end
class Animal < ActiveRecord::Base
has_many :pursuits, :foreign_key => 'predator_id',
:class_name => 'Hunt',
:dependent => :destroy
has_many :preys, :through => :pursuits
has_many :escapes, :foreign_key => 'prey_id',
:class_name => 'Hunt',
:dependent => :destroy
has_many :predators, :through => :escapes
end
class Hunt < ActiveRecord::Base
belongs_to :predator, :class_name => "Animal"
belongs_to :prey, :class_name => "Animal"
end
</code></pre>
<p>The Hunt model describes how likely a species of predator is to catch a species of prey. From the predator's perspective the hunt is a pursuit, but the ever-hopeful prey sees it as an escape. Note that you can model both kinds of hunts between the same pairings of animals: Some days you get the bear, some days the bear gets you.</p><p>This article updates <a href="http://blog.hasmanythrough.com/2006/4/21/self-referential-through">a previous version</a> for the Rails 2.0 way of things. Since there's not much difference, I decided to fix up the example code to be more understandable. After all, not everyone is a discrete math geek.</p>
<p>This example updates the one from the previous article. The only significant difference is that you don't need to specify the <code>:foreign_key</code> when using the <code>:class_name</code> option in a <code>belongs_to</code> association. In Rails 2.0, the key is inferred from the association name instead of the class name. I also included the <code>:dependent</code> option because I feel it's too often overlooked.</p>
<p>These classes could be used to model a food chain. Spider eats fly, bird eats spider, cat leaves bird on pillow as gift...</p>
<pre><code>create_table :animals do |t|
t.string :species
end
create_table :hunts do |t|
t.integer :predator_id
t.integer :prey_id
t.integer :capture_percent
end
class Animal < ActiveRecord::Base
has_many :pursuits, :foreign_key => 'predator_id',
:class_name => 'Hunt',
:dependent => :destroy
has_many :preys, :through => :pursuits
has_many :escapes, :foreign_key => 'prey_id',
:class_name => 'Hunt',
:dependent => :destroy
has_many :predators, :through => :escapes
end
class Hunt < ActiveRecord::Base
belongs_to :predator, :class_name => "Animal"
belongs_to :prey, :class_name => "Animal"
end
</code></pre>
<p>The Hunt model describes how likely a species of predator is to catch a species of prey. From the predator's perspective the hunt is a pursuit, but the ever-hopeful prey sees it as an escape. Note that you can model both kinds of hunts between the same pairings of animals: Some days you get the bear, some days the bear gets you.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/892007-07-17T01:58:00-07:002008-01-24T00:19:35-08:00New on edge: inferred foreign key name changeJosh Susser<p>Here's a change that has been a long time coming. (Really. The first time I heard DHH mention he wanted to do this was in March of 2006.) I liked it so much that I've been playing shepherd for it to make sure it happens. So if you're on edge, <a href="http://dev.rubyonrails.org/changeset/7188">now is the time</a>.</p>
<p>In 1.2.x and previous, the name of the foreign key of a belongs_to association is inferred to be the name of the association's class plus "_id". The class is inferred to be the camel-case equivalent of the association name, so in the simple case the foreign key ends up the same as the association name plus that "_id". For example: :line_item >> LineItem >> line_item_id. But if you explicitly set the class to something other than the default, the foreign key got inferred from the class instead of the association name. Example: :manager <> Employee >> employee_id.</p>
<p>Pre 2.0:</p>
<pre><code>class Employee < ActiveRecord::Base
belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id"
has_many :employees, :foreign_key => "manager_id"
end
</code></pre>
<p>As of now (and thus in 2.0), when the class is explicitly set, the foreign key will be inferred from the association name instead of the class. For example: :manager <> Employee >> manager_id.</p>
<p>In edge/2.0:</p>
<pre><code>class Employee < ActiveRecord::Base
belongs_to :manager, :class_name => "Employee"
has_many :employees, :foreign_key => "manager_id"
end
</code></pre>
<p>Why is this an improvement? Oh, it just saves a little typing.</p>
<pre><code>class Article < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
belongs_to :updater, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :creations, :class_name => "Article", :foreign_key => "creator_id"
has_many :updates, :class_name => "Article", :foreign_key => "updater_id"
end
</code></pre>
<p>Notice that you still need to be explicit about the foreign key name in the has_one or has_many associations. It would be a little nicer to be able to say something like <code>:using => :creator</code> in the has_many, but for now that's just a pipe dream.</p><p>Here's a change that has been a long time coming. (Really. The first time I heard DHH mention he wanted to do this was in March of 2006.) I liked it so much that I've been playing shepherd for it to make sure it happens. So if you're on edge, <a href="http://dev.rubyonrails.org/changeset/7188">now is the time</a>.</p>
<p>In 1.2.x and previous, the name of the foreign key of a belongs_to association is inferred to be the name of the association's class plus "_id". The class is inferred to be the camel-case equivalent of the association name, so in the simple case the foreign key ends up the same as the association name plus that "_id". For example: :line_item >> LineItem >> line_item_id. But if you explicitly set the class to something other than the default, the foreign key got inferred from the class instead of the association name. Example: :manager <> Employee >> employee_id.</p>
<p>Pre 2.0:</p>
<pre><code>class Employee < ActiveRecord::Base
belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id"
has_many :employees, :foreign_key => "manager_id"
end
</code></pre>
<p>As of now (and thus in 2.0), when the class is explicitly set, the foreign key will be inferred from the association name instead of the class. For example: :manager <> Employee >> manager_id.</p>
<p>In edge/2.0:</p>
<pre><code>class Employee < ActiveRecord::Base
belongs_to :manager, :class_name => "Employee"
has_many :employees, :foreign_key => "manager_id"
end
</code></pre>
<p>Why is this an improvement? Oh, it just saves a little typing.</p>
<pre><code>class Article < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
belongs_to :updater, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :creations, :class_name => "Article", :foreign_key => "creator_id"
has_many :updates, :class_name => "Article", :foreign_key => "updater_id"
end
</code></pre>
<p>Notice that you still need to be explicit about the foreign key name in the has_one or has_many associations. It would be a little nicer to be able to say something like <code>:using => :creator</code> in the has_many, but for now that's just a pipe dream.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/882007-07-14T17:16:00-07:002008-01-24T00:19:35-08:00Validate your existenceJosh Susser<p>Ever since I started using Rails I've been wanting a way to enforce referential integrity in the database. The Rails philosophy is to keep business logic out of the database and do it in Ruby. All those nifty validation methods are meant to take care of that. That means no foreign key constraints in the database - just use the rails validations.</p>
<p>But which validations to use? The thing that's always frustrated me is that there isn't a validation to enforce that a foreign key references a record that exists. Sure, <code>validates_presence_of</code> will make sure you have a foreign key that isn't nil. And <code>validates_associated</code> will tell you if the record referenced by that key passes its own validations. But that is either too little or too much, and what I want is in the middle ground. So I decided it was time to roll my own.</p>
<p>Enter <code>validates_existence_of</code>.</p><p>Ever since I started using Rails I've been wanting a way to enforce referential integrity in the database. The Rails philosophy is to keep business logic out of the database and do it in Ruby. All those nifty validation methods are meant to take care of that. That means no foreign key constraints in the database - just use the rails validations.</p>
<p>But which validations to use? The thing that's always frustrated me is that there isn't a validation to enforce that a foreign key references a record that exists. Sure, <code>validates_presence_of</code> will make sure you have a foreign key that isn't nil. And <code>validates_associated</code> will tell you if the record referenced by that key passes its own validations. But that is either too little or too much, and what I want is in the middle ground. So I decided it was time to roll my own.</p>
<p>Enter <code>validates_existence_of</code>.</p>
<pre><code>class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, :polymorphic => true
validates_existence_of :tag, :taggable
belongs_to :user
validates_existence_of :user, :allow_nil => true
end
</code></pre>
<p>The above example shows all the interesting bits. You can validate a simple belongs_to association, or one that is polymorphic. You can also use the <code>:allow_nil</code> option to allow for an optional foreign key. If the foreign key is non-nil, then the referenced record must exist.</p>
<p>Of course <code>validates_existence_of</code> only works for a <code>belongs_to</code> association.</p>
<p>Try it out for yourself:</p>
<pre><code>$ script/plugin install http://svn.hasmanythrough.com/public/plugins/validates_existence/
</code></pre>
<p><em>Caveat lector</em>: I've only tried this out on edge, but it should work fine on 1.2.3. Please let me know if you have problems with it.</p>
<p>If there is enough interest I'll submit this as a patch to core.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/732007-01-22T08:02:00-08:002008-01-24T00:19:34-08:00Using faux accessors to initialize valuesJosh Susser<p>One of the little annoyances of ActiveRecord is not being able to override the <code>#initialize</code> method in your class. There's the <code>#after_initialize</code> callback, but there's no way to pass an argument to it when you create the record. So what do you do if you want to create a record and initialize it with some stuff that isn't one of its attributes? The answer is, you cheat.</p>
<blockquote>
<p>The implementor must cheat, but not get caught. -- <a href="http://en.wikipedia.org/wiki/Dan_Ingalls">Dan Ingalls</a></p>
</blockquote><p>One of the little annoyances of ActiveRecord is not being able to override the <code>#initialize</code> method in your class. There's the <code>#after_initialize</code> callback, but there's no way to pass an argument to it when you create the record. So what do you do if you want to create a record and initialize it with some stuff that isn't one of its attributes? The answer is, you cheat.</p>
<blockquote>
<p>The implementor must cheat, but not get caught. -- <a href="http://en.wikipedia.org/wiki/Dan_Ingalls">Dan Ingalls</a></p>
</blockquote>
<p>The usual way to instantiate a new record is to use <code>#new</code> and pass a hash of attributes. The cool thing is that the way ActiveRecord assigns those attributes to the new record is easily hackable. For example, consider this:</p>
<pre><code>post = Post.new(:title => "Man bites dog!")
</code></pre>
<p>ActiveRecord will set the :title attribute of the new post by using the #title= accessor. Very simple. But that opens a world of possibilities...</p>
<pre><code>class User < ActiveRecord::Base
def full_name=(full_name)
self.first = full_name.split(" ").first
self.last = full_name.split(" ").last
end
end
user = User.new(:full_name => "Monty Python")
</code></pre>
<p>That's a pretty simple example. Here's a more standard alternate approach:</p>
<pre><code>class User < ActiveRecord::Base
attr :full_name
def after_initialize
self.first_name = @full_name.split(" ").first
self.last_name = @full_name.split(" ").last
end
end
</code></pre>
<p>That's not too bad, but I still think the first example is better. Why muck around with a callback when you don't have to?</p>
<p>Now, check this out.</p>
<pre><code>class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
def init_category=(category)
self.categories << category
end
end
post = Post.new(:init_category => Category.find_by_name("Main"))
</code></pre>
<p>The above example shows how to create a new post with a default category set. Since we're using <code>has_and_belongs_to_many</code> here, we don't have to worry about managing whether the object is a new record since habtm does that for us. But it's a little trickier if we want to use a join model...</p>
<pre><code>class Post < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
def tag_list=(tag_string)
if self.new_record?
@tag_string = tag_string
else
self.create_taggings(tag_string) # parse tags, create tags & taggings
end
end
def after_create
self.tag_list = @tag_string if @tag_string
end
end
post = Post.new(:tag_list => "chocolate, dessert, sinful")
</code></pre>
<p><code>has_many :through</code> won't work with new records, as it needs saved records with actual ids to use in the foreign keys in the join model. This example defers processing the tag_list until a new record is saved, at which point it parses out the tags and creates the taggings to join them to the post.</p>
<p>Tricks like that are why I just love ruby!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/722007-01-15T16:35:00-08:002008-01-24T00:19:34-08:00Basic Rails association cardinalityJosh Susser<p>Over the weekend I noticed that the Rails API docs didn't have any basic information on relation cardinality and how that maps to associations. It wasn't difficult to come up with some overview documentation, and a <a href="http://dev.rubyonrails.org/ticket/7022">patch</a> later, it's now part of the API docs. I'm reproducing it here because most people won't notice its appearance in the docs, and it's good to call out basic stuff like this.</p><p>Over the weekend I noticed that the Rails API docs didn't have any basic information on relation cardinality and how that maps to associations. It wasn't difficult to come up with some overview documentation, and a <a href="http://dev.rubyonrails.org/ticket/7022">patch</a> later, it's now part of the API docs. I'm reproducing it here because most people won't notice its appearance in the docs, and it's good to call out basic stuff like this.</p>
<h2>Cardinality and associations</h2>
<p>ActiveRecord associations can be used to describe relations with one-to-one, one-to-many and many-to-many cardinality. Each model uses an association to describe its role in the relation. In each case, the <code>belongs_to</code> association is used in the model that has the foreign key.</p>
<h3>One-to-one</h3>
<p>Use <code>has_one</code> in the base, and <code>belongs_to</code> in the associated model.</p>
<pre><code>class Employee < ActiveRecord::Base
has_one :office
end
class Office < ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
</code></pre>
<h3>One-to-many</h3>
<p>Use <code>has_many</code> in the base, and <code>belongs_to</code> in the associated model.</p>
<pre><code>class Manager < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :manager # foreign key - manager_id
end
</code></pre>
<h3>Many-to-many</h3>
<p>There are two ways to build a many-to-many relationship.</p>
<p>The first way uses a has_many association with the :through option and a join model, so
there are two stages of associations.</p>
<pre><code>class Assignment < ActiveRecord::Base
belongs_to :programmer # foreign key - programmer_id
belongs_to :project # foreign key - project_id
end
class Programmer < ActiveRecord::Base
has_many :assignments
has_many :projects, :through => :assignments
end
class Project < ActiveRecord::Base
has_many :assignments
has_many :programmers, :through => :assignments
end
</code></pre>
<p>For the second way, use <code>has_and_belongs_to_many</code> in both models. This requires a join table that has no corresponding model or primary key.</p>
<pre><code>class Programmer < ActiveRecord::Base
has_and_belongs_to_many :projects # foreign keys in the join table
end
class Project < ActiveRecord::Base
has_and_belongs_to_many :programmers # foreign keys in the join table
end
</code></pre>
<p>It is not always a simple decision which way of building a many-to-many relationship is best. But if you need to work with the relationship model as its own entity, then you'll need to use <code>has_many :through</code>. Use <code>has_and_belongs_to_many</code> when working with legacy schemas or when you never work directly with the relationship itself.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/632006-09-09T15:21:44-07:002008-01-24T00:19:33-08:00Finding unassociated objectsJosh Susser<p>Say you have two models with a one-to-many relationship (using belongs_to and has_many). For example, a manager has many employees.</p>
<pre><code># Manager
has_many :employees
# Employee
belongs_to :manager
</code></pre>
<p>Rails associations make it easy to find all the employees belonging to a manager (<code>manager.employees</code>). And a rather simple find with conditions gets you the list of employees who belong to any manager, or even the orphaned employees with no manager.</p>
<pre><code># Employee
def self.find_managed
find(:all, :conditions => "manager_id IS NOT NULL")
end
def self.find_orphans
find(:all, :conditions => "manager_id IS NULL")
end
</code></pre><p>Say you have two models with a one-to-many relationship (using belongs_to and has_many). For example, a manager has many employees.</p>
<pre><code># Manager
has_many :employees
# Employee
belongs_to :manager
</code></pre>
<p>Rails associations make it easy to find all the employees belonging to a manager (<code>manager.employees</code>). And a rather simple find with conditions gets you the list of employees who belong to any manager, or even the orphaned employees with no manager.</p>
<pre><code># Employee
def self.find_managed
find(:all, :conditions => "manager_id IS NOT NULL")
end
def self.find_orphans
find(:all, :conditions => "manager_id IS NULL")
end
</code></pre>
<p>What if we need to find all the employees that don't work for a particular manager?</p>
<pre><code># Manager
has_many :employees do
def not
find(:conditions => ["employees.id != ?", proxy_owner.id])
end
end
</code></pre>
<p>That association extension lets us use <code>manager.employees.not</code> to find the list of employees that don't work for that manager.</p>
<p>It's a bit harder to find manager with no employees at all. Still, we can do it without too much ugly SQL in our pretty Rails code. We want to find all managers for which there are no employees that belong to them.</p>
<pre><code># Manager
def self.find_without_employees
find(:all, :readonly => false,
:select => "managers.*",
:joins => "LEFT OUTER JOIN employees e ON managers.id = e.manager_id",
:conditions => "e.id IS NULL")
end
</code></pre>
<p>Then <code>Manager.find_without_employees</code> returns all the managers without any employees. Time for a re-org!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/612006-08-19T14:17:00-07:002008-01-24T00:19:33-08:00New on edge: Magic join model creationJosh Susser<p>Mark the date: August 18, 2006 is the day that has_many :through finally beat has_and_belongs_to_many into submission. Yesterday, Rails <a href="http://rubyonrails.org/core">core</a> member <a href="http://bitsweat.net/">Jeremy Kemper</a> checked in a <a href="http://dev.rubyonrails.org/changeset/4786">change</a> to Rails trunk ActiveRecord that allows using <code><<</code> and friends on <code>has_many :through</code> associations. If you don't know why this is cool (or needed!), take a moment and read <a href="http://blog.hasmanythrough.com/articles/2006/04/17/join-models-not-proxy-collections">this</a> for background.</p>
<p>With this change, ActiveRecord will automagically create the join model record needed to link the associated object. What does that mean? Check this out...</p>
<pre><code>post.tags << Tag.find_or_create_by_name("magic")
</code></pre>
<p>In the old world, that would have been</p>
<pre><code>post.taggings.create!(:tag => Tag.find_or_create_by_name("magic"))
</code></pre>
<p>And then you'd <em>still</em> need to do a <code>post.tags.reset</code> or <code>post.tags(true)</code> to reload the tags collection so you could see the new tags in the association.</p>
<p>Kudos to Jeremy for getting this to work. I'd been working on my own hack to achieve this, but couldn't come up with an approach that was clean enough to satisfy me. Jeremy did the expedient thing and cut corners, defining the semantics on failure to throw an exception instead of returning false. That differs from how <code><<</code> works on <code>has_many</code>, which returns false on failure (and how does that make sense?). I'm lobbying to fix <code>has_many <<</code> to do the same thing now.</p>
<p>Obligatory word of caution: As with any new edge feature, this may be unstable for a short time. I've been helping Jeremy stomp some bugs and fill in holes, but it's looking pretty solid now. If you're curious, give it a shot.</p>
<p>Now, the big bonus. This new functionality lets you easily set extra attributes when creating join model records, just like the deprecated <code>push_with_attributes</code> did for <code>has_and_belongs_to_many</code>. And of course I've got a trick for customizing default values based on associations.</p>
<p>Read on for more details...</p>
<p><em>(By the way, I'm entering this post in Peter Cooper's Ruby/Rails blogging <a href="http://www.rubyinside.com/win-100-by-blogging-about-ruby-or-rails-this-week-184.html">contest</a>. There are some good entries already, and he's always got good links on his site, so check it out.)</em></p><p>Mark the date: August 18, 2006 is the day that has_many :through finally beat has_and_belongs_to_many into submission. Yesterday, Rails <a href="http://rubyonrails.org/core">core</a> member <a href="http://bitsweat.net/">Jeremy Kemper</a> checked in a <a href="http://dev.rubyonrails.org/changeset/4786">change</a> to Rails trunk ActiveRecord that allows using <code><<</code> and friends on <code>has_many :through</code> associations. If you don't know why this is cool (or needed!), take a moment and read <a href="http://blog.hasmanythrough.com/articles/2006/04/17/join-models-not-proxy-collections">this</a> for background.</p>
<p>With this change, ActiveRecord will automagically create the join model record needed to link the associated object. What does that mean? Check this out...</p>
<pre><code>post.tags << Tag.find_or_create_by_name("magic")
</code></pre>
<p>In the old world, that would have been</p>
<pre><code>post.taggings.create!(:tag => Tag.find_or_create_by_name("magic"))
</code></pre>
<p>And then you'd <em>still</em> need to do a <code>post.tags.reset</code> or <code>post.tags(true)</code> to reload the tags collection so you could see the new tags in the association.</p>
<p>Kudos to Jeremy for getting this to work. I'd been working on my own hack to achieve this, but couldn't come up with an approach that was clean enough to satisfy me. Jeremy did the expedient thing and cut corners, defining the semantics on failure to throw an exception instead of returning false. That differs from how <code><<</code> works on <code>has_many</code>, which returns false on failure (and how does that make sense?). I'm lobbying to fix <code>has_many <<</code> to do the same thing now.</p>
<p>Obligatory word of caution: As with any new edge feature, this may be unstable for a short time. I've been helping Jeremy stomp some bugs and fill in holes, but it's looking pretty solid now. If you're curious, give it a shot.</p>
<p>Now, the big bonus. This new functionality lets you easily set extra attributes when creating join model records, just like the deprecated <code>push_with_attributes</code> did for <code>has_and_belongs_to_many</code>. And of course I've got a trick for customizing default values based on associations.</p>
<p>Read on for more details...</p>
<p><em>(By the way, I'm entering this post in Peter Cooper's Ruby/Rails blogging <a href="http://www.rubyinside.com/win-100-by-blogging-about-ruby-or-rails-this-week-184.html">contest</a>. There are some good entries already, and he's always got good links on his site, so check it out.)</em></p>
<p>First, the basics. Here's what's new:</p>
<ul>
<li>Add records to <code>has_many :through</code> associations using <code><<</code>, <code>push</code>, and <code>concat</code> by creating the join record. Raises an error if base or associate are new records since both ids are required to create the join record.</li>
<li><code>#build</code> raises and error since you can't associate an unsaved record (one without an id).</li>
<li><code>#create!</code> takes an attributes hash and creates both the associated record and the join record in a transaction.</li>
</ul>
<p>And of course, I have some tricks for you as well. Here are some models you may have seen <a href="http://blog.hasmanythrough.com/articles/2006/05/06/through_gets_uniq">before</a>.</p>
<pre><code>create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "role", :string
end
class Contribution < ActiveRecord::Base
belongs_to :book
belongs_to :contributor
end
class Book < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :contributors, :through => :contributions
end
class Contributor < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :books, :through => :contributions
end
</code></pre>
<p>Let me just spruce them up to take advantage of the new features.</p>
<pre><code>class Contributor < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :books, :through => :contributions do
def push_with_attributes(book, join_attrs)
Contribution.with_scope(:create => join_attrs) { self << book }
end
end
end
</code></pre>
<p>Here I've created an association extension with the method <code>push_with_attributes</code>. That lets me do this:</p>
<pre><code>dave = Contributor.create(:name => "Dave")
awdr = Book.create(:title => "Agile Web Development with Rails")
dave.books.push_with_attributes(awdr, :role => "author")
</code></pre>
<p>That automagically creates the join record with the role attribute set to "author". Pretty nifty, eh? But we can do better.</p>
<pre><code>class Contribution < ActiveRecord::Base
belongs_to :book
belongs_to :contributor
belongs_to :author, :class_name => "Contributor"
belongs_to :editor, :class_name => "Contributor"
end
class Book < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :contributors, :through => :contributions, :uniq => true
has_many :authors, :through => :contributions, :source => :author,
:conditions => "contributions.role = 'author'" do
def <<(author)
Contribution.with_scope(:create => {:role => "author"}) { self.concat author }
end
end
has_many :editors, :through => :contributions, :source => :editor,
:conditions => "contributions.role = 'editor'" do
def <<(editor)
Contribution.with_scope(:create => {:role => "editor"}) { self.concat editor }
end
end
end
</code></pre>
<p>Then give this a shot...</p>
<pre><code>dave = Contributor.create(:name => "Dave")
chad = Contributor.create(:name => "Chad")
awdr = Book.create(:title => "Agile Web Development with Rails")
awdr.authors << dave
awdr.editors << chad
</code></pre>
<p>The above code creates a join record with the role attribute set to "author", and another with the role set to "editor". The trick is using the association extension to define a new <code><<</code> method. Since that method is defined in the context of a specialized association, it can assume correctly that it knows what the role should be. This example code isn't very DRY, but it shouldn't be much work to come up with a way to generate those extensions dryly.</p>
<p>I think I'm happy now.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/512006-06-30T14:46:00-07:002008-01-24T00:19:32-08:00Working with the relationship modelJosh Susser<p>There's been a lot of buzz this week about <a href="http://www.loudthinking.com/arc/000593.html">DHH's keynote at RailsConf</a> 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 href="http://www.loudthinking.com/lt-files/worldofresources.pdf">A World of Resources</a>. In particular, Ryan Daigle has a nice <a href="http://www.ryandaigle.com/articles/2006/06/30/whats-new-in-edge-rails-activeresource-is-here">summary</a> that covers all the basics, and Jim Greer's <a href="http://jimonwebgames.com/articles/2006/06/26/dont-say-crud-say-fucd">post</a> is a must-read too.</p>
<p>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.</p>
<p>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.</p><p>There's been a lot of buzz this week about <a href="http://www.loudthinking.com/arc/000593.html">DHH's keynote at RailsConf</a> 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 href="http://www.loudthinking.com/lt-files/worldofresources.pdf">A World of Resources</a>. In particular, Ryan Daigle has a nice <a href="http://www.ryandaigle.com/articles/2006/06/30/whats-new-in-edge-rails-activeresource-is-here">summary</a> that covers all the basics, and Jim Greer's <a href="http://jimonwebgames.com/articles/2006/06/26/dont-say-crud-say-fucd">post</a> is a must-read too.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<pre><code>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
</code></pre>
<p>Then a view template to show all the registations for a user's products.</p>
<pre><code><% @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 %>
</code></pre>
<p>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:</p>
<pre><code>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)
</code></pre>
<p>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:</p>
<pre><code>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
</code></pre>
<p>Then we get to fix the view thusly:</p>
<pre><code><% @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 %>
</code></pre>
<p>It may seem like a minor point, but it's an important one.</p>
<p>Now I think I need to look into dynamic finders and see if I can make them work with belongs_to associations too.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/442006-06-12T15:40:00-07:002008-01-24T00:19:31-08:00When associations aren't enough, part 2Josh Susser<p>After I posted <a href="http://blog.hasmanythrough.com/articles/2006/06/12/when-associations-arent-enough">part 1</a> this morning, the inimitable <a href="http://www.rubyinside.com/">Peter Cooper</a> left a comment of a very different way to get the same results. I'd been thinking about using my approach to do tagging, and wasn't too happy with it. Doing two or three joins is one thing, but doing a dozen or so is another matter and likely to make a database cranky. And with a spiffy tagging UI, you can expect users to be filtering by a bunch of tags. Looks like my many-joins approach won't scale well.</p><p>After I posted <a href="http://blog.hasmanythrough.com/articles/2006/06/12/when-associations-arent-enough">part 1</a> this morning, the inimitable <a href="http://www.rubyinside.com/">Peter Cooper</a> left a comment of a very different way to get the same results. I'd been thinking about using my approach to do tagging, and wasn't too happy with it. Doing two or three joins is one thing, but doing a dozen or so is another matter and likely to make a database cranky. And with a spiffy tagging UI, you can expect users to be filtering by a bunch of tags. Looks like my many-joins approach won't scale well.</p>
<p>Peter's approach uses grouping and then counting the number of matches. Pretty slick trick. Here's how the (ANSIfied) SQL for his approach looks:</p>
<pre><code>SELECT * FROM movies
INNER JOIN appearances a ON movies.id = a.movie_id
WHERE (a.actor_id IN (17, 23, 39, 42))
GROUP BY movies.id HAVING COUNT(movies.id) = 4
</code></pre>
<p>The join finds all the appearances for any actor in a movie. The WHERE filters that down to just the actors we care about, so our result set is now all the movies any of the Marx brothers appeared in. Then we group all the movies by their id, and count how many of our specified actors appeared in each movie. Only the movies that have the right number of actors are selected.</p>
<p>Here's the method for generating that query:</p>
<pre><code>def self.find_with_all_actors(*actors)
return [] if actors.empty?
actors = actors.flatten
find(:all, :readonly => false,
:joins => "INNER JOIN appearances a ON movies.id = a.movie_id",
:conditions => "a.actor_id IN (#{actors.map(&:id).join(', ')})",
:group => "movies.id HAVING COUNT(movies.id) = #{actors.size}")
end
</code></pre>
<p>Note that if you're running edge, you can use this for the conditions: </p>
<pre><code> :conditions => ["a.actor_id IN (?)", actors],
</code></pre>
<p>I haven't had time to do any sort of performance comparison against the two approaches, but I'm pretty sure that for more than a few actors, Peter's approach will usually win. For a small number of actors, it may depend on a lot of factors.</p>
<p>The other nice thing Peter points out is that his approach lets you match <em>most</em> of the actors. If you change the HAVING to <code>COUNT(movies.id) > 1</code>, you'll find all the movies where at least two of the brothers appear together, not not necessarily all four. Or you can order them by number of actors appearing, etc.</p>
<p>Very nice. I'll probably use this technique for a tagging system I'm about to implement.</p>
<p>By the way, <a href="http://ruby.meetup.com/6/events/4929994/">SF Ruby Meetup</a> tonight. And just 10 days to RailsConf!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/432006-06-12T09:55:00-07:002008-01-24T00:19:31-08:00When associations aren't enoughJosh Susser<p>ActiveRecord associations are the neatest thing since sliced bread. They are great for simplifying a common set of relational database access patterns, something even sliced bread can't do. ActiveRecord associations provide a lot of flexibility, but there comes a point there they run out of steam and you have to roll up your sleeves and write some custom SQL.</p>
<p>Last week I read a <a href="http://www.ruby-forum.com/topic/68432">question</a> on the rails email list that had, I think, a rather interesting solution. I think it's a good example of how to use custom SQL without throwing all of ActiveRecord out the window and resorting to <code>find_by_sql</code>. I'm going to answer that question again here because it deserves more attention than I was able to give it in just a quick email. I also wanted to test my answer and make sure everything works correctly (which it does now).</p><p>ActiveRecord associations are the neatest thing since sliced bread. They are great for simplifying a common set of relational database access patterns, something even sliced bread can't do. ActiveRecord associations provide a lot of flexibility, but there comes a point there they run out of steam and you have to roll up your sleeves and write some custom SQL.</p>
<p>Last week I read a <a href="http://www.ruby-forum.com/topic/68432">question</a> on the rails email list that had, I think, a rather interesting solution. I think it's a good example of how to use custom SQL without throwing all of ActiveRecord out the window and resorting to <code>find_by_sql</code>. I'm going to answer that question again here because it deserves more attention than I was able to give it in just a quick email. I also wanted to test my answer and make sure everything works correctly (which it does now).</p>
<p><a href="http://www.imdb.com/">IMDb</a> is one of my best friends on the internet. One of its nifty features is the ability to find all the movies in which several actors appear together. How would we go about doing something like that using ActiveRecord associations? Actors appearing in movies is quite similar to the example of dancers in movies that I used in ye olde <a href="http://blog.hasmanythrough.com/articles/2006/04/20/many-to-many-dance-off">Many-to-many Dance-off</a>, so we can start with pretty much the same tables I used there, just turning dancers into actors (like that would be the first time that ever happened).</p>
<p>With either of <code>has_and_belongs_to_many</code> or <code>has_many :through</code> you can find all the movies for a given actor simply by using the magic accessor <code>actor.movies</code>, and all the actors for a movie with <code>movie.actors</code>. But there's no simple way to use the standard association machinery to find the set of movies in which several actors appear. The naive approach would be to use a condition that compares the <code>actor_id</code> against the ids of several actors, but that just won't work.</p>
<pre><code>movies = Movie.find(:all, :conditions => "actor_id = 17 AND actor_id = 23")
</code></pre>
<p>First off, there is no <code>actor_id</code> in the Movie model - it's in the join table! (Or in the join model's table if using <code>has_many :through</code>.) So if we mess around and get the join table into the SQL, we'll just be looking for rows in the join table where the <code>actor_id</code> is both 17 and 23 at the same time, and that only happens in Bizarro world. What we need to do is to join with the join table <em>multiple times</em>. If you know SQL well then this won't be at all impressive. But if you're a SQL novice it might seem like some kind of black magic. Don't worry, it's not that difficult.</p>
<p>Say we want to find all the Marx Brothers' movies.</p>
<pre><code>movies = Movie.find_with_all_actors([groucho, harpo, chico, zeppo])
</code></pre>
<p>Time to write some custom SQL. We can build a query using SQL's ability to join to the same table more than once in a single query. You have to alias the table to a different name for each join, but other than that the rest is straightforward.</p>
<pre><code>SELECT * FROM movies
INNER JOIN appearances ap0 ON movies.id = ap0.movie_id
INNER JOIN appearances ap1 ON movies.id = ap1.movie_id
INNER JOIN appearances ap2 ON movies.id = ap2.movie_id
INNER JOIN appearances ap3 ON movies.id = ap3.movie_id
WHERE (ap0.actor_id = 17 AND ap1.actor_id = 23 AND
ap2.actor_id = 39 AND ap3.actor_id = 42)
</code></pre>
<p>I'm using the join model table <code>appearances</code> in that SQL, but for a habtm join table you could just change that to <code>actors_movies</code> and it would work the same. No need to join to the actors table because we aren't searching on any of those attributes, just the <code>actor_id</code> in the join model. If we run that query we'll get back all the movies that all four of the Marx Brothers appeared in. Duck soup!</p>
<p>Let's write the method to generate that SQL. But wait, we're going to take a slight detour and before we write the method and write some simple tests for it. We'd like the method to work correctly whether we give it no arguments, a single actor, an array of actors, or just a bunch of actors not in an array.</p>
<pre><code>assert_equal [], Movie.find_with_all_actors()
assert_equal 12, Movie.find_with_all_actors(groucho).length
assert_equal 7, Movie.find_with_all_actors([groucho, harpo, chico, zeppo]).length
assert_equal 7, Movie.find_with_all_actors(groucho, harpo, chico, zeppo).length
</code></pre>
<p>Alright, <em>now</em> let's write the method to generate that SQL. Since we're looking for movies, we'll create a class method in the Movie model:</p>
<pre><code>def self.find_with_all_actors(*actors)
return [] if actors.empty?
join_frags, condition_frags = [], []
actors.flatten.each_with_index do |actor,i|
join_frags << "INNER JOIN appearances ap#{i} ON movies.id = ap#{i}.movie_id"
condition_frags << "ap#{i}.actor_id = #{actor.id}"
end
find(:all, :readonly => false,
:joins => join_frags.join(" "),
:conditions => condition_frags.join(" AND "))
end
</code></pre>
<p>That method isn't all that complex, but I'll break it down and walk through it so it's clear. Note that if <code>actors</code> is empty we just return an empty Array. After that we create some arrays to collect the SQL fragments in as they are generated.</p>
<pre><code> return [] if actors.empty?
join_frags, condition_frags = [], []
</code></pre>
<p>Then comes the meat of the method. We enumerate the actors and create both the JOIN clause and WHERE condition we'll need for that actor. Each join aliases the <code>appearances</code> table to a unique name that is used only for that join to the table.</p>
<pre><code> actors.flatten.each_with_index do |actor,i|
join_frags << "INNER JOIN appearances ap#{i} ON movies.id = ap#{i}.movie_id"
condition_frags << "ap#{i}.actor_id = #{actor.id}"
end
</code></pre>
<p>Then we put the fragments together into the JOIN and WHERE clauses and do the find.</p>
<pre><code> find(:all, :readonly => false,
:joins => join_frags.join(" "),
:conditions => condition_frags.join(" AND "))
</code></pre>
<p>We set <code>:readonly</code> to false to override the default behavior where ActiveRecord assumes that joined results can't be written back to a single table. Since we're selecting only the columns from movies, our records are writable.</p>
<p>As I said, we're not joining with the <code>actors</code> table. In this contrived example there aren't a lot of reasons to do that. If you want to find all the movies starring Kevin Bacon and any one of the Baldwins, you might be tempted to write a join against the actors table to search on the last name "Baldwin". However it's simpler to use one query to fetch all the Baldwins then use the ids of those records to create a query that uses OR and AND to find your movies. It takes two queries instead of just one, but odds are that's not going to be a significant performance hit. If it is, then you can sweat out the nasty join.</p>
<p><strong>UPDATE:</strong> The story continues in <a href="http://blog.hasmanythrough.com/articles/2006/06/12/when-associations-arent-enough-part-2">part 2</a>.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/402006-05-28T21:44:00-07:002008-01-24T00:19:31-08:00Come on, Eileen!Josh Susser<p>Unlike Dexy's Midnight Runners, I'm no longer a one-hit-wonder. Contributor, that is. Today I got two more patches accepted to Rails (thanks Rick!). One was just to lay groundwork for my experimental ThroughExtender module that will enable some of the association collection methods (<code><<</code>, <code>build</code>, <code>create</code>) for some subset of <code>has_many :through</code> associations - more on this in another post, and fairly soon I hope.</p>
<p>The more timely change was a fix to habtm <code>create</code> that corrects a problem where the join table wasn't being populated for newly created associated objects (<a href="http://dev.rubyonrails.org/ticket/3692">#3692</a>). As so often seems to be the case, the fix was fairly easy (almost a one-liner), but getting the test cases to prove things were working right was the harder part. And just to be a good citizen, I created a plugin that will make the exact same fix for habtm <code>create</code> available to those who don't run off of trunk, to tide you over until the next release version. You can get it from my <a href="http://svn.hasmanythrough.com/public/plugins/habtm_create/">public svn repository</a>.</p><p>Unlike Dexy's Midnight Runners, I'm no longer a one-hit-wonder. Contributor, that is. Today I got two more patches accepted to Rails (thanks Rick!). One was just to lay groundwork for my experimental ThroughExtender module that will enable some of the association collection methods (<code><<</code>, <code>build</code>, <code>create</code>) for some subset of <code>has_many :through</code> associations - more on this in another post, and fairly soon I hope.</p>
<p>The more timely change was a fix to habtm <code>create</code> that corrects a problem where the join table wasn't being populated for newly created associated objects (<a href="http://dev.rubyonrails.org/ticket/3692">#3692</a>). As so often seems to be the case, the fix was fairly easy (almost a one-liner), but getting the test cases to prove things were working right was the harder part. And just to be a good citizen, I created a plugin that will make the exact same fix for habtm <code>create</code> available to those who don't run off of trunk, to tide you over until the next release version. You can get it from my <a href="http://svn.hasmanythrough.com/public/plugins/habtm_create/">public svn repository</a>.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/352006-05-06T18:40:00-07:002008-01-24T00:19:31-08:00has_many :through gets :uniqJosh Susser<p>If you don't follow the Rails Trac <a href="http://dev.rubyonrails.org/timeline">commit log</a>, you may find this interesting. Jeremy Kemper just checked in a <a href="http://dev.rubyonrails.org/changeset/4325">change</a> to enable the <code>:uniq</code> option for <code>has_many :through</code> associations.</p><p>If you don't follow the Rails Trac <a href="http://dev.rubyonrails.org/timeline">commit log</a>, you may find this interesting. Jeremy Kemper just checked in a <a href="http://dev.rubyonrails.org/changeset/4325">change</a> to enable the <code>:uniq</code> option for <code>has_many :through</code> associations.</p>
<p>So what does the <code>:uniq</code> option do? The docs for the <code>has_many</code> options say:</p>
<blockquote>
<p><code>:uniq</code> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.</p>
</blockquote>
<p>This is very handy when there are multiple connections between two model objects via a join model but you only care about whether there are any connections at all. Let's go back to my favorite example, where contributors contribute to books in various roles, such as author, editor, illustrator, etc. I'll omit the migrations for Book and Contributor as they aren't interesting for what we're doing here.</p>
<pre><code>create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "role", :string
end
class Contribution < ActiveRecord::Base
belongs_to :book
belongs_to :contributor
end
class Book < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :contributors, :through => :contributions
end
class Contributor < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :books, :through => :contributions
end
</code></pre>
<p>That's all well and good, but the associations in the Book and Contributor models are a bit weak. If Sam contributed to a book as both an author and an illustrator, then <code>book.contributors</code> will include Sam twice. Since the contributors collection doesn't include any information about the roles, Sam showing up twice is mere redundancy. Let's try to improve the model so things are more useful.</p>
<pre><code>class Book < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :contributors, :through => :contributions, :uniq => true
end
</code></pre>
<p>Adding the <code>:uniq => true</code> option tells the association to eliminate redundant results. Now it doesn't matter how many different ways Sam contributes to a book; he is listed as a contributor only once.</p>
<p>That's nice, but we can do better.</p>
<pre><code>class Contribution < ActiveRecord::Base
belongs_to :book
belongs_to :contributor
belongs_to :author, :class_name => "Contributor"
belongs_to :editor, :class_name => "Contributor"
belongs_to :illustrator, :class_name => "Contributor"
belongs_to :proofreader, :class_name => "Contributor"
end
class Book < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :contributors, :through => :contributions, :uniq => true
has_many :authors, :through => :contributions, :source => :author, :conditions => "contributions.role = 'author'"
has_many :editors, :through => :contributions, :source => :editor, :conditions => "contributions.role = 'editor'"
has_many :illustrators, :through => :contributions, :source => :illustrator, :conditions => "role = 'contributions.illustrator'"
has_many :proofreaders, :through => :contributions, :source => :proofreader, :conditions => "role = 'contributions.proofreader'"
end
</code></pre>
<p>Here I've added some special associations to make it easier to access contributors by role. I'm assuming there will only be one contribution record for each role, so there's no <code>:uniq</code> option needed. With the above associations we can say <code>book.contributors</code> and <code>book.authors</code>, and both will return collections of contributors with no duplicates.</p>
<p><strong>A word about performance...</strong></p>
<p>The <code>:uniq</code> option removes duplicates in Ruby code, not in the database query. If you have a large number of duplicates, it might be better to use the <code>:select</code> option to tell the database to remove duplicates using the <code>DISTINCT</code> keyword. Like so:</p>
<pre><code>class Book < ActiveRecord::Base
has_many :contributions, :dependent => :destroy
has_many :contributors, :through => :contributions, :select => "DISTINCT contributors.*"
end
</code></pre>
<p>I find using this approach a tad messy, as you have to explicitly include the name of the table in the select option, which isn't very DRY. I'd love to see a <code>:distinct</code> option that could be used like the one in counters. (I've looked into what it would take to implement that, but the association code is some of the nastiest code in ActiveRecord, and I'm not brave enough to try a change like that yet.)</p>
<p>And remember, as always, the best way to decide between using <code>:uniq => true</code> and <code>:select => "DISTINCT..."</code> is to run performance measurements on your application. Only the data can tell you which way is best for you, or if there's even enough of a difference to matter.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/312006-04-21T11:50:00-07:002008-01-24T00:19:30-08:00Self-referential has_many :through associationsJosh Susser<p><strong>Update:</strong> This article is now superceded by a <a href="http://blog.hasmanythrough.com/2007/10/30/self-referential-has-many-through">new version</a> that is updated for Rails 2.0 changes.</p>
<p>Here we go. Rick Olson helped me figure out how to do bi-directional, self-referential associations using <code>has_many :through</code>. It's not obvious (until you know the trick), so here's how it's done.</p>
<p>This example is for modeling digraphs.</p>
<pre><code>create_table "nodes" do |t|
t.column "name", :string
t.column "capacity", :integer
end
create_table "edges" do |t|
t.column "source_id", :integer, :null => false
t.column "sink_id", :integer, :null => false
t.column "flow", :integer
end
class Edge < ActiveRecord::Base
belongs_to :source, :foreign_key => "source_id", :class_name => "Node"
belongs_to :sink, :foreign_key => "sink_id", :class_name => "Node"
end
class Node < ActiveRecord::Base
has_many :edges_as_source, :foreign_key => 'source_id', :class_name => 'Edge'
has_many :edges_as_sink, :foreign_key => 'sink_id', :class_name => 'Edge'
has_many :sources, :through => :edges_as_sink
has_many :sinks, :through => :edges_as_source
end
</code></pre>
<p>A few pictures would probably be helpful, but I don't have time to be artistic this morning so you'll have to use your imagination and visualize things on your own. Each edge connects a source node to a sink node, and each node can have any number of incoming or outgoing edges.</p>
<p>The tricky bit is using the main <code>has_many</code> associations to distinguish the direction of the edge. Use the <code>:foreign_key</code> option to specify whether the edge refers to the node as its source or sink. Then the node can refer to other nodes as its sources or sinks by going through the appropriate <code>has_many</code> association.</p>
<p>And that's it.</p><p><strong>Update:</strong> This article is now superceded by a <a href="http://blog.hasmanythrough.com/2007/10/30/self-referential-has-many-through">new version</a> that is updated for Rails 2.0 changes.</p>
<p>Here we go. Rick Olson helped me figure out how to do bi-directional, self-referential associations using <code>has_many :through</code>. It's not obvious (until you know the trick), so here's how it's done.</p>
<p>This example is for modeling digraphs.</p>
<pre><code>create_table "nodes" do |t|
t.column "name", :string
t.column "capacity", :integer
end
create_table "edges" do |t|
t.column "source_id", :integer, :null => false
t.column "sink_id", :integer, :null => false
t.column "flow", :integer
end
class Edge < ActiveRecord::Base
belongs_to :source, :foreign_key => "source_id", :class_name => "Node"
belongs_to :sink, :foreign_key => "sink_id", :class_name => "Node"
end
class Node < ActiveRecord::Base
has_many :edges_as_source, :foreign_key => 'source_id', :class_name => 'Edge'
has_many :edges_as_sink, :foreign_key => 'sink_id', :class_name => 'Edge'
has_many :sources, :through => :edges_as_sink
has_many :sinks, :through => :edges_as_source
end
</code></pre>
<p>A few pictures would probably be helpful, but I don't have time to be artistic this morning so you'll have to use your imagination and visualize things on your own. Each edge connects a source node to a sink node, and each node can have any number of incoming or outgoing edges.</p>
<p>The tricky bit is using the main <code>has_many</code> associations to distinguish the direction of the edge. Use the <code>:foreign_key</code> option to specify whether the edge refers to the node as its source or sink. Then the node can refer to other nodes as its sources or sinks by going through the appropriate <code>has_many</code> association.</p>
<p>And that's it.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/302006-04-20T15:53:00-07:002008-01-24T00:19:30-08:00Many-to-many Dance-off!Josh Susser<p>I've noticed there's a bit of confusion about the differences between the two ways to create many-to-many relationships using Rails associations. That confusion is understandable, since <code>has_many :through</code> is still pretty new there isn't much written about it. <code>has_and_belongs_to_many</code> is the old, established player and most stuff out there assumes that's what you use for a many-to-many relationship. In fact, a lot of people don't seem to grasp that there is a difference at all!</p>
<p>As we all learned from watching classic movies, the best way to tell the difference between two prospective choices is to have a dance-off. You get to see everyone's moves (which of course are an accurate reflection of inner character), nobody has to die, and the hummable tune makes it a shoe-in for an Oscar nomination. Well, it's either that or do one of those boring "compare and contrast" essays they taught us about in sixth grade English class.</p><p>I've noticed there's a bit of confusion about the differences between the two ways to create many-to-many relationships using Rails associations. That confusion is understandable, since <code>has_many :through</code> is still pretty new there isn't much written about it. <code>has_and_belongs_to_many</code> is the old, established player and most stuff out there assumes that's what you use for a many-to-many relationship. In fact, a lot of people don't seem to grasp that there is a difference at all!</p>
<p>As we all learned from watching classic movies, the best way to tell the difference between two prospective choices is to have a dance-off. You get to see everyone's moves (which of course are an accurate reflection of inner character), nobody has to die, and the hummable tune makes it a shoe-in for an Oscar nomination. Well, it's either that or do one of those boring "compare and contrast" essays they taught us about in sixth grade English class.</p>
<p>So who are the players we have to choose between? Let's take a quick look at them before the music starts and we get to see their moves.</p>
<h2>Join Table: Simple Associations</h2>
<p>Table:</p>
<pre><code>create_table "dancers_movies", :id => false do |t|
t.column "dancer_id", :integer, :null => false
t.column "movie_id", :integer, :null => false
end
</code></pre>
<p>Models:</p>
<pre><code>class Dancer < ActiveRecord::Base
has_and_belongs_to_many :movies
end
class Movie < ActiveRecord::Base
has_and_belongs_to_many :dancers
end
</code></pre>
<p><code>has_and_belongs_to_many</code> associations are simple to set up. The join table has only foreign keys for the models being joined - no primary key or other attributes. (Other attributes were supported using <code>push_with_attributes</code> for a while, but that feature has been deprecated.) There is no model class for the join table.</p>
<h2>Join Model: Rich Associations</h2>
<p>Table:</p>
<pre><code>create_table "appearances", do |t|
t.column "dancer_id", :integer, :null => false
t.column "movie_id", :integer, :null => false
t.column "character_name", :string
t.column "dance_numbers", :integer
end
</code></pre>
<p>Models:</p>
<pre><code>class Appearance < ActiveRecord::Base
belongs_to :dancer
belongs_to :movie
end
class Dancer < ActiveRecord::Base
has_many :appearances, :dependent => true
has_many :movies, :through => :appearances
end
class Movie < ActiveRecord::Base
has_many :appearances, :dependent => true
has_many :dancers, :through => :appearances
end
</code></pre>
<p><code>has_many :through</code> associations are pretty easy to set up for the simple case, but can get tricky when using other features like polymorphism. The table for the join model has a primary key and can contain attributes just like any other model.</p>
<h2>Checking out the moves</h2>
<p>Here's the basic feature comparison of the two options.</p>
<table style="text-align: left">
<tr><th>Association</th>
<td><code>has_and_belongs_to_many</code></td>
<td><code>has_many :through</code></td></tr>
<tr><th>AKA</th>
<td>habtm</td>
<td>through association</td></tr>
<tr><th>Structure</th>
<td>Join Table</td>
<td>Join Model</td></tr>
<tr><th>Primary Key</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
<tr><th>Rich Association</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
<tr><th>Proxy Collection</th>
<td style="color: green">yes</td>
<td style="color: red">no</td></tr>
<tr><th>Distinct Selection</th>
<td style="color: green">yes</td>
<td><span style="text-decoration: line-through">no</span> <span style="color: green">yes</span></td></tr>
<tr><th>Self-Referential</th>
<td style="color: green">yes</td>
<td style="color: green">yes</td></tr>
<tr><th>Eager Loading</th>
<td style="color: green">yes</td>
<td style="color: green">yes</td></tr>
<tr><th>Polymorphism</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
<tr><th>N-way Joins</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
</table>
<p>There's a lot of good stuff packed into that table, so let's break it down and see what it's all about.</p>
<h4>Structure</h4>
<p><code>has_and_belongs_to_many</code> uses a simple join table where each row is just two foreign keys. There's no model class for the join as the join table records are never accessed directly.</p>
<p><code>has_many :through</code> upgrades the join table to a full-fledged model. It uses a model class to represent entries in the table.</p>
<h4>Primary Key</h4>
<p>Join tables have no primary key. I've heard some people like to create a primary key from the pair of foreign keys, but Rails won't use that primary key for anything. I'm not sure what you'd get from creating that key, though it might give you a performance benefit depending on your database. (I'm not a DBA so I have nothing more to say about that.)</p>
<p>Join models have primary keys, just like every other model. This means you can access and manipulate records directly.</p>
<h4>Rich Association</h4>
<p>Way back before Rails 1.1, you could use <code>push_with_attributes</code> to store extra attributes in your habtm join table. There were all sorts of problems with doing that, including not being able to update the attributes later on. <code>push_with_attributes</code> has now been deprecated. If you want a rich association with extra attributes, use a join model.</p>
<h4>Proxy Collection</h4>
<p>One of the advantages of using habtm is that associations are proxy collections. That means you can create entries in the join table using the association's <code><<</code> method, just like with <code>has_many</code> associations. Since join model records have those extra attributes, it is more complicated to create them automatically the same way join table entries can be. Rails punts on this, so you have to create the join model entries manually. (For a full explanation, see my <a href="http://blog.hasmanythrough.com/2006/4/17/join-models-not-proxy-collections">Why aren't join models proxy collections?</a> article.)</p>
<h4>Distinct Selection</h4>
<p>Sometimes join table (or model) can have multiple references between the same records. For example, a person may contribute to a book as a writer and an illustrator. If you have multiple references, the database will happily return you all those multiple copies in response to your query. The option <code>:uniq</code> tells the association to filter out duplicate objects so you only get a single copy of each. This is similar to using the DISTINCT keyword in SQL, though the removal of duplicates happens in Ruby instead of the database. When this article was first written only habtm supported <code>:uniq</code>, but now through associations do as well.</p>
<h4>Self-Referential</h4>
<p>Both habtm and through associations can be self-referential. Users being friends with users is an example of a self-referential relationship. You can do that with habtm using the <code>:foreign_key</code> and <code>:association_foreign_key</code> options on the association. You can do the same thing with through associations, though it's obscure how to do it so I'll have to write up how to manage it soon.</p>
<h4>Eager Loading</h4>
<p>Both habtm and through associations support eager loading of associated objects with the <code>:include</code> option.</p>
<h4>Polymorphism</h4>
<p>Join models and through associations can work with polymorphic model types. At least in one direction they do. (c.f. <a href="http://blog.hasmanythrough.com/2006/4/3/polymorphic-through">The other side of polymorphic :through associations</a>)</p>
<h4>N-way Joins</h4>
<p>A habtm association can only join two models. But sometimes you need to represent an association of multiple models. For example, a booking might represent a flight, a passenger, and a seat assignment. Using a through association, you can create a join model that joins as many models as you need. The tricky part is building the queries to get at the associated objects conveniently.</p>
<h2>And the winner is...</h2>
<p><code>has_and_belongs_to_many</code> is light on his feet and has some smooth moves. But <code>has_many :through</code> is versatile, even if he has to work harder and his moves are a bit rough in places.</p>
<p>Seriously, there's no way to pick a winner here. Like any engineering decision, choosing a join table or a join model is a matter of picking the right tool for the job (or the right dancer for the part). Now that you've seen our players go head to head, you can make a better choice about who should get that part.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/282006-04-17T21:26:00-07:002008-01-24T00:19:30-08:00Why aren't join models proxy collections?Josh Susser<p>Who hasn't created a <code>has_many :through</code> association and then tried to use <code><<</code> to add an object to the association? It seems like the obvious thing to do, but it doesn't work. Well, it works until you save the model and find that the new record in the join model didn't get saved into the database. It took me a bit of head scratching, but I finally figured out <em>why</em> it doesn't work, and what to do about it.</p>
<p>To understand the problem, you have to dig into how ActiveRecord implements the convenience methods for working with associated objects. If you don't have a thing for the inner workings of Rails you might want to skip ahead.</p>
<p>As usual, I'll start with an example so we have something concrete to discuss. Consider a many-to-many mapping that matches technicians with their certified skills.</p>
<pre><code>class Certification < ActiveRecord::Base
belongs_to :technician
belongs_to :skill
end
class Technician < ActiveRecord::Base
has_many :certifications
has_many :skills, :through => :certifications
end
class Skill < ActiveRecord::Base
has_many :certifications
has_many :technician, :through => :certifications
end
</code></pre><p>Who hasn't created a <code>has_many :through</code> association and then tried to use <code><<</code> to add an object to the association? It seems like the obvious thing to do, but it doesn't work. Well, it works until you save the model and find that the new record in the join model didn't get saved into the database. It took me a bit of head scratching, but I finally figured out <em>why</em> it doesn't work, and what to do about it.</p>
<p>To understand the problem, you have to dig into how ActiveRecord implements the convenience methods for working with associated objects. If you don't have a thing for the inner workings of Rails you might want to skip ahead.</p>
<p>As usual, I'll start with an example so we have something concrete to discuss. Consider a many-to-many mapping that matches technicians with their certified skills.</p>
<pre><code>class Certification < ActiveRecord::Base
belongs_to :technician
belongs_to :skill
end
class Technician < ActiveRecord::Base
has_many :certifications
has_many :skills, :through => :certifications
end
class Skill < ActiveRecord::Base
has_many :certifications
has_many :technician, :through => :certifications
end
</code></pre>
<p>Given these models (and the implied database schema), it's easy to map technicians to skills by creating new certification objects.</p>
<pre><code>pat = Technician.find(1)
pat.skills.size # => 4
macosx = Skill.find(10)
cert = Certification.new(:technician => pat,
:skill => macosx)
cert.save
pat.skills.size # => 4
pat.skills(true).size # => 5
</code></pre>
<p>After the call to <code>cert.save</code>, the <code>skills</code> accessor on <code>pat</code> still returns the cached collection of skills, so we need to call it passing <code>true</code> to force a reload. All that's great, but what if we try the following?</p>
<pre><code>pat.skills.size # => 5
winxp = Skill.find(13)
pat.skills << winxp
pat.skills.size # => 6
pat.save
pat.skills(true).size # => 5
</code></pre>
<p>The above shows that Pat's WinXP certification didn't get saved. Perhaps Pat will have to take the training again. But what happened? The <code><<</code> method works fine for regular <code>has_many</code> associations, and for <code>has_and_belongs_to_many</code> too. Why doesn't it work for <code>has_many :through</code> associations?</p>
<p>If you look at the API documentation for the <a href="http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M000530">has_many</a> method, you can see it adds a collection attribute for managing the objects in the association. That attribute behaves mostly like an Array of associated objects, but it includes some extra methods that make working with those objects more convenient. That old Rails magic can make your life much easier, but it can also be confusing. If you evaluate <code>pat.skills.class</code>, the result will be <code>Array</code>. So how does an Array know how to <code>build()</code> and <code>create()</code> associated objects, or have <code><<</code> fix up their foreign keys?</p>
<p>Let me pretend I'm Penn Jillette for a moment and explain the secret behind the magic trick: It's not really an Array. In actuality it's an instance of AssociationCollection, which is a subclass of AssociationProxy. These classes follow the well-known <a href="http://c2.com/cgi/wiki?ProxyPattern">Proxy pattern</a>. They front a target object, provide some extra behavior on top of that object, and delegate all other methods to the target object. AssociationProxy delegates to a single model object, as for <code>has_one</code> or <code>belongs_to</code>. AssociationCollection delegates to an Array of model objects. Since the delegated methods include the <code>class()</code> method, if you ask the <code>skills</code> object what its class is, it will say it's an Array even though it's not. It will also do everything else an Array does, and then some.</p>
<p>The final piece of the puzzle can be found in the inheritance hierarchy of the association classes.</p>
<pre><code>AssociationProxy
AssociationCollection
HasAndBelongsToManyAssociation
HasManyAssociation
BelongsToAssociation
HasOneAssociation
HasManyThroughAssociation
</code></pre>
<p>This hierarchy shows that HasManyAssociation is a subclass of AssociationCollection, but HasManyThroughAssociation is a subclass of AssociationProxy. That means that HasManyThroughAssociation doesn't get the collection management methods that HasManyAssociation does, including <code><<</code>. But if the association doesn't have a <code><<</code> method, why doesn't <code>pat.skills << winxp</code> throw an exception? Because the <code><<</code> method is delegated to the target array! The <code>Array#<<</code> method adds the <code>winxp</code> object to the target array, but doesn't do any ActiveRecord magic to fix its foreign key or make sure it gets saved. Well, why not?</p>
<p>Think for a moment about what <code>pat.skills << winxp</code> would imply. Since skills are associated via a join model, that method would have to create a new Certification object to hold the mapping. The <code><<</code> method for <code>has_and_belongs_to_many</code> associations does something very close to that, creating a record in the appropriate join table. If it's good enough for <code>has_and_belongs_to_many</code>, why not for <code>has_many :through</code>?</p>
<p>The main difference between a simple <code>has_and_belongs_to_many</code> join table and a <code>has_many :through</code> join model is that the join model can have attributes other than the foreign keys for the records it is joining. In fact, if you didn't have those other attributes you probably wouldn't use a join model and would settle for a join table.</p>
<p>Let's say our Certification object has some extra attributes, like the date and level of the certification. Now let's get Pat certified for a new skill.</p>
<pre><code>pat.skills << winvista
</code></pre>
<p>For that to do the right thing Rails would have to create a new Certification object. Alright, what values should it use for the certification date and level? Care to guess? Neither does Rails, so you can't do it that way.</p>
<pre><code>cert = Certification.new(:technician => pat,
:skill => macosx,
:date => Date.new(2006,4,15),
:level => 1)
pat.skills(true)
</code></pre>
<p>The above example works fine for certifying Pat in a new skill. Not as convenient as <code><<</code>, but it has the advantage of working correctly.</p>
<p>In closing, I think that this all points out how different <code>has_many :through</code> associations are from regular <code>has_many</code> associations. I think it would probably have been better to coin a new name for the association rather than confuse things by reusing <code>has_many</code> for something that's not really a <code>has_many</code> anymore. At the very least, there's some work needed to fix the <code>has_many</code> documentation to describe the differences when using the <code>:through</code> option.</p>
<p>I do actually have some ideas about how to add some more magic and make join models easier to work with. However this article is long enough already, so I'll have to save that for next time.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/222006-04-03T14:20:00-07:002008-01-24T00:19:29-08:00The other side of polymorphic :through associationsJosh Susser<p>Here's a gotcha: <code>has_many :through</code> associations do not support polymorphic access to the associated object. In this article I'll show the reasons for this limitation, and also provide an approach that lets you work with polymorphic associations without too much trouble.</p>
<p>Let's start with some example code so I have something concrete to talk about. Here's some models for representing that an author can write either articles or books, and that each article or book can have multiple authors.</p>
<pre><code>class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :publication, :polymorphic => true
end
class Author < ActiveRecord::Base
has_many :authorships
has_many :publications, :through => :authorships
end
class Article < ActiveRecord::Base
has_many :authorships, :as => :publication
has_many :authors, :through => :authorships
end
class Book < ActiveRecord::Base
has_many :authorships, :as => :publication
has_many :authors, :through => :authorships
end
</code></pre>
<p>That's all by the book (no pun intended) and follows the examples in the Rails 1.1 release. So where is the gotcha?</p><p>Here's a gotcha: <code>has_many :through</code> associations do not support polymorphic access to the associated object. In this article I'll show the reasons for this limitation, and also provide an approach that lets you work with polymorphic associations without too much trouble.</p>
<p>Let's start with some example code so I have something concrete to talk about. Here's some models for representing that an author can write either articles or books, and that each article or book can have multiple authors.</p>
<pre><code>class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :publication, :polymorphic => true
end
class Author < ActiveRecord::Base
has_many :authorships
has_many :publications, :through => :authorships
end
class Article < ActiveRecord::Base
has_many :authorships, :as => :publication
has_many :authors, :through => :authorships
end
class Book < ActiveRecord::Base
has_many :authorships, :as => :publication
has_many :authors, :through => :authorships
end
</code></pre>
<p>That's all by the book (no pun intended) and follows the examples in the Rails 1.1 release. So where is the gotcha?</p>
<p><strong>The problem</strong> </p>
<p>Given our example, we can ask an article or book for its authors:</p>
<pre><code>my_article.authors # => [author1, author2, ...]
my_book.authors # => [author1, author2, ...]
</code></pre>
<p>However, we can't ask an author for all its publications!</p>
<pre><code>an_author.publications # => ERROR
</code></pre>
<p>So what's going on? Why does traversing the join model only work in one direction? Well, lets take a look at what is in the join model table.</p>
<pre><code>create_table "authorships" do |t|
t.column "author_id", :integer
t.column "publication_id", :integer
t.column "publication_type", :string
end
</code></pre>
<p>The reference to the publication is essentially a compound foreign key. The <code>_id</code> field holds the id of the object's record in its own table, and the <code>_type</code> field holds the name of the Ruby class of that object. That information is combined to do the join to find a publication's authors. Look at the SQL Rails generates for <code>article_1.authors</code></p>
<pre><code>SELECT authors.* FROM authors
INNER JOIN authorships ON authors.id = authorships.author_id
WHERE (authorships.publication_id = 1
AND authorships.publication_type = 'Article')
</code></pre>
<p>You can see in the WHERE clause how the join uses both the id and type fields to match the authors to the article. That's quite straightforward and works fine.</p>
<p>So what about the other direction? Why doesn't <code>author_1.publications</code> do the right thing? Let's try and write the SQL for it. First off, what about the SELECT clause?</p>
<pre><code>SELECT publications.* FROM publications
</code></pre>
<p>That's not right, because <em>there is no <code>publications</code> table</em>. The author's publications are scattered among some number of tables: articles, books, and possibly other types we may decide to add later. I'm not an SQL god so I don't know if there is some way to indirect the name of the table within a query, or a way to return non-homogeneous results, but I'm betting even if there were it would be pretty gross. Well, maybe not much grosser than the SQL for <code>:include</code>, but still gross. In short, you can't traverse a <code>:through</code> association to a polymorphic data type because you can't tell what table it's in.</p>
<p><strong>Another approach</strong></p>
<p>Where does that leave us? Are polymorphic :through associations useless? Not at all. There are two ways you can make use of them to get to the polymorphic object. First, if you really need to get a polymorphic collection of the associated objects, you can roll your own.</p>
<pre><code>def publications
self.authorships.collect { |a| a.publication }
end
</code></pre>
<p>There may be some performance issues with doing a query for each publication, but at least you can do it.</p>
<p>The other option is to create associations for each of the polymorphic types. Like so:</p>
<pre><code>class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :publication, :polymorphic => true
belongs_to :article, :class_name => "Article",
:foreign_key => "publication_id"
belongs_to :book, :class_name => "Book",
:foreign_key => "publication_id"
end
class Author < ActiveRecord::Base
has_many :authorships
has_many :articles, :through => :authorships, :source => :article,
:conditions => "authorships.publication_type = 'Article'"
has_many :books, :through => :authorships, :source => :book,
:conditions => "authorships.publication_type = 'Book'"
end
</code></pre>
<p>This technique provides associations that let you access all the objects of a particular class. It's not totally polymorphic, but at least you can grab lots of objects in a single query. Speaking of which, lets use these new associations to improve the <code>publications()</code> method in class Author.</p>
<pre><code>def publications
self.articles + self.books
end
</code></pre>
<p>Now we're down to needing only one query for each class of object in the polymorphic association, which scales much better than one query for each object.</p>
<p><strong>Whew!</strong></p>
<p>So that's it. Polymorphic :through associations can only be traversed from the polymorphic side. However, you can use special associations with conditions to limit the association to a particular class of object and restore the use of association access by collection.</p>
<p>Exercise for the reader: Use the approach in this article to create a join model where both associated objects are polymorphic. Have fun with that.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/202006-04-02T14:25:00-07:002008-01-24T00:19:29-08:00Rich associations: OUT, join models: INJosh Susser<p>I noticed this comment in the Rails trac timeline <a href="http://dev.rubyonrails.org/changeset/4123">[4123]</a></p>
<blockquote>
<p><code>* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH]</code></p>
</blockquote>
<p>Couple that with the change to make habtm records with additional attributes <code>:read_only</code>, and it looks like the time to switch to <code>has_many :through</code> is now.</p>
<p>By the way, I've got a little list of articles I want to write showing various ways of using join models. If you have a request for something you want to see, leave a comment and I'll add it to the list.</p><p>I noticed this comment in the Rails trac timeline <a href="http://dev.rubyonrails.org/changeset/4123">[4123]</a></p>
<blockquote>
<p><code>* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH]</code></p>
</blockquote>
<p>Couple that with the change to make habtm records with additional attributes <code>:read_only</code>, and it looks like the time to switch to <code>has_many :through</code> is now.</p>
<p>By the way, I've got a little list of articles I want to write showing various ways of using join models. If you have a request for something you want to see, leave a comment and I'll add it to the list.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/162006-03-28T15:47:00-08:002008-01-24T00:19:29-08:00The other ways :throughJosh Susser<p>Rails 1.1 has finally been <a href="http://weblog.rubyonrails.com/articles/2006/03/28/rails-1-1-rjs-active-record-respond_to-integration-tests-and-500-other-things">released</a>, and it includes a bunch of yummy new features.
Included in those are some changes to <code>has_many :through</code> associations that were added since I started writing about them.</p>
<p>The old stuff:</p>
<ul>
<li>A <code>has_many :through</code> association lets you use a join model instead of a <code>has_and_belongs_to_many</code> association and a join table. The advantages are that you can easily read and update attributes on the association, and you can use eager loading (<code>:includes</code>) to pre-fetch associated model data.</li>
</ul>
<p>The new stuff:</p>
<ul>
<li>You can use the <code>:through</code> option on a <code>has_many</code> association without a join model. This allows you to collapse two has_many relationships into one.</li>
<li>Use the <code>:source</code> option instead of <code>:class_name</code> to specify the associated model when the default name isn't what you want.</li>
</ul><p>Rails 1.1 has finally been <a href="http://weblog.rubyonrails.com/articles/2006/03/28/rails-1-1-rjs-active-record-respond_to-integration-tests-and-500-other-things">released</a>, and it includes a bunch of yummy new features.
Included in those are some changes to <code>has_many :through</code> associations that were added since I started writing about them.</p>
<p>The old stuff:</p>
<ul>
<li>A <code>has_many :through</code> association lets you use a join model instead of a <code>has_and_belongs_to_many</code> association and a join table. The advantages are that you can easily read and update attributes on the association, and you can use eager loading (<code>:includes</code>) to pre-fetch associated model data.</li>
</ul>
<p>The new stuff:</p>
<ul>
<li>You can use the <code>:through</code> option on a <code>has_many</code> association without a join model. This allows you to collapse two has_many relationships into one.</li>
<li>Use the <code>:source</code> option instead of <code>:class_name</code> to specify the associated model when the default name isn't what you want.</li>
</ul>
<p><strong>The other kind of join model</strong></p>
<p>In addition to reaching through a <code>belongs_to/belongs_to</code> join model, you can also use <code>has_many :through</code> where the join model is <code>belongs_to/has_many</code>. Here's an example:</p>
<pre><code>class Meal < ActiveRecord::Base
has_many :dishes
has_many :ingredients, :through => :dishes
end
class Dish < ActiveRecord::Base
belongs_to :meal
has_many :ingredients
end
class Ingredient < ActiveRecord::Base
belongs_to :dish
end
</code></pre>
<p>If you want to go shopping for your meal, <code>@meal.ingredients</code> gets you your shopping list.</p>
<p><strong>Use the :source</strong></p>
<p>By default, a <code>has_many :through</code> association looks for an association in the join model with the same name as the base association. In our example above, the <code>:ingredients</code> looks for an <code>:ingredients</code> association in the Dish model. Sometimes that won't work, and in those cases use the <code>:source</code> option to specify which association to use.</p>
<pre><code>class Meal < ActiveRecord::Base
has_many :dishes
has_many :ingredients, :through => :dishes
has_many :meats, :through => :dishes, :source => :ingredients,
:conditions => "ingredients.kind = 'meat'"
has_many :veggies, :through => :dishes, :source => :ingredients,
:conditions => "ingredients.kind = 'vegetable'"
has_many :dry_goods, :through => :dishes, :source => :ingredients,
:conditions => "ingredients.kind = 'dry'"
end
</code></pre>
<p>This is a change from using <code>:class_name</code> to specify the non-default model.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/62006-03-01T19:23:00-08:002008-01-24T00:19:28-08:00New association goodness in Rails 1.1, part 2Josh Susser<p>In <a href="http://blog.hasmanythrough.com/articles/2006/02/28/association-goodness">part 1</a> I covered the basics of how to set up a <code>has_many :through</code> association and simple queries using the default accessors. In this installment, I'll show how setting up association extensions can make using through associations easier.</p><p>In <a href="http://blog.hasmanythrough.com/articles/2006/02/28/association-goodness">part 1</a> I covered the basics of how to set up a <code>has_many :through</code> association and simple queries using the default accessors. In this installment, I'll show how setting up association extensions can make using through associations easier.</p>
<p>If you recall from last time, I created some model classes to represent books, the people who contributed to them, and the nature of their contribution. Now I'm going to enhance those models with <em>association extensions</em>, one of the niftier features in Rails 1.1.</p>
<p>First I'll add some more information to the join model. I'll include some information about the kind of contribution made to the book. I'm not going to get cute and fake up the migration code here, as that's not the point of this article. Here's the generic schema definition with the new data added.</p>
<pre><code>create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "royalty", :float
t.column "role", :string
end
</code></pre>
<p>I've added a <code>role</code> field to indicate the kind of contribution. It will hold values like "author", "editor", "illustrator", etc. With that addition we could do queries using dynamic finders, like so:</p>
<pre><code>josh = Contributor.find_by_name("Joshua Susser")
books = josh.books.find(:all, :conditions => ["contributions.role = ?", "author"])
</code></pre>
<p>If you look at the log you'll see this generates a query that looks like:</p>
<pre><code>SELECT books.* FROM contributions, books
WHERE (books.id = contributions.book_id
AND contributions.contributor_id = 1
AND (contributions.role = 'author'))
</code></pre>
<p>Here I'm taking advantage of the fact that the find on the association includes a join on contributions to bind back to the contributor, so I can use other fields of that table in the query. Nifty, but ugly to type and read. How can we make that better? That's where association extensions come in.</p>
<pre><code>class Contributor < ActiveRecord::Base
has_many :contributions, :dependent => true
has_many :books, :through => :contributions do
def by_role(role)
find(:all, :conditions => ["contributions.role = ?", role])
end
end
end
</code></pre>
<p>This lets us do a query like so:</p>
<pre><code>books = josh.books.by_role("author")
</code></pre>
<p>What goes on in the extension is that finders are bound to the association just as if they were sent to it as in the earlier example. The advantage is they make things easy to read and write, and can encapsulate more business logic into the queries too.</p>
<p>The documentation for extensions provides a nice way to reuse code. Put the extension definition into a module then bind it with this syntax:</p>
<pre><code>has_many :books, :through => :contributions,
:extend => BooksExtension
</code></pre>
<p>Here is a trick for using more than one extension module for an association:</p>
<pre><code>has_many :books, :through => :contributions do
include BooksExtension
include ContributionsExtension
end
</code></pre>
<p>I didn't see that documented anywhere, but I figured it was worth trying and the experiment worked great. And if you're wondering where to put the extension modules, they do just fine in the lib directory.</p>
<p>That's it for association extensions today. Tomorrow (I hope) I'll show how to do a 3-way join and really put extensions through their paces.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/42006-02-28T20:00:00-08:002008-01-24T00:19:28-08:00New association goodness in Rails 1.1Josh Susser<p>It looks like the release of Rails 1.1 is just days away, so I thought I'd share some of what I've learned about some of the new features and how they work together. Given the name of this blog, I'm going to start with the <code>has_many :through</code> association. Just to make things interesting, I'll show how association extensions can make queries on :through associations incredibly easy to use.</p>
<p>This is looking to be a fair amount of material for a blog entry, so I'm going to split it into installments. I think it should take about 3 parts to cover, unless I get sidetracked and start writing about something cool but obscure.</p><p>It looks like the release of Rails 1.1 is just days away, so I thought I'd share some of what I've learned about some of the new features and how they work together. Given the name of this blog, I'm going to start with the <code>has_many :through</code> association. Just to make things interesting, I'll show how association extensions can make queries on :through associations incredibly easy to use.</p>
<p>This is looking to be a fair amount of material for a blog entry, so I'm going to split it into installments. I think it should take about 3 parts to cover, unless I get sidetracked and start writing about something cool but obscure.</p>
<p>The <code>has_many :through</code> association allows you to specify a one-to-many relationship indirectly via an intermediate join table. In fact, you can specify more than one such relationship via the same table, which effectively makes it a replacement for <code>has_and_belongs_to_many</code>. The biggest advantage is that the join table contains full-fledged model objects complete with primary keys and ancillary data. No more <code>push_with_attributes</code>; join models just work the same way all your other ActiveRecord models do.</p>
<p>Let's try an example. The typical example uses Authors and Books, but I'm going to shift that around a bit to show you some new things later on.</p>
<p>First, the tables. Remember <code>create_table</code> creates a primary key named <code>id</code> by default.</p>
<pre><code>create_table "books" do |t|
t.column "title", :string
t.column "isbn", :string
t.column "year", :integer
end
create_table "contributors" do |t|
t.column "name", :string
end
create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "royalty", :float
end
</code></pre>
<p>Then the model classes...</p>
<pre><code>class Book < ActiveRecord::Base
has_many :contributions, :dependent => true
has_many :contributors, :through => :contributions
end
class Contributor < ActiveRecord::Base
has_many :contributions, :dependent => true
has_many :books, :through => :contributions
end
class Contribution < ActiveRecord::Base
belongs_to :book
belongs_to :contributor
end
</code></pre>
<p>Using <code>habtm</code>, the join table would have been named <code>books_contributors</code>. Here I've named it <code>contributions</code> as it's now a real model. To prove it's more than just a join table, I've added a <code>royalty</code> field to the contribution to indicate the percentage royalty the contributor will earn from sales of the book. I've also made the contribution dependent on both the book and contributor, as if either book or contributor is deleted there's no point in preserving the object that represents the relationship between them.</p>
<p>Now let's play with this a bit. (Please excuse the shameless use of a book I actually had published. It never sold a darn copy because OpenDoc was cancelled a week before it hit the stores, so this is the most use I'll ever get out of having worked on it.)</p>
<pre><code>bgod = Book.create(:title => "BYTE Guide to OpenDoc",
:isbn => "0078821185", :year => 1996)
drew = Contributor.create(:name => "Andrew MacBride")
josh = Contributor.create(:name => "Joshua Susser")
Contribution.create(:book => bgod, :contributor => drew,
:royalty => 0.20)
Contribution.create(:book => bgod, :contributor => josh,
:royalty => 0.10)
</code></pre>
<p>Time to do some queries. Notice we can use the <code>:through</code> association to get right at the data on the other side, just as if it was an <code>habtm</code> association.</p>
<pre><code>Contributor.find(1).books.first.year # => 1996
Book.find_by_year(1996).contributors.first.name # => "Andrew MacBride"
</code></pre>
<p>But we can also get at data stored in the join model.</p>
<pre><code>bgod.contributions.size # => 2
drew.contributions.first.royalty # => 0.20
</code></pre>
<p>And more interestingly...</p>
<pre><code>josh.books.find_by_year(1996).title # => "BYTE Guide to OpenDoc"
josh.contributions.find_by_book_id(bgod.id).royalty # => 0.10
</code></pre>
<p>That last expression found the join model object for the contribution between contributor <code>josh</code> and book <code>bgod</code>, then retrieved the royalty amount from that. The next logical thing to expect would be this:</p>
<pre><code>josh.contributions.find_by_book_id(bgod.id).royalty = 0.30
</code></pre>
<p>However this is either a bug or a design limitation, and you can't write to fields obtained by chained accessors and finders. I'm not sure what the actual problem is but I've seen it mentioned a few times on various blogs and mailing lists. But at least you can still do this:</p>
<pre><code>author = josh.contributions.find_by_book_id(bgod.id)
author.royalty = 0.30
</code></pre>
<p>Not ideal, but at least it works.</p>
<p>That's enough about <code>has_many :through</code> associations for this installment. Next time I'll show you how to make pretty queries using association extensions.</p>
<p>I hope this has been interesting or useful information for someone. I'm still learning how these new features work, so please let me know if you find I've got something wrong.</p>