Friday, May 26, 2006

Ruby on Rails : Has_One versus Belongs_To

There's a question that comes up when learning Rails associations: when do you use has_one, and when do you use belongs_to? Sometimes it's not as obvious as you might think. Even the wiki page isn't very helpful, saying in general Foo belongs to Bar if table foo has a bar_id column. That's not very helpful when I'm creating my model from scratch and wondering why, not how. I can put the index wherever it makes sense.

Imagine you've got a small library of ContentTemplate objects. You're going to have instances of a Content object, and each of those pieces of Content will have a pointer to one ContentTemplate object. I suppose you'd call this a one-way many-to-one relationship. Many Content objects point to one ContentTemplate, but that one ContentTemplate does not need to know about its many Content objects. According to the traditional rules of composition you might think that Content has_one ContentTemplate, or even that ContentTemplate belongs_to Content. Both are incorrect. Let's look at why.

The second one first. If ContentTemplate belongs_to Content, then ContentTemplate will need to contain a key that says what single Content it belongs to. But ContentTemplates are used by many Content objects. So unless we want to get into a "has_and_belongs_to_many" relationship, this is not the answer. That's also wrong, since the Content object only needs to know about one ContentTemplate. It's not many to many. Also for this example our ContentTemplate does not need to know about the Content objects that use it.

So then the first is right - Content has_one ContentTemplate, yes? Nope. It appears from my experience that has_one implies a one:one relationship between the objects. That is, it assumes that when you create a new Content object, you're also going to create a new ContentTemplate object. Because the ContentTemplate object has to contain a content_id that points back to its parent. I found this concept articulated nicely at the very bottom of the wiki ForumExample: "With belongs_to, the table accepts responsibility for the foreign key. With has_one, the table expect the other table to hold it." So if Content has_one ContentTemplate, then ContentTemplate has to have the key. But as we already said, ContentTemplates are used by multiple Content objects. I don't want multiple instances of them, each pointing to its parent.

The answer turns out to be the combination of the two that might be counter-intuitive when you see it -- Content belongs_to ContentTemplate. Go back and look at how I described the problem, and you'll see that it works. With belongs_to, the table accepts responsibility for the foreign key. So Content has a content_template_id. And ContentTemplate doesn't need anything. I can point to it at will. Done.

Another way to think about this is to ask whether you're talking about creating objects or linking to pre-existing ones. If A has_one B, then when you create a new A you're probably going to be creating a new B as well. But if A belongs_to B, then you can point to a pre-existing B and hang yourself off of it. Say you've got a Person object that has_one Address. When you create a new Person, you're going to create a new Address to go with it. However, if you wanted to come back later and add another address (or change the current one) then you have to have something for that Address to belong to.

I have no idea if that made it easier to understand or not :). But I've just spent a hunk of the last 2 days trying to figure it out, and this seems to be the answer in this case, so I wanted to document it in case anybody else out there is having the same problem.


AddThis Social Bookmark Button




Technorati Tags: ,

31 comments:

Nelson said...

Wow. This finally make senes. Everything in Rails is straightforward except the ill-defined terminology used to define relationships between SQL tables (it wants to obscure it using english verbs).

Duane said...

Thanks Nelson!

ktom1 said...

helped me... thanks!

Anonymous said...

This was really helpful, thank you!

Paul said...

one more soul you saved... Thanks. While reading I have been scribing as if I were in college.

Paul said...

if I understand correctly:

==One-to-One==
class Foo
belongs_to :bar
# Table foos contains bar_id
end
class Bar
has_one :foo
# Table bars doesn't contain foo_id, but his one foo is accessable in Bar now
end

==One-to-Many==
class Foo
belongs_to :bar
# Table foos contains bar_id
end
class Bar
has_many :foo
# Table bars couldn't contain foo_id because it has multiple foos; all accessable in Bar now.
end

==Many-to-Many==
class Foo
has_and_belongs_to_many :bar
# Table bars_foos contains bar_id and foo_id
end
class Bar
has_and_belongs_to_many :foo
# Table bars_foos contains bar_id and foo_id
end

Daniel said...

Thank you, I had big problems with this, but now any more, ha, ha ;D

Anonymous said...

Thankyou for saving my hair - I was pulling it out in great handfuls before I read this....

stoobers said...

I have a rule of thumb that works for me, and I'll use your example to illustrate:

content has a pointer to content_template called 'content_template_id'. If this isn't clear, WRITE IT DOWN and look at it. 'content_template_id' is also called a foreign key. Draw an arrow starting at content.content_template_id to content_template.id

Put your finger on content.content_template_id, and trace the arrow over to content_template.id

While you do this, say outloud:

content_template_id BELONGS_TO content_template and therefore,
content belongs_to :content_template
(in SQL, this is a left join)


Then in the same breath, trace the arrow backward and say:

content_template HAS_MANY :content.

It is all about the direction of the pointer. With this technique, there is no one-to-one, only many-to-one, which is the underlying philosophy of a relational data structure. It is a set of 'daughters' that have a 'mother', but the 'mother' knows nothing about the 'daughters'. This also translates beautifully to the LEFT JOIN used by the MySQL optimizer.

st4rbux said...

stupid question: can Foo belongs_to more than one other table?

I want to create a mapping table, and my fields are employee_id, frequency_id, proficiency_id (and those other models/tables exist with a has_many relationship back to 'map'). the relationship to employee seems to work (map.employee returns the employee object) but the others do not (map.frequency gives an error).

primordial said...

thanks, mucho appreciated. My head was full of "has and belongs to many" scenarios but i knew it was wrong ... just needed you to clarify it :) well done.

Anonymous said...

Thanks! It cleared things up!

Anonymous said...

makes perfect sense, good work :)

Sergei Tulentsev said...

Thanks, Duane (and Google) :-)
You really helped me here!

Liam J. said...

Thanks, Duane. Seems a little counter-intuitive, since you think that the container would own the component, but you're right, and I can't argue with results.

Sonny Beast said...

Thanks alot for this. The definition is definitely counter intuitive.

Sean P. DeNigris said...

Thanks for a great article. And, it seems like both the links to the wiki are broken.

Yegon said...

Thanks Duane, you've made it as clear as an azure sky of deepest summer.

Anonymous said...

Very lucid explanation. Thank you!

Anonymous said...

Thank You!

Yatsuko Yin said...

Thanks!
It helped me very much!

nadine a. said...

This made a lot of sense and helped me a lot. Thanks!

Deryl R. Doucette said...

Definitely, thank you for this article. Clears up some mindspace for me. Appreciated.

Aislan S Maia said...

Thanks Duane. Wonderful post.

God bless you.

Mr Gwapo said...

Thanks a lot!

Haha actually as a beginner, the first few paragraphs confused me even more but the last two was really good. Now I get it.

David Rawk said...

I'd say the has_one is off.
My son, has_one dad.. but I'm not going to add a new fk to myself with each child.
He both belongs_to and has_one me.. (metaphorically speaking)

What makes the doc's hard is the insistence on foo and bar, and other abstract ideas for examples

a bowl has many noodles.
a noodle belongs_to a bowl..
a noodle also happens to have_one bowl.

I still don't see the distinction.

My cat has_one litter box,
and shares it with my other cat.
The box has_many cats.. (yuck but I can see it)

Anonymous said...

Bless you I was confused now Im not...

seb sept said...

yeah, it's about the way to think. Thanks a lot for sharing :)

Harshit rastogi said...

another useful post !!

Anonymous said...

greaty, it helped me a lot!

Bhavesh A.P. said...

Really great explanation in functional & technical perspective.