Краткий обзор полиморфных ассоциаций ActiveRecord.

При разработке наших приложений мы часто сталкиваемся с ситуациями, когда две или более разных моделей имеют ассоциации, которые кажутся идентичными. Например, у нас может быть модель продукта и услуги, в которой много фотографий, или модель проекта, подрядчика и задачи, в которой много комментариев. Один из способов решения этой проблемы - использовать одну дочернюю таблицу базы данных для каждой родительской модели:

class Product < ApplicationRecord
  has_many :photos, class_name: 'ProductPhotos'
end
class Service < ApplicationRecord
  has_many :photos, class_name: 'ServicePhotos'
end
class Project < ApplicationRecord
  has_many :comments, class_name: 'ProjectComments'
end
class Contractor < ApplicationRecord
  has_many :comments, class_name: 'ContractorComments'
end
class Task < ApplicationRecord
  has_many :comments, class_name: 'TaskComments'
end

В качестве альтернативы мы можем использовать полиморфные ассоциации ActiveRecord для хранения этих похожих ассоциаций в одной таблице базы данных. Согласно Руководству по Ruby on Rails полиморфные ассоциации можно определить следующим образом:

С полиморфными ассоциациями модель может принадлежать нескольким другим моделям в одной ассоциации.

Мы смогли сгенерировать следующие модели:

rails g model Photo imageable:references{polymorphic}
rails g model Comment commentable:references{polymorphic}

Что создает следующие миграции:

class CreatePhotos < ActiveRecord::Migration[5.2]
  def change
    create_table :photos do |t|
      t.references :imageable, polymorphic: true
      t.timestamps
    end
  end
end
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.references :commentable, polymorphic: true
      t.timestamps
    end
  end
end

И соответствующие ассоциации ActiveRecord будут определены как таковые:

class Photo < ApplicationRecord
  belong_to :imageable
end
class Product < ApplicationRecord
  has_many :photos, as: :imageable
end
class Service < ApplicationRecord
  has_many :photos, as: :imageable
end
class Comment < ApplicationRecord
  belongs_to :commentable
end
class Project < ApplicationRecord
  has_many :comments, as: :commentable
end
class Contractor < ApplicationRecord
  has_many :comments, as: :commentable
end
class Task < ApplicationRecord
  has_many :comments, as: :commentable
end

Под капотом полиморфной миграции были созданы два столбца в каждой полиморфной таблице: imageable_id и imageable_type в фотографиях и commentable_id и commentable_type в комментариях. Поскольку полиморфная таблица может принадлежать более чем одной другой модели, ActiveRecord использует столбец polymorphic_type для определения имени класса связи, из которого нужно найти правильный экземпляр с polymorphic_id.

В приведенных выше примерах imageable_type - это строка, которая может принимать значение Product или Service, а imageable_id - внешний ключ из одной из этих двух таблиц, тогда как commentable_type - это строка, которая может принимать значения Project, Contractor или Task, а commentable_id - внешний ключ из одной из этих трех таблиц.