DEV Community

Cover image for Active Record Associations Guide
Olivia Pomeroy
Olivia Pomeroy

Posted on

Active Record Associations Guide

Active Record Associations

Programmers use associations in code to be able to access data in different tables. Associations allow us to shorten down our code and makes things simpler but helping us not write as much SQL. Associations follow the idea of "convention over configuration", which is the most ideal practice when it comes to programming.

Types of Active Record Associations

There can be different types of relationships between Classes. We're going to go through the following:

  • one-to-one
  • one-to-many
  • many-to-many

Our Example

To be consistent throughout this blog, we're going to stick with one example and create different associations through that. In our example, we are going to have a Cookbook, which has an author, a publisher, recipes, and ingredients. We'll walk through the different relationships each of these may have with one another.

Where to Begin

The best place to start is by mapping out what data we have and understanding how our different tables will relate to each other. This helps us understand what kind of relationship the Classes have with each other, and will especially help us figure out where the foreign keys belong. The best way to do this is by creating an Entity Relationship Diagram (ERD).

Image description

This ERD shows us our different tables, which columns that may have, and a rough idea as to the relationship amongst by tracking which tables have other tables foreign keys. This table may make a bit more sense as we walk through each relationship so take a look back at it along the way.

Now we can start building out our associations in code by using Active Record macros (a method that helps write our code).

One-to-One Relationship

A one-to-one relationship means that one table belongs to solely another. In our example, we can say that the cookbook has one publisher, and that publisher belongs to the cookbook. We write this using the has_one macro along with the belongs_to

class Cookbook < ActiveRecord::Base
  has_one :publisher
end

class Publisher < ActiveRecord::Base
  belongs_to :cookbook
end

Enter fullscreen mode Exit fullscreen mode

Syntax Notes: has_one and belongs_to both will point to a word in singular form.


This establishes a one-to-one, bi-directional connection since they point to each other.

We can establish a one-to-one, one-directional connection by using belongs_to on it's own. Such as, an editor may belong to a publisher, but the publisher may not always have or use the editor.

class Editor
  belongs_to :publisher
end
Enter fullscreen mode Exit fullscreen mode

We can create another one-to-one, bi-directional connection by using a join table. In this case we would use the macro has_one through:. We have an author, that has a publisher, who publishes their cookbook. Therefore, the cookbook has an author. The publisher belongs both to the cookbook and the author, as a "middle-man". We would write this like this:

class Cookbook < ActiveRecord::Base
  has_one :publisher
  has_one :author through: :publisher
end

class Publisher < ActiveRecord::Base
  belongs_to :cookbook
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_one :publisher
end
Enter fullscreen mode Exit fullscreen mode

Note: The order of macros matters! We need to put our has_one :car, before going through it in our Owner class.

One-to-Many Relationship

A one-to-many relationship means that one class has many instances of another class, and that second class will belong to the first class. Our cookbook has many ingredients in it, and those ingredients belong to the cookbook. We can write this out using the has_many macro, along with the belongs_to macro.

class Cookbook < ActiveRecord::Base
   has_many :ingredients
end

class Ingredient < ActiveRecord::Base
   belongs_to :cookbook
end

Enter fullscreen mode Exit fullscreen mode

Syntax Notes: has_many will always point to a word in plural form.


If you ever get confused, it's helpful to say aloud what it would sound like, as Ruby is very programmer friendly and makes it sound like our day to day grammar.


Many-to-Many Relationship

Many-to-many means one table can have many instances of another, and visa versa. We've established our cookbook has many ingredients. Those ingredients make up (belong to)a recipe. We can then say our cookbook has many recipes, through the ingredients it has listed. We would write this using the has_many through macro.

class Cookbook < ActiveRecord::Base
   has_many :ingredients
   has_many :recipes, through: :ingredients
end

class Ingredient < ActiveRecord::Base
  belongs_to :cookbook
  belongs_to :recipe

class Recipe < ActiveRecord::Base
   has_many :ingredients
   belongs_to :cookbook
end

Enter fullscreen mode Exit fullscreen mode

In this case, the Ingredients class is a join table, because it joins the relationship between the two others, by allowing Cookbook to go through it to access Recipes.

We can also use the macro has_and_belongs_to_many which creates a direct many-to-many connection with another model, without the joined table. We could say we have a recipe that combines two ingredients, to then use as an ingredient in another recipe (think making your own pasta sauce). The recipe has many ingredients, the ingredients have many recipes, the recipe belongs to the ingredients, and the ingredients also belong to the recipes. (Ik ik, it's a lot). This is how we would write it:

class Ingredient < ActiveRecord::Base
  has_and_belongs_to_many :recipes
end

class Recipe < ActiveRecord::Base
  has_and_belongs_to_many :ingredients
end

Enter fullscreen mode Exit fullscreen mode

*Of note, this does not follow the relationship set up with our ERD since there will be a change to the foreign keys. In this situation, another join or junction table will be created to hold both foreign keys. The standard is to call the join table "recipes_ingredients".

Creating Associations during Migration

We have been creating our associations through macros in our classes. We can also create associations when we make our tables before migration, which will help assist us with setting up the foreign keys. For example with our Cookbook:

class CreateANewCookbook < ActiveRecord::Migration[6.1]
  def change
    create_table :cookbook do |t|
      t.string :name
    end

    create_table :ingredient do |t|
      t.string :name
      t.belongs_to :cookbook
      t.belongs_to :recipe
    end

    create_table : recipe do |t|
      t.string :item_name
    end
end

Enter fullscreen mode Exit fullscreen mode

Polymorphic Associations

Polymorphic associations are when a model can belong to multiple other models, but with only a single association and doesn't go through a join table. Let's say we have food pictures. Those pictures could either belong to a recipe or it can belong to the cookbook (spread throughout not connected to the recipe). These two locations aren't connected to each other, and the pictures have a single association with each.

class Picture < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end

class Cookbook < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

class Recipe < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

Enter fullscreen mode Exit fullscreen mode

*imageable is an unwritten naming-convention. Imageable means that the Picture model doesn't belong to either Cookbook or Recipe, but belongs to imageable, which can then be used to create the association between either Cookbook and Picture or Recipe and Picture.

To Conclude

When we establish these relationships, it allows us then to use various methods on the classes. If we create a relationship between Ingredient and Cookbook, we can then call self(Cookbook).ingredient as a method, and so on and so forth. These Active Record Associations act as our getter and setter methods we may have used previously, so we no longer need to initiate or read the instance, because this does it for us.

You know have your associate degree - Happy coding!


Sources

https://guides.rubyonrails.org/association_basics.html

https://stackoverflow.com/questions/59173076/correct-foreign-keys-for-has-and-belongs-to-many-association

https://www.pilanites.com/polymorphic-associations-rails/

Latest comments (0)