Rails Association

Muhammad Tri Wibowo
4 min readNov 23, 2023

--

One to One

class Supplier < ApplicationRecord
has_one :account
end

Migration

class CreateSuppliers < ActiveRecord::Migration[7.1]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end

create_table :accounts do |t|
t.belongs_to :supplier, index: { unique: true }, foreign_key: true
t.string :account_number
t.timestamps
end
end
end

One to One through Associations

class Supplier < ApplicationRecord
has_one :account
has_one :account_history, through: :account
end

class Account < ApplicationRecord
belongs_to :supplier
has_one :account_history
end

class AccountHistory < ApplicationRecord
belongs_to :account
end

Migration

class CreateAccountHistories < ActiveRecord::Migration[7.1]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end

create_table :accounts do |t|
t.belongs_to :supplier
t.string :account_number
t.timestamps
end

create_table :account_histories do |t|
t.belongs_to :account
t.integer :credit_rating
t.timestamps
end
end
end

One To Many

class Author < ApplicationRecord
has_many :books
end

Migration

class CreateAuthors < ActiveRecord::Migration[7.1]
def change
create_table :authors do |t|
t.string :name
t.timestamps
end

create_table :books do |t|
t.belongs_to :author
t.datetime :published_at
t.timestamps
end
end
end

One To Many Through Associations

class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end

class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end

Migration

class CreateAppointments < ActiveRecord::Migration[7.1]
def change
create_table :physicians do |t|
t.string :name
t.timestamps
end

create_table :patients do |t|
t.string :name
t.timestamps
end

create_table :appointments do |t|
t.belongs_to :physician
t.belongs_to :patient
t.datetime :appointment_date
t.timestamps
end
end
end

Many To Many

class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end

class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end

Or

class Assembly < ApplicationRecord
has_many :assembly_parts
has_many :parts, through: :assembly_parts
end

class AssemblyPart < ApplicationRecord
belongs_to :assembly
belongs_to :part
end

class Part < ApplicationRecord
has_many :assembly_parts
has_many :assemblies, through: :assembly_parts
end

You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.

Migration

class CreateAssembliesAndParts < ActiveRecord::Migration[7.1]
def change
create_table :assemblies do |t|
t.string :name
t.timestamps
end

create_table :parts do |t|
t.string :part_number
t.timestamps
end

create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly
t.belongs_to :part
end

add_index :assemblies_parts, :assembly_id
add_index :assemblies_parts, :part_id
end
end

Or

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.1]
def change
create_join_table :assemblies, :parts do |t|
t.index :assembly_id
t.index :part_id
end
end
end

Polymorphic Associations

class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end

class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
has_many :pictures, as: :imageable
end

Migration

class CreatePictures < ActiveRecord::Migration[7.1]
def change
create_table :pictures do |t|
t.string :name
t.bigint :imageable_id
t.string :imageable_type
t.timestamps
end

add_index :pictures, [:imageable_type, :imageable_id]
end
end

Or

class CreatePictures < ActiveRecord::Migration[7.1]
def change
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true
t.timestamps
end
end
end

Associations Between Models with Composite Primary Keys

class Order < ApplicationRecord
self.primary_key = [:shop_id, :id]
has_many :books
end

class Book < ApplicationRecord
belongs_to :order
end

Sample

order = Order.create!(id: [1, 2], status: "pending")
book = order.books.create!(title: "A Cool Book")

book.reload.order

will generate the following SQL to access the order:

SELECT * FROM orders WHERE id = 2

Or

class Author < ApplicationRecord
self.primary_key = [:first_name, :last_name]
has_many :books, query_constraints: [:first_name, :last_name]
end

class Book < ApplicationRecord
belongs_to :author, query_constraints: [:author_first_name, :author_last_name]
end

Sample

author = Author.create!(first_name: "Jane", last_name: "Doe")
book = author.books.create!(title: "A Cool Book")

book.reload.author

will use :first_name and :last_name in the SQL query:

SELECT * FROM authors WHERE first_name = 'Jane' AND last_name = 'Doe'

Self Joins

class Employee < ApplicationRecord
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"

belongs_to :manager, class_name: "Employee", optional: true
end

Migration

class CreateEmployees < ActiveRecord::Migration[7.1]
def change
create_table :employees do |t|
t.references :manager, foreign_key: { to_table: :employees }
t.timestamps
end
end
end

--

--

No responses yet