Basic Rails association cardinality

— January 15, 2007 at 16:35 PST


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 patch 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.

Cardinality and associations

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 belongs_to association is used in the model that has the foreign key.

One-to-one

Use has_one in the base, and belongs_to in the associated model.

class Employee < ActiveRecord::Base
  has_one :office
end
class Office < ActiveRecord::Base
  belongs_to :employee    # foreign key - employee_id
end

One-to-many

Use has_many in the base, and belongs_to in the associated model.

class Manager < ActiveRecord::Base
  has_many :employees
end
class Employee < ActiveRecord::Base
  belongs_to :manager     # foreign key - manager_id
end

Many-to-many

There are two ways to build a many-to-many relationship.

The first way uses a has_many association with the :through option and a join model, so there are two stages of associations.

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

For the second way, use has_and_belongs_to_many in both models. This requires a join table that has no corresponding model or primary key.

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

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 has_many :through. Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.

23 commentsassociations, rails

Comments
  1. DMK2007-01-15 18:26:07

    Thanks for this -- a clear, concise reference.

  2. Tomas Jogin2007-01-15 18:32:49

    Just to nitpick, a one-to-one relationship between office and employee seems very odd? Why would an office only hold a single employee?

  3. Josh Susser2007-01-15 18:39:00

    Tomas: A one-to-one relation is often a poor design choice, compared to just including the related data in the main table in the first place. I had a hard time thinking of a good example, but I think employee to office is a good one. Each office (or cube) can hold one person, and the assignment of employee to office can change over time. You can nitpick over doubling up in an office, but you can nitpick any contrived example.

  4. Tomas Jogin2007-01-15 20:44:38

    Josh: Yeah I agree, it's hard to think of a good one-to-one example. But cubicle-employee is certainly a good one. I read "office" as "office building", as opposed to "office space", which is why I totally hiccuped on the example. Perhaps naming it cubicle instead would make the example more obvious.

  5. Matt West2007-01-15 23:26:39

    Thanks for the info, I hadn't realised that the model that :belongs_to another also has the foreign key - definatly makes things clearer. On the subject of 1 to 1 relationships I use one on my website to store photos. I store the photos in the database, to make backing up easier. I have one table storing data about the photo; size, title, created_on, etc which has a 1 to 1 relationship with another which contains the actual photo data. This way I can use the meta-data to create a webpage without the overhead of loading the entire photo into memory. I believe that other ORM systems have an option for 'lazy loading', but this way seems to work as well. The difference this made to page loading times was dramatic.

  6. Tomas Jogin2007-01-16 01:52:30

    Great example Matt, I do something like that too; I store the binary image data in a separate table from the Image object itself, for that same reason. Perhaps though, that might be too advanced to use as a generic example of one-to-one relationships.

  7. James2007-01-16 22:00:20

    Not wishing to nit-pick, but shoudn't the forgeign key in the one-to-many example be manager_id? Apart from that, just wanted to say thanks for all the articles - been learning Rails over the last couple of months and this site's been invaluable for reference material.

  8. Josh Susser2007-01-16 22:21:13

    James: The foreign key is "manager_id", but the name of the association is "manager". Rails magic adds the "_id" for you.

    Glad you enjoy the site and find it useful. That's what it's here for!

  9. James2007-01-16 23:45:07

    I should have been more clear - you have, on the penultimate line of the one-to-many example:

    belongs_to :manager     # foreign key - employee_id
    

    whereas you should have

    belongs_to :manager     # foreign key - manager_id
    

    It doesn't affect the working of the code, but the comment might confuse people as-is.

  10. Josh Susser2007-01-17 06:32:57

    James: oh wow, stupid copy/paste error. I hate those! Oops! Fixed now.

  11. Sandro Paganotti2007-01-17 11:47:33

    Thanks Josh, clean and simple as usual :)

  12. James T2007-01-18 00:33:24

    Kind of off topic, but do you have a rss version of your feed? I've tried http://blog.hasmanythrough.com/feed/rss.xml but it seems to just be the atom feed. I'm wondering because I use a console based feed reader that only supports rss. I tried converting your atom feed to rss but didn't have any luck. Thanks.

  13. Kian2007-01-24 01:11:26

    Actually you can have a model for the join table for a hasandbelongsto_many association. The table also can (and probably should) have a primary key: usually a composite primary key. There are some things that you can't easily do with such a model however, such as issue the #save method, as AR sometimes demands a single column auto-sequenced primary key. However you can then have both the HABTM association linking two tables and also the hasmany association linking directly to the join table.

    It's not optimal code certainly, but it does work, and can be useful for legacy join tables that have additional columns in them that you want to retrieve without getting the other parent table.

  14. ChrisB2007-01-24 15:01:14

    Great post. Thank you. I have a question, though:

    Once I've set up a "has_many :through" association like the one you present here, how do I then actually -- via an rhtml form -- associate, say, the programmer whose username is "Noob"? with the project whose title is "Learn Ruby"? All the examples I can find of setting up this kind of association don't explain this next step.

    If possible, I'd like to set up a form so that a brand new programmer's username and brand new project's title could be typed in to it and then associated through the assignments table in one step. I'm guessing that's more difficult than doing it in three steps by associating a programmer that already exists in the programmers table with a project that already exists in the projects table (although I'm not sure how to do that either).

    Although associating brand new items probably doesn't make much sense with the present example (given the nature of programmers and projects), I can think of things it would be helpful if I could create and associate in one go like this.

    I just can't seem to make this work, so any help would be much appreciated.

  15. ChrisB2007-01-26 22:55:38

    After several days, I just got it to work. Thank you once again for your post--it definitely helped me cobble a solution together.

  16. Giles Bowkett2007-02-06 20:36:41

    Hey Josh -- sorry, it looks as if the Trac link is broken.

  17. Terry Nolen2007-02-08 22:20:33

    How do you satisfy a "zero or one to many"?

  18. Josh Susser2007-02-09 01:29:17

    Giles: Rails Trac is having issues. They're working on it.

    Terry: What do you mean? has_many/belongs_to can do zero-or-one-to-many. Do you mean something else?

  19. amit2007-02-13 19:50:11

    Hi Josh, thank you for this great post. I am a rails newbie and I have a question regarding many to many relationships.I have two tables 'teachers' and 'subjects' and a join table 'teachers_subjects' which has a 'year' field in addition to the foreign keys( to track which teacher was assigned which subjects in a calendar year).How can we find all the subjects allotted to a particular teacher in a particular year say, 2007. Thanks in advance.

  20. Bala Paranj2007-03-16 21:43:18

    If you have attributes in addition to the foreign keys in the join table then you must use the :through style of many-to-many declaration. In that case, you must name the many-to-many resolution table as: Course or something that describes the association between the teachers and subjects. The table name "teachers_subjects" is meaningless in this case.

  21. crayz2007-04-03 04:02:09

    I think the obvious use of one-to-one relationships is the ability to do 1-1-1, which is not possible if you try to squeeze the data into fewer tables.

    For example: class Cubicle belongs_to :employee

    class Employee has_one :cubicle

    class Weight # amount the object weighs - and associated values has_one :employee has_one :cubicle

  22. crayz2007-04-03 04:06:10

    Uggh, lets try that code again(where's the preview?)

    class Cubicle
      belongs_to :employee
      belongs_to :weight
    
    class Employee
      has_one :cubicle
      belongs_to :weight
    
    class Weight # amount the object weighs - and associated values
      has_one :employee
      has_one :cubicle
    
  23. newpixel2007-04-11 05:11:33

    I have been searching high and low for a problem that I have encountered using includes with pagination. I thought that the new way of creating a relation rather than hasandbelongs_to_many would fix it but it still wont gather the attributes of the associated model.

    here is my code: @projectpages, @projects = paginate(:project, :perpage => 10,:order => sort_clause, :include => [:students, :course, :quarter])

    models: class Association < ActiveRecord::Base belongs_to :project belongs_to :student end

    class Student < ActiveRecord::Base has_many :associations has_many :projects, :through => :associations belongs_to :discipline
    belongs_to :college belongs_to :year end

    class Project < ActiveRecord::Base has_many :associations has_many :students, :through => :associations hasandbelongs_to_many :tags hasandbelongsto_many :medias, :jointable => "projects_medias" belongs_to :typerelation belongs_to :course belongs_to :quarter end

    now ... the reason I am including students is so that I can search on the appropriate fields (find projects where students.firstname = something). If I do not include :students then I get all the attributes of student.

    but ... as soon as I include :students it I dont get the student attibutes.

    if there is any input you can give me i would deeply appreciate it.

Sorry, comments for this article are closed.