Rails Association — Part 1
What, Why, How & 3 Types of Associations discussed.
Prerequisite:
- Active Record Basics
- Rails Migrations
- Generating Migration Files (Part 1)
- Executing Migration Files (Part 2)
- Updating Existing Tables (Part 3)
What is Association?
Association is one of the features of Active Record which enables us to link between instances of 2 or more tables through model or Active Record Object.
Why Association?
This tool comes in handy when we want to extract data of one or more instances of a table B
through another table A
.
Let's consider a situation where a person has a single or multiple bank accounts. We create 2 different tables for it, first, which contains details of people, let's call that table users
and another table as bank_details
which contains details related to their bank.
Now, If we had many users, where each user had one or more bank accounts and we wanted to extract data of single users’ bank details, rails wouldn't know which bank details to extract of that particular user since we haven’t set any relationship between tables.
If you’re curious which association we would use for this example, it would be has_many-belongs_to
type of association.
How to set up connections between models:
Without Association:
However, there’s a way to extract the bank details of a user without explicitly declaring association in model files, by providing a foreign key to bank_details
table with thereference
method is given through migration.
Why foreign-key on bank_details
table? Because bank_details
instances provide more data about a particular instance of users
table.
I have explained the foreign key definition and declaration in my previous article.
So, after setting up a foreign key, lets input bank data in the bank_details
table:
Our model files:
# app/model/user.rb
class User < ApplicationRecord
end# app/model/bank_detail.rb
class BankDetail < ApplicationRecord
end
In the terminal, first, we load the required user:
user = User.find_by(name: "Juzer Shakir")
then set that users’ bank details:
bank_detail = BankDetail.create(name: "ABC Bank", user_id: user.id)
The user_id
is a foreign key attribute set through the reference
method in the migration file. We create a new bank detail for the user Juzer Shakir
by setting the name of the bank as ABC Bank
and set foreign keys’ value explicitly as the primary key value of Juzer Shakir
in users
table.
This way now we can extract all the bank details of an initialized user using a foreign key.
bank_details = BankDetail.where(user_id: user.id)
What if we want to delete a particular user from our table users
and its corresponding bank details from the bank_details
table? Well, first, we would initialize that user and extract its bank data, (following the above steps) and then:
bank_details.each do | bank_detail |
bank_detail.destroy
end
user.destroy
We loop through users’ list of bank accounts, and then manually delete each of them and then delete that user. This ensures that all the bank details of a user are deleted along with the user.
Now, let's see how this is done when given an association:
With association:
After declaring the appropriate association in the model file, we invoke several useful model methods (discussed in Part 3) in our application with which we can manipulate data in the table.
# app/model/user.rb
class User < ApplicationRecord
has_many :bank_details, dependent: :destroy
end# app/model/bank_detail.rb
class BankDetail < ApplicationRecord
belongs_to :user
end
Now, to create a new bank account of an initialized user:
bank_detail = user.bank_details.create(name: "DCB Bank")
See how instead of explicitly setting the foreign key, we use the power of association which sets the value of the foreign key appropriately and creates a new instance in the bank_details
table.
Extracting all the bank details of that user:
user.bank_details
And if we wish to delete the user along with its bank details, it's way easier:
user.destroy
The :dependent
option provided to the User
model helped delete all the bank details associated with that user. There are many other options provided by Rails for different types of associations (more on this in Part 3).
Types of Association
Active Record supports 9 types of Association and those are:
belongs_to
has_one
has_one
—:through
has_many
has_many
—:through
has_and_belongs_to_many
- Polymorphic
- Self-Join
- Single Table Inheritance
This article will cover the first 3 associations, #4–6 will be covered in Part 2 and the remaining associations have their separate articles.
So, now let’s begin with our first association, belongs_to
which is the most simple and used association of all.
belongs_to
‘belongs_to’ sets up a relationship with another model such that each instances of declaring model (where
belongs_to
is declared) ‘belongs to’ exactly one instance of another model.
The definition is self-explanatory and the example we saw above exemplifies it. However, we can also set this as a one-directional association, meaning we set a relationship with another model from only one side and not both which means we declare belongs_to
in just one model file. So taking the same example from above, our model files now will be:
# app/model/user.rb
class User < ApplicationRecord
end# app/model/bank_detail.rb
class BankDetail < ApplicationRecord
belongs_to :user
end
A belongs_to
declared in model file alone doesn’t ensure reference consistency but with the foreign key in the table which is declared through migration as:
create_table :bank_details do | t |
t.reference :user, foreign_key: true
#...
end
In a one-directional relationship, each instance of bank_details
table knows its user, but each instance of the user
table doesn’t know about their bank details.
To set up a bi-directional relationship, we can use has_one
or has_many
on a model in combination with belongs_to
to an opposite model, as seen in the example above.
Note:
The table name declared tobelongs_to
method will always be in singular-snake-case.
has_one
The
has_one
association sets anyone instance of a tableA
to only one instance of another tableB
.
For example: “A user can have only one passport”. We would set its association as follows:
# app/model/user.rb
class User < ApplicationRecord
has_one :passport
end# app/model/passport.rb
class Passport < ApplicationRecord
end
This sets a one-directional relationship. So the table, passed as a value to has_one
should have a foreign key that refers to the model where has_one
is declared. In our example, the passport
table should have a foreign key that refers to the user
table.
Typically, associations work in two directions, setting up a bi-directional association by requiring the declaration of association on two different models. To make our example bi-directional, we set a belongs_to
association on another model (More on bi-directional in Part 3):
# app/model/user.rb
class User < ApplicationRecord
has_one :passport
end# app/model/passport.rb
class Passport < ApplicationRecord
belongs_to :user
end
Now, to create a new user and its passport instance in the database, fire up your terminal, navigate to an appropriate directory, and run rails c
or rails console
- Create a new instance in the
users
table:
$ u = User.create(name: "Akshay")
=> #<User id: 1, name: "Akshay", created_at: "##", updated_at: "##">
- Create a passport of that user, a new instance in
passports
table:
$ u.passport = Passport.create(number: 97645)
=> #<Passport id: 1, number: 97645, user_id: 1, created_at: "##", updated_at: "##">
Notice, user_id
(aka. foreign-key) links to primary-key (id
) of the user we created. Now to extract a user’s passport:
$ u.passport
Or to extract a user from passports
table, first we load an instance from the passport table:
$ p = Passport.find(1)
=> #<Passport id: 1, number: 97645, user_id: 1, created_at: "##", updated_at: "##">$ p.user
=> #<User id: 1, name: "Akshay", created_at: "##", updated_at: "##">
The instance methods, passport
& user
given to the class object are created by Active Record because we gave these values, :passport
& :user
, to our association methods (has_one / belongs_to) which link to the appropriate model class.
has_one or belongs_to?
To set a one-to-one relationship between 2 models, we can use has_one
and belongs_to
combinations but how do we know exactly which model is to be declared with has_one
or belongs_to
?
The answer is in your data! So for instance, a person has only one smartphone, and we have a model named as Person
and a Smartphone
. So in Person
s’ model, we declare has_one
association because our data says that a person can have only one smartphone and since we know which model has has_one
association, the other model will have belongs_to
association. However, if we swapped the association, that would mean ‘a smartphone has only one person’, which is opposite to our example.
And once you know which model declares a belongs_to
association, we can then set a foreign key to that corresponding table through migration.
has_one — :through
In this association, the concept of linking models is similar to the has_one
association, but instead of 2, we need 3 models. So...
The
has_one-:through
association sets up 1-to-1 connection between 3 models where first models’ instance can link-up to third model via second model, where second & third model include a foreign-key.
To better understand this, let's take an example of “ a person having one Amazon pay account through its Amazon account”.
To implement the above example, we would need 3 tables in a database, and in their corresponding model files we would set the following associations:
# app/model/person.rb
class Person < ApplicationRecord
has_one :amazon_account
has_one :amazon_payment, through: :amazon_account
end# app/model/amazon_account.rb
class AmazonAccount < ApplicationRecord
belongs_to :person
has_one :amazon_payment
end# app/model/amazon_payment.rb
class AmazonPayment < ApplicationRecord
belongs_to :amazon_account
end
This is the visual representation of how models will link:
This association enables us to extract the data of a person’s Amazon payment directly instead of first extracting data from a person’s Amazon account and then from its Amazon payment. To get a better understanding, let's set up some data in our tables:
$ person = Person.create(name: "Shourya Roy", age: 25, gender: "Male", city: "New York")
Associations enable us to access person
s’ Amazon account through .amazon_account
method to a class object of the class Person
.
$ person.amazon_account
=> nil
Since we haven’t set any data on that person
s’ Amazon account, it will return value as nil
, so let's set it up:
$ person.amazon_account = AmazonAccount.create(public_name: "SRoy", email: "abc@yahoo")
=> #<AmazonAccount id: 1, public_name: "SRoy", profile_photo: nil, email: "abc@yahoo", about: nil, person_id: 1, created_at: "##", updated_at: "##">
person_id
links the instance of the Amazon account to the correct person
. Now to set person
s’ Amazon payment, we need to set it through an Amazon account.
$ acc = AmazonAccount.first$ acc.amazon_payment = AmazonPayment.create(upi: "12345@xyz", cashback: 14.00, balance: 29.00)
=> #<AmazonPayment id: 1, cashback: 14.0, upi: "12345@xyz", balance: 29.0, amazon_account_id: 1, created_at: "##", updated_at: "##">
Let's visualize the data we set above:
Extracting all Amazon payment details of a person
:
person.amazon_payment
To extract only balance
value of that user:
person.amazon_payment.balance
= 29.00
The remaining associations has_many
, has_many-:through
and has_and_belongs_to_many
are discussed thoroughly in Rails Association — Part 2. See you there!