rails tutorials: Association

ER

One-to-One Association

  • One person has exactly one personal_info entry
  • One personal_info entry belongs to exactly one person
  • The “belongs to” side is the one with a foreign key
1
rails g model personal_info height:float weight:float person:references

db/migrate/_create_personal_info.rb

1
2
3
4
5
6
7
8
9
10
11
class CreatePersonalInfos < ActiveRecord::Migration[6.0]
def change
create_table :personal_infos do |t|
t.float :height
t.float :weight
t.references :person, null: false, foreign_key: true

t.timestamps
end
end
end
1
rake db:migrate

app/models/person.rb

1
2
3
class Person < ApplicationRecord
has_one :personal_info
end

app/models/personal_info.rb

1
2
3
class PersonalInfo < ApplicationRecord
belongs_to :person
end

More Methods

you have build_personal_info(hash) and create_personal_info(hash) methods on a person instance

Test

1
2
3
4
5
zhang = Person.first
pi1 = PersonalInfo.create height: 6.5 weight: 220
zhang.personal_info = pi1

zhang.build_personal_info height: 6.5 weight: 220

One-to-Many Association

  • One person has one or more jobs
  • One job entry belongs to exactly one person
  • The “belongs to” side is the one with a foreign key
1
rails g model job title company position_id person:references

db/migrate/_create_jobs.rb

1
2
3
4
5
6
7
8
9
10
11
12
class CreateJobs < ActiveRecord::Migration[6.0]
def change
create_table :jobs do |t|
t.string :title
t.string :company
t.string :position_id
t.references :person, null: false, foreign_key: true

t.timestamps
end
end
end
1
rake db:migrate

app/models/person.rb

1
2
3
4
class Person < ApplicationRecord
has_one :personal_info
has_many :jobs
end

app/models/job.rb

1
2
3
class Job < ApplicationRecord
belongs_to :person
end

More Methods

  • person.jobs = jobs
  • person.jobs << job(s)
  • person.jobs.clear

Many-to-Many

  • One person can have many hobbies
  • One hobby can be shared by many people
  • Need to create an extra (a.k.a. join) table (without a model, i.e. just a migration)
  • Convention: Plural model names separated by an underscore in alphabetical order
1
2
rails g model hobby name
rails g migration create_hobbies_people person:references hobby:references

db/migrate/_create_habbies_people.rb

1
2
3
4
5
6
7
8
class CreateHabbiesPeople < ActiveRecord::Migration[6.0]
def change
create_table :habbies_people, id:false do |t|
t.references :person, null: false, index: false, foreign_key: true
t.references :hobby, null: false, index: false, foreign_key: true
end
end
end
1
rake db:migrate

app/models/person.rb

1
2
3
4
5
class Person < ApplicationRecord
has_one :personal_info
has_many :jobs
has_and_belongs_to_many :hobbies
end

app/models/hobby.rb

1
2
3
class Hobby < ApplicationRecord
has_and_belongs_to_many :people
end

Rich Many-to-Many Association

  • Sometimes, you need to keep some data on the join table
  • You need to store grandchild relationships on a model, like Person -> Job -> SalaryRange
  • ActiveRecord provides a :through option for this purpose
  • Basic idea: you first create a regular parent-child relationship and then use the child model as a join between the parent and grandchild.
1
rails g model salary_range min_salary:float  max_salary:float job:references

db/migrate/_create_salary_ranges.rb

1
2
3
4
5
6
7
8
9
10
11
class CreateSalaryRanges < ActiveRecord::Migration[6.0]
def change
create_table :salary_ranges do |t|
t.float :min_salary
t.float :max_salary
t.references :job, null: false, foreign_key: true

t.timestamps
end
end
end
1
rake db:migrate

app/models/salary_range.rb

1
2
3
class SalaryRange < ApplicationRecord
belongs_to :job
end

app/models/job.rb

1
2
3
4
class Job < ApplicationRecord
belongs_to :person
has_one :salary_range
end

app/models/person.rb

1
2
3
4
5
6
7
8
9
10
11
class Person < ApplicationRecord
has_one :personal_info
has_many :jobs
has_and_belongs_to_many :hobbies
has_many :approx_salaries, through: :jobs, source: :salary_range

def max_salary
approx_salaries.maximum(:max_salary)
end
end
# Average, minimum and sum also available...

More Methods

1
2
lebron = Person.find_by last_name: "James"
lebron.max_salary