parm530
11/8/2017 - 3:20 PM

ActiveRecord Associations

Beginner's guide for activerecord relationships

belongs_to

  • Sets up a one-to-one connection with another model
  • Such that the model that belongs_to is assigned to the other model
  • A book belongs_to an author
class Book
  belongs_to :author
end 

has_one

  • Also, is used to set up a one-to-one connection with another model
  • Each instance of a model contains or possesses another
class Supplier 
  has_one :account
end 

has_many

  • Creates a one-to-many connection
  • Used in conjunction with belongs_to, the model on the other end of this relation will have many of the other model
  • Pluralize the other model (books)
class Author 
  has_many :books
end 

has_many :through

  • Sets up a many-to-many connection with another model
  • Introduces a third table to the connection join table
class Doctor 
  has_many :appointments
  has_many :patients, through: :appointments
end 

class Appointment 
  belongs_to :patient
  belongs_to :doctor
end 

class Patient 
  has_many :appointments
  has_many :doctors, through: :appointments
end 

has_one :through

  • Sets up a one-to-one connection with another model
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_and_belongs_to_many

  • Creates a direct many-to-many connection with another model, with no middle model!
class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end
 
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end
  • Setting up a one-to-one association means giving one model the belongs_to (this model will have the foreign key) and the other model a has_one
  • Setting up a many-to-many association is simple by giving both models the has_and_belongs_to_many.
  • The other way is through a joins table and using the through: clause.

Polymorphic Associations

  • With this association, a model can belong to more than one other model, on a single association.
  • Ex. a picture model that belongs to either an employee model or a product model:
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
  • You can call @employee.pictures or @product.pictures.
  • To access the parent @picture.imageable. To do so, you must declare 2 feilds in the polymorphic model:
    • :imageable_id
    • :imageable_type
class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end
 
    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

Counter Cache


  • Used to cache the number of belonging objects on an association!
  • Using Post/Comments relation: a comments_count column in the Post class will count many instances of Comment, caching the number of existent comments for each post.
class Post < ActiveRecord::Base 
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

  • Need to add a modelname_count column to the belonging model's migration:
# Posts table
add_column :posts, :comments_count, :integer, default: 0
#add_column belonging_to table name, :which table to count _count, :integer, default: 0
  • Add the following to the belongs_to model:
class Comment < ActiveRecord::Base
  belongs_to :post, :counter_cache => true
end

  • This will not update or reload the count, so you'll want to upgrade the migration file to incorporate the following code if you alreay have a working database, not a new table!!!:
Post.find_each { |post| Post.reset_counters(post.id, :comments) }
  • Whenever a model with a comment is added or deleted, it will automatically update the count!
  • For larger databases, to speed up time performance, replace the above code with:
class AddCommentsCountToPosts < ActiveRecord::Migration
  def change
    change_table :posts do |t|
      t.integer :comments_count, default: 0
    end

    reversible do |dir|
      dir.up { data }
    end
  end

  def data
    execute <<-SQL.squish
        UPDATE posts
           SET comments_count = (SELECT count(1)
                                   FROM comments
                                  WHERE comments.post_id = posts.id)
    SQL
  end
end