In this post, we take a look at associations such as one-to-one, many-to-many, and one-to-many, and how they can be applied to Ruby on Rails applications.
Join the DZone community and get the full member experience.Join For Free
An association is a connection between two Active Record models. It makes it much easier to perform various operations on the records in your code. We will divide associations into four categories:
- Polymorphic One-to-Many
When you set up one-to-one relations you are saying that a record contains exactly one instance of another model. For example, if each user in your application has only one profile, you can describe your models as:
class User < ApplicationRecord class Profile < ApplicationRecord has_one :profile belongs_to :user end end
One of the models in the given relation will have the
has_one method invocation and another one will have
belongs_to. It is used to describe which model contains a foreign key reference to the other one, in our case, it is the profiles model.
Cover your associations with RSpec tests to make sure everything works the way you want it to.
A one-to-many association is the most common used relation. This association indicates that each instance of the model A can have zero or more instances of another model B and model B belongs to only one model A.
Let's check it out via an example. We want to create an application where users can write multiple stories, our models should look like this:
class User < ApplicationRecord class Story < ApplicationRecord has_many :stories belongs_to :user end end
Here, deciding which model will have
has_many and which will have
belongs_to is more important than in one-to-one relations, because it changes the logic of your application. In both cases, the second model contains one reference to the first model in form of a foreign key.
The second model doesn't know about the first model's relation to it - it is not aware if the first model has a reference to more than one of them or just to one.
The tests for associations get more complex to write the more relations you create. You should check out how to create your own association factories to make your testing life easier.
Many-to-many associations are a bit more complex and can be handled in two ways, "has and belongs to many" and "has many through" relations.
Has and Belongs to Many
has_and_belongs_to_many association creates a direct many-to-many connection with another model. It is simpler than the other one, as it only requires calling
has_and_belongs_to_many from both models.
Example: Let's say, a user can have many different roles and the same role may contain many users, our models would be like this:
class User < ApplicationRecord class Role < ApplicationRecord has_and_belongs_to_many :roles has_and_belongs_to_many :users end end
You will need to create a join table for this association to work. It is a table that connects two different models. The join table is created with rails function
create_join_table :user, :role in a separate migration.
class CreateUserRoles < ActiveRecord::Migration def change create_table :user_roles, id: false do |t| t.references :user, index: true, foreign_key: true t.references :role, index: true, foreign_key: true end end end
This is a very simple approach, but you don't have the direct access to related objects, you can only hold references to two models and nothing else.
Has Many Through
Another way to define a many-to-many association is to use the has many through association type. Here we define a separate model, to handle that connection between two different ones.
Instead of putting down a new example, you should check this one out! It explains everything you should know about this association.
Using the example of
has_and_belongs_to_many association, this time the three models should be written like this:
class User < ApplicationRecord has_many :user_roles has_many :roles, through: :user_roles end class UserRoles < ApplicationRecord belongs_to :user belongs_to :role end class Role < ApplicationRecord has_many :user_roles has_many :users, through: :user_roles end
This association will enable you to do things like
user.role and to get a list of all connected second model instances. It will also enable you to get access to data specific to the relation between first and second models.
Polymorphic associations are the most advanced associations available to us. You can use it when you have a model that may belong to many different models on a single association.
Let's imagine you want to be able to write comments for users and stories. You want both models to be commentable. Here's how this could be declared:
class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true end class Employee < ApplicationRecord has_many :comment, as: :commentable end class Product < ApplicationRecord has_many :comment, as: :commentable end
You can think of a polymorphic
belongs_to declaration as setting up an interface that any other model can use. To declare the polymorphic interface you need to declare both a foreign key column and a type column in the model. You should run the migration once you are done.
class CreateComments < ActiveRecord::Migration def change create_table :comments do |t| t.text :body t.integer :commentable_id t.string :commentable_type t.timestamps end add_index :comments, :commentable_id end end
This migration can be simplified by using references:
class CreateComments < ActiveRecord::Migration def change create_table :comments do |t| t.text :body t.references :commentable, polymorphic: true, index: true t.timestamps end end end
There are a few additional options you can use when defining relations between models, we will cover two most commonly used ones.
If the name of the other model cannot be derived from the association name, you can use the
:class_nameoption to supply the model name. You can read more about this on RailsGuides.
Example: You want the
belongs_to relation to call for an author, but there are two problems:
- There is no model called in that way.
- The story table is missing an
Easy fix! To make this work and stop your tests from failing, you just have to specify the
:foreign_key and the actual name of the class:
class Story < ApplicationRecord belongs_to :author, class_name: 'User', foreign_key: 'user_id' end
You can use this option when you want to get rid of orphaned records since they can lead to various problems. Orphaned records are created when we delete or destroy model A that was associated with model B, but model B wasn't removed in the process.
class User < ApplicationRecord has_many :stories, dependent: :destroy end
Example: Suppose, a user called Maria wrote a lot of stories. Then, we deleted Maria from the database, but the stories still have the
user_id column set to Maria's id. These stories are called orphaned records.
Thank you for reading!
Published at DZone with permission of Nesha Zoric , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.