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).
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
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
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
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
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
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
*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
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
*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
Top comments (0)