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.
There can be different types of relationships between Classes. We're going to go through the following:
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.
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).
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.
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 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".
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 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.
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!