Skip to content

Instantly share code, notes, and snippets.

@jendiamond
Last active March 10, 2017 17:28
Show Gist options
  • Select an option

  • Save jendiamond/2e2a8fe9db2d455144579bf8770c7db0 to your computer and use it in GitHub Desktop.

Select an option

Save jendiamond/2e2a8fe9db2d455144579bf8770c7db0 to your computer and use it in GitHub Desktop.

belongs_to | has_one | has_many | has_many :through | has_one :through | has_and_belongs_to_many


Associations

Active Record associations declaratively tells Rails that there is a connection between two models.

class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end
 
class Book < ApplicationRecord
  belongs_to :author
end

Using associations we can easily
Create a new book for a particular author:

@book = @author.books.create(published_at: Time.now)

We can also easily
Delete an author
And delete all of their books:

@author.destroy

Types of Associations

Rails supports six types of associations:

===

Associations are implemented using macro-style calls, so that you can declaratively add features to your models.

For example, by declaring that one model belongs_to another,

  • You instruct Rails to maintain Primary Key and Foreign Key information between instances of the two models,
  • You also get a number of utility methods added to your model.

belongs_to Association

A belongs_to association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model.

For example:

  • Given that your application includes authors and books
  • When each book can be assigned to exactly one author
  • Then you'd declare the Book model this way:
class Book < ApplicationRecord
  belongs_to :author
end

belongs_to Association Diagram

===

belongs_to associations must use the singular term

If you use the pluralized form: (belongs_to :authors)

class Book < ApplicationRecord
  belongs_to :authors
end

You will receive the Error:
Uninitialized constant Book::Authors

This is because Rails automatically infers the class name from the association name.
If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.

===

The corresponding migration might look like this:

class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :books do |t|
      t.belongs_to :author, index: true
      t.datetime :published_at
      t.timestamps
    end
  end
end

A has_one association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences).

This association indicates that each instance of a model contains or possesses one instance of another model.

For example,

  • Given that you have a Supplier model and an Account model
  • When each Supplier in your application has only one Account
  • Then you would declare the Supplier model like this:
class Supplier < ApplicationRecord
  has_one :account
end

has_one Association Diagram

The corresponding migration might look like this:

class CreateSuppliers < ActiveRecord::Migration[5.0]
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.belongs_to :supplier, index: true
      t.string :account_number
      t.timestamps
    end
  end
end

Depending on the use case, you might also need to create a unique index and/or a foreign key constraint on the supplier column for the accounts table.

In this case, the column definition might look like this:

create_table :accounts do |t|
  t.belongs_to :supplier, index: true, unique: true, foreign_key: true
  # ...
end

A has_many association indicates a one-to-many connection with another model.

You'll often find this association on the "other side" of a belongs_to association.
This association indicates that each instance of the model has zero or more instances of another model.

For example:

  • Given that you have an application containing Authors and Books
  • When the Author has many Books
  • Then the Author model could be declared like this:
class Author < ApplicationRecord
  has_many :books
end

The name of the other model is pluralized when declaring a has_many association. (has_many :books)

has_many Association Diagram

The corresponding migration might look like this:

class CreateAuthors < ActiveRecord::Migration[5.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :books do |t|
      t.belongs_to :author, index: true
      t.datetime :published_at
      t.timestamps
    end
  end
end

A has_many :through association is often used to set up a many-to-many connection with another model.

This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.

For example: consider a medical practice where patients make appointments to see physicians.
The relevant association declarations could look like this:

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

has_many :through Association Diagram

The corresponding migration might look like this:

class CreateAppointments < ActiveRecord::Migration[5.0]
  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, index: true
      t.belongs_to :patient, index: true
      t.datetime :appointment_date
      t.timestamps
    end
  end
end

The collection of join models can be managed via the has_many association methods.

For example: if you assign:
physician.patients = patients

Then new join models are automatically created for the newly associated objects.
If some that existed previously are now missing,
then their join rows are automatically deleted.

Automatic deletion of join models is direct,
no destroy callbacks are triggered.

The has_many :through association is also useful for setting up "shortcuts" through nested has_many associations.

For example:
Given: A Document has many Sections
And a Section has many paragraphs
you may sometimes want to get a simple collection of all paragraphs in the document.

You could set that up this way:

class Document < ApplicationRecord
  has_many :sections
  has_many :paragraphs, through: :sections
end
 
class Section < ApplicationRecord
  belongs_to :document
  has_many :paragraphs
end
 
class Paragraph < ApplicationRecord
  belongs_to :section
end

With through: :sections specified, Rails will now understand:
@document.paragraphs


A has_one :through association sets up a one-to-one connection with another model. This association indicates that the declaring model can be matched with one instance of another model by proceeding through a third model. For example, if each supplier has one account, and each account is associated with one account history,
then the supplier model could look like this:

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

has_one :through Association Diagram

The corresponding migration might look like this:

class CreateAccountHistories < ActiveRecord::Migration[5.0]
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.belongs_to :supplier, index: true
      t.string :account_number
      t.timestamps
    end
 
    create_table :account_histories do |t|
      t.belongs_to :account, index: true
      t.integer :credit_rating
      t.timestamps
    end
  end
end

A has_and_belongs_to_many association creates
a direct many-to-many connection with another model,
with no intervening model.

For example: Given: that your application includes Assemblies and Parts,
With each Assembly having many Parts
And each Part appearing in many Assemblies

You could declare the models this way:

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end
 
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

has_and_belongs_to_many Association Diagram

The corresponding migration might look like this

class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
  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, index: true
      t.belongs_to :part, index: true
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment