Spec-Zone.ru › Ruby on Rails 8.1

модуль ActiveRecord::Associations::ClassMethods

Ассоциации Active Record

Ассоциации — это набор похожих на макросы методов класса, которые связывают объекты с помощью внешних ключей. Они описывают отношения, например «у проекта есть руководитель» или «проект принадлежит портфелю». Каждый макрос добавляет в класс несколько методов, специализированных в соответствии с символом коллекции или ассоциации и хешем параметров. Это работает почти так же, как собственные методы attr* в Ruby.

class Project < ActiveRecord::Base
  belongs_to              :portfolio
  has_one                 :project_manager
  has_many                :milestones
  has_and_belongs_to_many :categories
end

Теперь у класса проекта есть следующие методы (и другие), упрощающие обход и изменение его связей:

project = Project.first
project.portfolio
project.portfolio = Portfolio.first
project.reload_portfolio

project.project_manager
project.project_manager = ProjectManager.first
project.reload_project_manager

project.milestones.empty?
project.milestones.size
project.milestones
project.milestones << Milestone.first
project.milestones.delete(Milestone.first)
project.milestones.destroy(Milestone.first)
project.milestones.find(Milestone.first.id)
project.milestones.build
project.milestones.create

project.categories.empty?
project.categories.size
project.categories
project.categories << Category.first
project.categories.delete(category1)
project.categories.destroy(category1)

Предупреждение

Не создавайте ассоциации с теми же именами, что и методы экземпляра ActiveRecord::Base. Поскольку ассоциация добавляет в модель метод с таким именем, использование ассоциации с тем же именем, что и у метода, предоставляемого ActiveRecord::Base, переопределит метод, унаследованный через ActiveRecord::Base, и приведёт к ошибкам. Например, attributes и connection — неудачный выбор имён для ассоциаций, поскольку эти имена уже есть в списке методов экземпляра ActiveRecord::Base.

Автоматически создаваемые методы

Дополнительные сведения см. также в разделе «Открытые методы экземпляра» ниже (из belongs_to).

Одиночные ассоциации (один к одному)

                                  |            |  belongs_to  |
generated methods                 | belongs_to | :polymorphic | has_one
----------------------------------+------------+--------------+---------
other                             |     X      |      X       |    X
other=(other)                     |     X      |      X       |    X
build_other(attributes={})        |     X      |              |    X
create_other(attributes={})       |     X      |              |    X
create_other!(attributes={})      |     X      |              |    X
reload_other                      |     X      |      X       |    X
other_changed?                    |     X      |      X       |
other_previously_changed?         |     X      |      X       |

Ассоциации-коллекции (один ко многим / многие ко многим)

                                  |       |          | has_many
generated methods                 | habtm | has_many | :through
----------------------------------+-------+----------+----------
others                            |   X   |    X     |    X
others=(other,other,...)          |   X   |    X     |    X
other_ids                         |   X   |    X     |    X
other_ids=(id,id,...)             |   X   |    X     |    X
others<<                          |   X   |    X     |    X
others.push                       |   X   |    X     |    X
others.concat                     |   X   |    X     |    X
others.build(attributes={})       |   X   |    X     |    X
others.create(attributes={})      |   X   |    X     |    X
others.create!(attributes={})     |   X   |    X     |    X
others.size                       |   X   |    X     |    X
others.length                     |   X   |    X     |    X
others.count                      |   X   |    X     |    X
others.sum(*args)                 |   X   |    X     |    X
others.empty?                     |   X   |    X     |    X
others.clear                      |   X   |    X     |    X
others.delete(other,other,...)    |   X   |    X     |    X
others.delete_all                 |   X   |    X     |    X
others.destroy(other,other,...)   |   X   |    X     |    X
others.destroy_all                |   X   |    X     |    X
others.find(*args)                |   X   |    X     |    X
others.exists?                    |   X   |    X     |    X
others.distinct                   |   X   |    X     |    X
others.reset                      |   X   |    X     |    X
others.reload                     |   X   |    X     |    X

Переопределение автоматически создаваемых методов

Методы ассоциаций создаются в модуле, включаемом в класс модели, поэтому их легко переопределить. Исходный автоматически созданный метод можно вызвать с помощью super:

class Car < ActiveRecord::Base
  belongs_to :owner
  belongs_to :old_owner

  def owner=(new_owner)
    self.old_owner = self.owner
    super
  end
end

Модуль методов ассоциаций включается сразу после модуля автоматически созданных методов атрибутов, поэтому ассоциация переопределит методы атрибута с таким же именем.

Типы связей и ассоциации

Ассоциации Active Record позволяют описывать отношения «один к одному», «один ко многим» и «многие ко многим» между моделями. Каждая модель использует ассоциацию, чтобы описать свою роль в отношении. Ассоциация belongs_to всегда используется в модели, содержащей внешний ключ.

Один к одному

Используйте has_one в основной модели и belongs_to в связанной модели.

class Employee < ActiveRecord::Base
  has_one :office
end
class Office < ActiveRecord::Base
  belongs_to :employee    # foreign key - employee_id
end

Один ко многим

Используйте has_many в основной модели и belongs_to в связанной модели.

class Manager < ActiveRecord::Base
  has_many :employees
end
class Employee < ActiveRecord::Base
  belongs_to :manager     # foreign key - manager_id
end

Многие ко многим

Существует два способа построить отношение «многие ко многим».

Первый способ использует ассоциацию has_many с параметром :through и промежуточную модель, благодаря чему ассоциация строится в два этапа.

class Assignment < ActiveRecord::Base
  belongs_to :programmer  # foreign key - programmer_id
  belongs_to :project     # foreign key - project_id
end
class Programmer < ActiveRecord::Base
  has_many :assignments
  has_many :projects, through: :assignments
end
class Project < ActiveRecord::Base
  has_many :assignments
  has_many :programmers, through: :assignments
end

Для второго способа используйте has_and_belongs_to_many в обеих моделях. Для этого требуется таблица соединения без соответствующей модели или первичного ключа.

class Programmer < ActiveRecord::Base
  has_and_belongs_to_many :projects       # foreign keys in the join table
end
class Project < ActiveRecord::Base
  has_and_belongs_to_many :programmers    # foreign keys in the join table
end

Не всегда просто выбрать способ построения отношения «многие ко многим». Если вам нужно работать с моделью связи как с самостоятельной сущностью, используйте has_many :through. Используйте has_and_belongs_to_many при работе с унаследованными схемами или если вы никогда не работаете непосредственно с самой связью.

Это ассоциация belongs_to или has_one?

Обе ассоциации описывают связь «один к одному». Разница заключается главным образом в том, где размещается внешний ключ: он находится в таблице класса, объявляющего отношение belongs_to.

class User < ActiveRecord::Base
  # I reference an account.
  belongs_to :account
end

class Account < ActiveRecord::Base
  # One user references me.
  has_one :user
end

Таблицы этих классов могут выглядеть примерно так:

CREATE TABLE users (
  id bigint NOT NULL auto_increment,
  account_id bigint default NULL,
  name varchar default NULL,
  PRIMARY KEY  (id)
)

CREATE TABLE accounts (
  id bigint NOT NULL auto_increment,
  name varchar default NULL,
  PRIMARY KEY  (id)
)

Несохранённые объекты и ассоциации

Вы можете работать с объектами и ассоциациями до их сохранения в базе данных, однако следует учитывать некоторые особенности поведения, в основном связанные с сохранением связанных объектов.

Для ассоциации has_one, belongs_to, has_many или has_and_belongs_to_many можно задать параметр :autosave. Значение true означает, что связанные объекты будут всегда сохраняться, а false — что они никогда не будут сохраняться. Подробнее о параметре :autosave см. в документации AutosaveAssociation.

Ассоциации «один к одному»

  • Присваивание объекта ассоциации has_one автоматически сохраняет этот объект и заменяемый объект (если он есть), чтобы обновить их внешние ключи, — кроме случая, когда родительский объект не сохранён (new_record? == true).

  • Если сохранение любого из этих объектов завершится ошибкой (поскольку один из объектов недопустим), будет вызвано исключение ActiveRecord::RecordNotSaved, а присваивание отменено.

  • Чтобы присвоить объект ассоциации has_one, не сохраняя его, используйте метод build_association (описан ниже). Заменяемый объект всё равно будет сохранён для обновления его внешнего ключа.

  • Присваивание объекта ассоциации belongs_to не сохраняет объект, поскольку поле внешнего ключа находится в родительской модели. Родительская модель также не сохраняется.

Коллекции

  • Добавление объекта в коллекцию (has_many или has_and_belongs_to_many) автоматически сохраняет этот объект, кроме случая, когда родительский объект (владелец коллекции) ещё не сохранён в базе данных.

  • Если не удастся сохранить какой-либо объект, добавляемый в коллекцию (с помощью push или аналогичного метода), то push вернёт false.

  • Если при замене коллекции (с помощью association=) сохранение завершится ошибкой, будет вызвано исключение ActiveRecord::RecordNotSaved, а присваивание отменено.

  • Чтобы добавить объект в коллекцию без автоматического сохранения, используйте метод collection.build (описан ниже).

  • Все несохранённые (new_record? == true) элементы коллекции автоматически сохраняются при сохранении родительского объекта.

Настройка запроса

Ассоциации строятся на основе объектов Relation, и для их настройки можно использовать синтаксис Relation. Например, чтобы добавить условие:

class Blog < ActiveRecord::Base
  has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
end

В блоке -> { ... } можно использовать все обычные методы Relation.

Доступ к объекту-владельцу

Иногда при построении запроса полезно иметь доступ к объекту-владельцу. Владелец передаётся блоку в качестве параметра. Например, следующая ассоциация находит все события, происходящие в день рождения пользователя:

class User < ActiveRecord::Base
  has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
end

Примечание. Использовать объединение таблиц или предварительную загрузку для таких ассоциаций нельзя, поскольку эти операции выполняются до создания экземпляров. Такие ассоциации можно предварительно загрузить, но это приведёт к N+1 запросам, поскольку для каждой записи будет своя область видимости (как и при предварительной загрузке полиморфных областей видимости).

Обратные вызовы ассоциаций

Как и обычные обратные вызовы, связанные с жизненным циклом объекта Active Record, можно определять обратные вызовы, которые срабатывают при добавлении объекта в коллекцию ассоциации или его удалении из неё.

class Firm < ActiveRecord::Base
  has_many :clients,
           dependent: :destroy,
           after_add: :congratulate_client,
           after_remove: :log_after_remove

  def congratulate_client(client)
    # ...
  end

  def log_after_remove(client)
    # ...
  end
end

Callbacks можно определить тремя способами:

  1. Символом, указывающим на метод, определённый в классе, содержащем связанную коллекцию. Например, after_add: :congratulate_client вызывает Firm#congratulate_client(client).

  2. Вызываемым объектом с сигнатурой, принимающей как запись со связанной коллекцией, так и добавляемую или удаляемую запись. Например, after_add: ->(firm, client) { ... }.

  3. Объектом, который отвечает на имя обратного вызова. Например, передача after_add: CallbackObject.new вызывает CallbackObject#after_add(firm, client).

Обратные вызовы можно объединять в цепочку, передав их массивом. Пример:

class CallbackObject
  def after_add(firm, client)
    firm.log << "after_adding #{client.id}"
  end
end

class Firm < ActiveRecord::Base
  has_many :clients,
           dependent: :destroy,
           after_add: [
             :congratulate_client,
             -> (firm, client) { firm.log << "after_adding #{client.id}" },
             CallbackObject.new
           ],
           after_remove: :log_after_remove
end

Возможные обратные вызовы: before_add, after_add, before_remove и after_remove.

Если любой из обратных вызовов before_add вызовет исключение, объект не будет добавлен в коллекцию.

Аналогично, если любой из обратных вызовов before_remove вызовет исключение, объект не будет удалён из коллекции.

Примечание. Чтобы сработали обратные вызовы удаления, необходимо использовать методы destroy / destroy_all. Например:

  • firm.clients.destroy(client)

  • firm.clients.destroy(*clients)

  • firm.clients.destroy_all

Следующие методы delete / delete_all не вызывают обратные вызовы удаления:

  • firm.clients.delete(client)

  • firm.clients.delete(*clients)

  • firm.clients.delete_all

Расширения ассоциаций

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

class Account < ActiveRecord::Base
  has_many :people do
    def find_or_create_by_name(name)
      first_name, last_name = name.split(" ", 2)
      find_or_create_by(first_name: first_name, last_name: last_name)
    end
  end
end

person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name  # => "Heinemeier Hansson"

Если нужно совместно использовать одни и те же расширения во многих ассоциациях, можно использовать именованный модуль расширения.

module FindOrCreateByNameExtension
  def find_or_create_by_name(name)
    first_name, last_name = name.split(" ", 2)
    find_or_create_by(first_name: first_name, last_name: last_name)
  end
end

class Account < ActiveRecord::Base
  has_many :people, -> { extending FindOrCreateByNameExtension }
end

class Company < ActiveRecord::Base
  has_many :people, -> { extending FindOrCreateByNameExtension }
end

Для некоторых расширений требуется знание внутреннего устройства ассоциации. Расширения могут обращаться к соответствующему состоянию с помощью следующих методов (где items — имя ассоциации):

  • record.association(:items).owner — возвращает объект, частью которого является ассоциация.

  • record.association(:items).reflection — возвращает объект отражения, описывающий ассоциацию.

  • record.association(:items).target — возвращает связанный объект для belongs_to и has_one либо коллекцию связанных объектов для has_many и has_and_belongs_to_many.

Однако в коде самого расширения у вас не будет доступа к record, как описано выше. В этом случае можно обратиться к proxy_association. Например, record.association(:items) и record.items.proxy_association возвращают один и тот же объект, позволяя вызывать proxy_association.owner в расширениях ассоциаций.

Промежуточные модели ассоциаций

Ассоциации Has Many можно настроить с помощью параметра :through, чтобы извлекать данные через явную промежуточную модель. Это работает аналогично ассоциации has_and_belongs_to_many. Преимущество такого подхода в том, что к промежуточной модели можно добавить валидации, обратные вызовы и дополнительные атрибуты. Рассмотрим следующую схему:

class Author < ActiveRecord::Base
  has_many :authorships
  has_many :books, through: :authorships
end

class Authorship < ActiveRecord::Base
  belongs_to :author
  belongs_to :book
end

@author = Author.first
@author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
@author.books                              # selects all books by using the Authorship join model

Также можно пройти через ассоциацию has_many промежуточной модели:

class Firm < ActiveRecord::Base
  has_many   :clients
  has_many   :invoices, through: :clients
end

class Client < ActiveRecord::Base
  belongs_to :firm
  has_many   :invoices
end

class Invoice < ActiveRecord::Base
  belongs_to :client
end

@firm = Firm.first
@firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
@firm.invoices                            # selects all invoices by going through the Client join model

Аналогично, можно пройти через ассоциацию has_one промежуточной модели:

class Group < ActiveRecord::Base
  has_many   :users
  has_many   :avatars, through: :users
end

class User < ActiveRecord::Base
  belongs_to :group
  has_one    :avatar
end

class Avatar < ActiveRecord::Base
  belongs_to :user
end

@group = Group.first
@group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
@group.avatars                                # selects all avatars by going through the User join model.

Важно учитывать, что ассоциации has_one или has_many, проходящие через промежуточную модель, доступны только для чтения. Например, следующий код не сработает в соответствии с предыдущим примером:

@group.avatars << Avatar.new   # this would work if User belonged_to Avatar rather than the other way around
@group.avatars.delete(@group.avatars.last)  # so would this

Настройка обратных ассоциаций

Если в промежуточной модели используется belongs_to, рекомендуется задать параметр :inverse_of для ассоциации belongs_to. Тогда следующий пример будет работать правильно (где tags — ассоциация has_many :through):

@post = Post.first
@tag = @post.tags.build name: "ruby"
@tag.save

Последняя строка должна сохранить промежуточную запись (Tagging). Это сработает только в том случае, если задан :inverse_of:

class Tagging < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag, inverse_of: :taggings
end

Если не задать запись :inverse_of, ассоциация попытается самостоятельно найти подходящую обратную ассоциацию. Автоматическое определение обратной ассоциации работает только для ассоциаций has_many, has_one и belongs_to.

Параметры :foreign_key и :through ассоциаций также не позволяют автоматически найти обратную ассоциацию; в некоторых случаях этому препятствуют и пользовательские области видимости. Дополнительные сведения см. в руководстве по ассоциациям Active Record.

Автоматический подбор обратной ассоциации основан на эвристике, использующей имя класса, поэтому он может работать не для всех ассоциаций, особенно если их имена нестандартны.

Автоматическое определение обратных ассоциаций можно отключить, задав параметру :inverse_of значение false, например:

class Tagging < ActiveRecord::Base
  belongs_to :tag, inverse_of: false
end

Вложенные ассоциации

С помощью параметра :through можно указать любую ассоциацию, в том числе ассоциацию, у которой самой есть параметр :through. Например:

class Author < ActiveRecord::Base
  has_many :posts
  has_many :comments, through: :posts
  has_many :commenters, through: :comments
end

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :commenter
end

@author = Author.first
@author.commenters # => People who commented on posts written by the author

Эту ассоциацию можно настроить и эквивалентным способом:

class Author < ActiveRecord::Base
  has_many :posts
  has_many :commenters, through: :posts
end

class Post < ActiveRecord::Base
  has_many :comments
  has_many :commenters, through: :comments
end

class Comment < ActiveRecord::Base
  belongs_to :commenter
end

При использовании вложенной ассоциации изменить её не получится, поскольку недостаточно информации, чтобы определить, какие изменения нужно внести. Например, при попытке добавить Commenter в приведённом выше примере невозможно определить, как настроить промежуточные объекты Post и Comment.

Полиморфные ассоциации

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

class Asset < ActiveRecord::Base
  belongs_to :attachable, polymorphic: true
end

class Post < ActiveRecord::Base
  has_many :assets, as: :attachable         # The :as option specifies the polymorphic interface to use.
end

@asset.attachable = @post

Это работает за счёт использования столбца типа наряду с внешним ключом для указания связанной записи. В примере с Asset нужны целочисленный столбец attachable_id и строковый столбец attachable_type.

Использовать полиморфные ассоциации вместе с наследованием с одной таблицей (STI) немного сложнее. Чтобы ассоциации работали ожидаемым образом, убедитесь, что в столбце типа полиморфной ассоциации хранится базовая модель моделей STI. Продолжая пример с Asset выше, предположим, что в таблице posts для STI хранятся гостевые и пользовательские публикации. В этом случае в таблице posts должен быть столбец type.

Примечание. При присваивании attachable вызывается метод attachable_type=. attachable объекта class_name передаётся как String.

class Asset < ActiveRecord::Base
  belongs_to :attachable, polymorphic: true

  def attachable_type=(class_name)
     super(class_name.constantize.base_class.to_s)
  end
end

class Post < ActiveRecord::Base
  # because we store "Post" in attachable_type now dependent: :destroy will work
  has_many :assets, as: :attachable, dependent: :destroy
end

class GuestPost < Post
end

class MemberPost < Post
end

Кэширование

Все методы основаны на простом принципе кэширования: результат последнего запроса сохраняется, если явно не указано иное. Кэш общий для разных методов, что делает использование методов, добавленных макросами, ещё эффективнее и избавляет от необходимости заранее беспокоиться о производительности.

project.milestones             # fetches milestones from the database
project.milestones.size        # uses the milestone cache
project.milestones.empty?      # uses the milestone cache
project.milestones.reload.size # fetches milestones from the database
project.milestones             # uses the milestone cache

Предварительная загрузка ассоциаций

Предварительная загрузка позволяет получать объекты определённого класса вместе с несколькими именованными ассоциациями. Это один из самых простых способов избежать пресловутой проблемы N+1, когда для получения 100 публикаций, в каждой из которых нужно отобразить автора, выполняется 101 запрос к базе данных. Предварительная загрузка сокращает число запросов со 101 до 2.

class Post < ActiveRecord::Base
  belongs_to :author
  has_many   :comments
end

Рассмотрим следующий цикл с использованием класса выше:

Post.all.each do |post|
  puts "Post:            " + post.title
  puts "Written by:      " + post.author.name
  puts "Last comment on: " + post.comments.first.created_on
end

Для перебора этих ста публикаций будет выполнен 201 запрос к базе данных. Сначала оптимизируем получение авторов:

Post.includes(:author).each do |post|

Здесь указано имя ассоциации belongs_to, в которой также используется символ :author. После загрузки публикаций find соберёт author_id каждой из них и загрузит всех соответствующих авторов одним запросом. Это сократит число запросов с 201 до 102.

Можно ещё улучшить ситуацию, указав обе ассоциации в запросе поиска:

Post.includes(:author, :comments).each do |post|

Это загрузит все комментарии одним запросом и сократит общее число запросов до 3. В общем случае число запросов будет равно единице плюс количеству именованных ассоциаций (кроме случаев, когда некоторые ассоциации belongs_to являются полиморфными; см. ниже).

Чтобы включить глубокую иерархию ассоциаций, используйте хеш:

Post.includes(:author, { comments: { author: :gravatar } }).each do |post|

Приведённый выше код загрузит все комментарии, а также всех связанных с ними авторов и граватары. Для получения нужных ассоциаций можно комбинировать символы, массивы и хеши.

Не следует считать, что благодаря сокращению числа запросов можно без потери производительности извлекать огромные объёмы данных. База данных всё равно должна передать все данные Active Record, которому также придётся их обработать. Поэтому предварительная загрузка не решает все проблемы производительности, но это отличный способ сократить число запросов в описанной выше ситуации.

Поскольку за раз загружается только одна таблица, условия или сортировка не могут ссылаться на таблицы, отличные от основной. В таких случаях Active Record возвращается к ранее использовавшемуся подходу на основе LEFT OUTER JOIN. Например:

Post.includes([:author, :comments]).where(['comments.approved = ?', true])

В результате будет выполнен один SQL-запрос с объединениями примерно такого вида: LEFT OUTER JOIN comments ON comments.post_id = posts.id и LEFT OUTER JOIN authors ON authors.id = posts.author_id. Обратите внимание, что использование таких условий может привести к непредвиденным последствиям. В приведённом выше примере публикации без одобренных комментариев вообще не возвращаются, поскольку условия применяются ко всему SQL-выражению, а не только к ассоциации.

Чтобы сработал этот запасной вариант, необходимо уточнить ссылки на столбцы: например, сработает order: "author.name DESC", но не order: "name DESC".

Чтобы загрузить все публикации, в том числе без одобренных комментариев, составьте собственный запрос LEFT OUTER JOIN с помощью ON:

Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")

В этом случае обычно естественнее включить ассоциацию, для которой задано условие:

class Post < ActiveRecord::Base
  has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
end

Post.includes(:approved_comments)

Этот код загрузит публикации и предварительно загрузит ассоциацию approved_comments, содержащую только одобренные комментарии.

Если при предварительной загрузке ассоциации задан параметр :limit, он будет проигнорирован, и будут возвращены все связанные объекты:

class Picture < ActiveRecord::Base
  has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
end

Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.

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

class Address < ActiveRecord::Base
  belongs_to :addressable, polymorphic: true
end

Вызов, пытающийся предварительно загрузить модель addressable:

Address.includes(:addressable)

Будет выполнен один запрос для загрузки адресов и по одному запросу для загрузки addressable каждого типа. Например, если все addressable относятся к классу Person или Company, всего будет выполнено 3 запроса. Список типов addressable для загрузки определяется на основе загруженных адресов. Этот способ не поддерживается, если Active Record приходится использовать предыдущую реализацию предварительной загрузки; в таком случае будет вызвано исключение ActiveRecord::EagerLoadPolymorphicError. Причина в том, что тип родительской модели — это значение столбца, поэтому соответствующее имя таблицы невозможно указать в предложениях FROM/JOIN этого запроса.

Псевдонимы таблиц

Active Record использует псевдонимы таблиц, когда таблица упоминается в объединении несколько раз. Если таблица упоминается только один раз, используется её обычное имя. При втором упоминании ей назначается псевдоним #{reflection_name}_#{parent_table_name}. При каждом последующем упоминании к имени таблицы добавляется индекс.

Post.joins(:comments)
# SELECT ... FROM posts INNER JOIN comments ON ...
Post.joins(:special_comments) # STI
# SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
# SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts

Пример с древовидной структурой:

TreeMixin.joins(:children)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
TreeMixin.joins(children: :parent)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
#                        INNER JOIN parents_mixins ...
TreeMixin.joins(children: {parent: :children})
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
#                        INNER JOIN parents_mixins ...
#                        INNER JOIN mixins childrens_mixins_2

В таблицах соединения Has and Belongs to Many используется тот же принцип, но добавляется суффикс _join:

Post.joins(:categories)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
Post.joins(categories: :posts)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
#                       INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
Post.joins(categories: {posts: :categories})
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
#                       INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
#                       INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2

Если вы хотите задать собственные объединения с помощью метода ActiveRecord::QueryMethods#joins, их имена таблиц будут иметь приоритет перед именами ассоциаций, загружаемых заранее:

Post.joins(:comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
Post.joins(:comments, :special_comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
#                       INNER JOIN comments special_comments_posts ...
#                       INNER JOIN comments ...

Псевдонимы таблиц автоматически сокращаются с учётом максимальной длины идентификаторов таблиц, установленной для конкретной базы данных.

Модули

По умолчанию ассоциации ищут объекты в текущей области видимости модуля. Рассмотрим следующий пример:

module MyApplication
  module Business
    class Firm < ActiveRecord::Base
      has_many :clients
    end

    class Client < ActiveRecord::Base; end
  end
end

При вызове Firm#clients он, в свою очередь, вызовет MyApplication::Business::Client.find_all_by_firm_id(firm.id). Чтобы связать модель с классом из другой области видимости модуля, укажите полное имя класса.

module MyApplication
  module Business
    class Firm < ActiveRecord::Base; end
  end

  module Billing
    class Account < ActiveRecord::Base
      belongs_to :firm, class_name: "MyApplication::Business::Firm"
    end
  end
end

Двунаправленные ассоциации

При объявлении ассоциации в связанной модели обычно также объявляется ассоциация, описывающая ту же связь в обратном направлении. Например, для следующих моделей:

class Dungeon < ActiveRecord::Base
  has_many :traps
  has_one :evil_wizard
end

class Trap < ActiveRecord::Base
  belongs_to :dungeon
end

class EvilWizard < ActiveRecord::Base
  belongs_to :dungeon
end

Ассоциация traps в Dungeon и ассоциация dungeon в Trap являются обратными друг другу; обратной ассоциацией для dungeon в EvilWizard является evil_wizard в Dungeon (и наоборот). По умолчанию Active Record может определить обратную ассоциацию по имени класса. Результат будет следующим:

d = Dungeon.first
t = d.traps.first
d.object_id == t.dungeon.object_id # => true

Экземпляры Dungeon d и t.dungeon в приведённом выше примере ссылаются на один и тот же экземпляр в памяти, поскольку имя ассоциации соответствует имени класса. Результат был бы таким же, если бы мы добавили :inverse_of в определения наших моделей:

class Dungeon < ActiveRecord::Base
  has_many :traps, inverse_of: :dungeon
  has_one :evil_wizard, inverse_of: :dungeon
end

class Trap < ActiveRecord::Base
  belongs_to :dungeon, inverse_of: :traps
end

class EvilWizard < ActiveRecord::Base
  belongs_to :dungeon, inverse_of: :evil_wizard
end

Дополнительные сведения см. в документации по параметру :inverse_of и в руководстве по ассоциациям Active Record.

Удаление из ассоциаций

Зависимые ассоциации

Ассоциации has_many, has_one и belongs_to поддерживают параметр :dependent. Он позволяет указать, что связанные записи нужно удалять при удалении владельца.

Например:

class Author
  has_many :posts, dependent: :destroy
end
Author.find(1).destroy # => Will destroy all of the author's posts, too

Параметр :dependent может принимать разные значения, определяющие способ удаления. Дополнительные сведения см. в документации по этому параметру для соответствующих типов ассоциаций. Если параметр не задан, связанные записи при удалении записи не затрагиваются.

Обратите внимание, что :dependent реализован с помощью системы обратных вызовов Rails, которая обрабатывает обратные вызовы по порядку. Поэтому другие обратные вызовы, объявленные до или после параметра :dependent, могут повлиять на его работу.

Обратите внимание, что параметр :dependent игнорируется для ассоциаций has_one :through.

Удалить или уничтожить?

Для ассоциаций has_many и has_and_belongs_to_many доступны методы destroy, delete, destroy_all и delete_all.

Для has_and_belongs_to_many методы delete и destroy работают одинаково: они удаляют записи из таблицы соединения.

Для has_many методы destroy и destroy_all всегда вызывают метод destroy удаляемой записи или записей, чтобы сработали обратные вызовы. Методы delete и delete_all либо выполняют удаление согласно стратегии, заданной параметром :dependent, либо, если параметр :dependent не задан, используют стратегию по умолчанию. По умолчанию ничего не происходит (внешние ключи сохраняют идентификаторы родительских объектов), кроме ассоциаций has_many :through, для которых по умолчанию используется delete_all (удаление записей соединения без вызова их обратных вызовов).

Также есть метод clear, который работает так же, как delete_all, но возвращает ассоциацию, а не удалённые записи.

Что удаляется?

Здесь есть потенциальная ловушка: ассоциации has_and_belongs_to_many и has_many :through содержат записи в таблицах соединения, а также связанные записи. Так что же именно удаляется при вызове одного из этих методов удаления?

Предполагается, что удаление из ассоциации означает удаление связи между владельцем и связанными объектами, а не обязательно удаление самих связанных объектов. Поэтому для has_and_belongs_to_many и has_many :through удаляются записи соединения, но связанные записи остаются.

Это логично: при вызове post.tags.delete(Tag.find_by(name: 'food')) вы захотите отвязать тег «food» от публикации, а не удалить сам тег из базы данных.

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

При использовании обычной ассоциации has_many различий между «ассоциированными записями» и «связью» нет, поэтому удалить можно только что-то одно.

При использовании has_and_belongs_to_many и has_many :through, если нужно удалить сами ассоциированные записи, всегда можно сделать что-то вроде person.tasks.each(&:destroy).

Устаревшие ассоциации

Ассоциации можно пометить как устаревшие, передав deprecated: true:

has_many :posts, deprecated: true

При использовании устаревшей ассоциации с помощью логгера Active Record выводится предупреждение, хотя настройки конфигурации позволяют задать дополнительные параметры.

Сообщение содержит контекст, помогающий понять, где использовалась устаревшая ассоциация:

The association Author#posts is deprecated, the method post_ids was invoked (...)
The association Author#posts is deprecated, referenced in query to preload records (...)

В приведённых выше примерах точки обозначают место на уровне приложения, где произошло использование, чтобы помочь определить, что вызвало предупреждение. Это место вычисляется с помощью средства очистки трассировки стека Active Record.

Что считается использованием?

  • Вызов любых методов ассоциации, например posts, posts= и т. д.

  • Если ассоциация принимает вложенные атрибуты — присваивание этим атрибутам.

  • Если ассоциация является ассоциацией through, а некоторые из её вложенных ассоциаций устарели, предупреждения будут выводиться каждый раз при использовании ассоциации through верхнего уровня. Это происходит независимо от того, устарела ли сама ассоциация through.

  • Выполнение запросов, в которых используется ассоциация. Например, выполнение eager_load(:posts), joins(author: :posts) и т. д.

  • Если для ассоциации задан параметр :dependent, при уничтожении ассоциированной записи выводятся предупреждения (поскольку это приводит к побочному эффекту, которого не было бы при удалении ассоциации).

  • Если для ассоциации задан параметр :touch, при сохранении или уничтожении записи выводится предупреждение (поскольку это приводит к побочному эффекту, которого не было бы при удалении ассоциации).

Действия, которые НЕ вызывают предупреждений

Большинство приведённых ниже пограничных случаев объясняется тем, что Active Record обращается к ассоциациям лениво, при их использовании. До этого ссылка на ассоциацию — по сути, просто символ Ruby.

  • Если ассоциация posts устарела, has_many :comments, through: :posts не вызывает предупреждение. Использование ассоциации comments регистрируется как использование posts, как объяснялось выше, но само объявление has_many этого не делает.

  • Аналогично, accepts_nested_attributes_for :posts не вызывает предупреждение. При присваивании атрибутов posts предупреждение выводится, как объяснялось выше, но сам вызов accepts_nested_attributes_for этого не делает.

  • То же относится к случаю, когда ассоциация объявлена обратной по отношению к устаревшей: сам макрос не вызывает предупреждение.

  • Аналогично, само объявление validates_associated :posts не вызывает предупреждение, хотя при выполнении проверки регистрируется обращение.

  • Методы запросов Relation, такие как Author.includes(:posts), сами по себе не вызывают предупреждение. На этом этапе это отношение, которое внутренне хранит символ для последующего использования. Как объяснялось в предыдущем разделе, предупреждение выводится, когда запрос выполняется.

  • Обращение к объекту отражения ассоциации, например Author.reflect_on_association(:posts) или Author.reflect_on_all_associations, не вызывает предупреждение.

Конфигурация

Вывод сообщений об использовании устаревших ассоциаций можно настроить:

config.active_record.deprecated_associations_options = { ... }

Если параметр задан, он должен быть хешем с ключами :mode и/или :backtrace.

Режим

  • В режиме :warn при использовании выводится предупреждение, содержащее место на уровне приложения, где произошло обращение, если такое место известно. Это режим по умолчанию.

  • В режиме :raise при использовании возникает исключение ActiveRecord::DeprecatedAssociationError с аналогичным сообщением и очищенной трассировкой стека в объекте исключения.

  • В режиме :notify публикуется уведомление Active Support deprecated_association.active_record. Полезная нагрузка события содержит отражение ассоциации (:reflection), место на уровне приложения (:location), где произошло обращение (объект Thread::Backtrace::Location или nil), и сообщение об устаревании (:message).

Трассировка стека

Если значение :backtrace равно true, предупреждения содержат очищенную трассировку стека в сообщении, а полезная нагрузка уведомлений включает ключ :backtrace со списком очищенных объектов Thread::Backtrace::Location. Для исключений всегда задаётся очищенная трассировка стека.

Очистка трассировки стека выполняется с помощью средства очистки трассировки стека Active Record. В приложениях Rails по умолчанию используется то же средство, что и Rails.backtrace_cleaner.

Проверка типов с помощью ActiveRecord::AssociationTypeMismatch

Если попытаться присвоить ассоциации объект, тип которого не соответствует выведенному или заданному :class_name, возникнет исключение ActiveRecord::AssociationTypeMismatch.

Параметры

Все макросы ассоциаций можно настраивать с помощью параметров. Это позволяет обрабатывать более сложные случаи, выходящие за рамки простых и предсказуемых вариантов.

Публичные методы экземпляра

belongs_to (name, scope = nil, **options) Показать исходный код
# File activerecord/lib/active_record/associations.rb, line 1824
def belongs_to(name, scope = nil, **options)
  reflection = Builder::BelongsTo.build(self, name, scope, options)
  Reflection.add_reflection(self, name, reflection)
end

Задаёт связь один-к-одному с другим классом. Этот метод следует использовать только в том случае, если внешний ключ содержится в этом классе. Если внешний ключ содержится в другом классе, вместо него следует использовать has_one. Дополнительные сведения о том, когда использовать has_one, а когда — belongs_to, см. в разделе Какую ассоциацию выбрать: belongs_to или has_one?.

Будут добавлены методы для получения и поиска одного ассоциированного объекта, идентификатор которого хранится в этом объекте:

association — это заполнитель для символа, переданного в аргументе name, поэтому belongs_to :author добавит, помимо прочих, author.nil?.

association

Возвращает ассоциированный объект. Если объект не найден, возвращается nil.

association=(associate)

Назначает ассоциированный объект, извлекает первичный ключ и задаёт его в качестве внешнего ключа. Существующие записи не изменяются и не удаляются.

build_association(attributes = {})

Возвращает новый объект ассоциированного типа, созданный с помощью attributes и связанный с этим объектом внешним ключом, но ещё не сохранённый.

create_association(attributes = {})

Возвращает новый объект ассоциированного типа, созданный с помощью attributes и связанный с этим объектом внешним ключом; объект уже сохранён, если прошёл проверку.

create_association!(attributes = {})

Выполняет то же, что и create_association, но вызывает исключение ActiveRecord::RecordInvalid, если запись недопустима.

reload_association

Возвращает ассоциированный объект, принудительно выполняя чтение из базы данных.

reset_association

Выгружает ассоциированный объект. При следующем обращении он будет запрошен из базы данных.

association_changed?

Возвращает true, если назначен новый ассоциированный объект и при следующем сохранении будет обновлён внешний ключ.

association_previously_changed?

Возвращает true, если при предыдущем сохранении ассоциация была обновлена и теперь ссылается на новый ассоциированный объект.

Пример

class Post < ActiveRecord::Base
  belongs_to :author
end

Объявление belongs_to :author добавляет следующие методы (и другие):

post = Post.find(7)
author = Author.find(19)

post.author           # similar to Author.find(post.author_id)
post.author = author  # similar to post.author_id = author.id
post.build_author     # similar to post.author = Author.new
post.create_author    # similar to post.author = Author.new; post.author.save; post.author
post.create_author!   # similar to post.author = Author.new; post.author.save!; post.author
post.reload_author
post.reset_author
post.author_changed?
post.author_previously_changed?

Области видимости

Второй аргумент scope можно передать в виде вызываемого объекта (например, proc или lambda), чтобы получить определённую запись или настроить формируемый запрос при обращении к ассоциированному объекту.

Примеры областей видимости:

belongs_to :firm, -> { where(id: 2) }
belongs_to :user, -> { joins(:friends) }
belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }

Параметры

В объявление также можно добавить хеш options, чтобы настроить поведение ассоциации.

:class_name

Задаёт имя класса ассоциации. Используйте этот параметр, только если имя класса нельзя вывести из имени ассоциации. Например, по умолчанию belongs_to :author будет связана с классом Author, но если фактическое имя класса — Person, его нужно указать с помощью этого параметра. Параметр :class_name не поддерживается для полиморфных ассоциаций, поскольку в этом случае имя класса ассоциированной записи хранится в столбце типа.

:foreign_key

Задаёт внешний ключ, используемый для ассоциации. По умолчанию он определяется как имя ассоциации с суффиксом «_id». Поэтому класс, в котором объявлена ассоциация belongs_to :person, по умолчанию будет использовать «person_id» в качестве :foreign_key. Аналогично, belongs_to :favorite_person, class_name: "Person" будет использовать внешний ключ «favorite_person_id».

При задании параметра :foreign_key автоматическое определение обратной ассоциации отключается, поэтому обычно рекомендуется также задать параметр :inverse_of.

:foreign_type

Задаёт столбец, в котором хранится тип ассоциированного объекта, если это полиморфная ассоциация. По умолчанию он определяется как имя ассоциации с суффиксом «_type». Поэтому класс, в котором объявлена ассоциация belongs_to :taggable, polymorphic: true, по умолчанию будет использовать «taggable_type» в качестве :foreign_type.

:primary_key

Задаёт метод, возвращающий первичный ключ ассоциированного объекта, используемый для ассоциации. По умолчанию это id.

:dependent

Если установлено значение :destroy, при уничтожении этого объекта уничтожается и ассоциированный объект. Если установлено значение :delete, ассоциированный объект удаляется без вызова его метода destroy. Если установлено значение :destroy_async, уничтожение ассоциированного объекта планируется как фоновая задача. Этот параметр не следует задавать, если belongs_to используется совместно с отношением has_many в другом классе, поскольку это может привести к появлению осиротевших записей.

:counter_cache

Кэширует количество принадлежащих объектов в ассоциированном классе с помощью методов CounterCache::ClassMethods#increment_counter и CounterCache::ClassMethods#decrement_counter. Кэш счётчика увеличивается при создании объекта этого класса и уменьшается при его уничтожении. Для этого в ассоциированном классе (например, в классе Post) должен использоваться столбец с именем #{table_name}_count (например, comments_count для принадлежащего ему класса Comment). Иными словами, миграция для #{table_name}_count создаётся в ассоциированном классе, чтобы Post.comments_count возвращал кэшированное количество. Можно также задать собственный столбец кэша счётчика, передав в этот параметр имя столбца вместо значения true/false (например, counter_cache: :my_custom_counter).

Добавление кэша счётчика к уже существующим большим таблицам может быть затруднительным: значения столбца необходимо заполнить отдельно от его добавления (чтобы не блокировать таблицу надолго) и до начала использования :counter_cache. В противном случае методы вроде size/any? и т. д., которые внутренне используют кэш счётчика, могут возвращать неверные результаты. Чтобы безопасно заполнить значения, одновременно обновляя столбцы кэша счётчика при создании и удалении дочерних записей, и не допустить использования указанными методами потенциально неверных значений столбца кэша (вместо этого всегда получая результаты из базы данных), используйте counter_cache: { active: false }. Если также нужно указать собственное имя столбца, используйте counter_cache: { active: false, column: :my_custom_counter }.

Примечание. Если вы включили кэш счётчика, возможно, следует добавить атрибут кэша счётчика в список attr_readonly ассоциированных классов (например, class Post; attr_readonly :comments_count; end).

:polymorphic

Объявляет ассоциацию полиморфной, если передать true. Примечание. Поскольку полиморфные ассоциации хранят имена классов в базе данных, обязательно обновите имена классов в столбце полиморфного типа *_type соответствующих строк.

:validate

Если установлено значение true, при сохранении родительского объекта проверяются новые объекты, добавленные в ассоциацию. По умолчанию используется false. Чтобы проверять ассоциированные объекты повторно при каждом обновлении, используйте validates_associated.

:autosave

Если значение равно true, при сохранении родительского объекта ассоциированный объект всегда сохраняется, а помеченный для уничтожения — уничтожается. Если значение равно false, ассоциированный объект никогда не сохраняется и не уничтожается. По умолчанию ассоциированный объект сохраняется, только если это новая запись.

Обратите внимание: NestedAttributes::ClassMethods#accepts_nested_attributes_for задаёт для :autosave значение true.

:touch

Если значение равно true, при сохранении или уничтожении этой записи обновляется время касания ассоциированного объекта (атрибуты updated_at / updated_on устанавливаются в текущее время). Если задан символ, указанный атрибут будет обновлён текущим временем дополнительно к атрибуту updated_at / updated_on. Обратите внимание: при касании проверки не выполняются, а вызываются только обратные вызовы after_touch, after_commit и after_rollback.

:inverse_of

Задаёт имя ассоциации has_one или has_many ассоциированного объекта, которая является обратной для этой ассоциации belongs_to. Дополнительные сведения см. в разделе Двунаправленные ассоциации.

:optional

Если установлено значение true, для ассоциации не проверяется наличие значения.

:required

Если установлено значение true, для ассоциации также проверяется наличие значения. Проверяется сама ассоциация, а не идентификатор. Чтобы избежать дополнительного запроса во время проверки, можно использовать :inverse_of. ПРИМЕЧАНИЕ. По умолчанию для required установлено значение true; этот параметр устарел. Если проверять наличие ассоциации не требуется, используйте optional: true.

:default

Принимает вызываемый объект (например, proc или lambda), задающий запись, которой следует инициализировать ассоциацию перед проверкой. Обратите внимание: вызываемый объект не будет выполнен, если запись существует.

:strict_loading

Каждый раз при загрузке ассоциированной записи через эту ассоциацию принудительно включается строгая загрузка.

:ensuring_owner_was

Задаёт имя метода экземпляра, вызываемого у владельца. Метод должен возвращать true, чтобы ассоциированные записи можно было удалить в фоновой задаче.

:query_constraints

Используется как составной внешний ключ. Определяет список столбцов для поиска ассоциированного объекта. Этот параметр необязателен. По умолчанию Rails попытается вывести значение автоматически. Если значение задано, размер Array должен совпадать с размером первичного ключа ассоциированной модели или с размером query_constraints.

:deprecated

Если значение равно true, ассоциация помечается как устаревшая. Использование устаревших ассоциаций регистрируется. Подробности см. выше в документации класса.

Примеры параметров:

belongs_to :firm, foreign_key: "client_of"
belongs_to :person, primary_key: "name", foreign_key: "person_name"
belongs_to :author, class_name: "Person", foreign_key: "author_id"
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
                          class_name: "Coupon", foreign_key: "coupon_id"
belongs_to :attachable, polymorphic: true
belongs_to :project, -> { readonly }
belongs_to :post, counter_cache: true
belongs_to :comment, touch: true
belongs_to :company, touch: :employees_last_updated_at
belongs_to :user, optional: true
belongs_to :account, default: -> { company.account }
belongs_to :account, strict_loading: true
belongs_to :note, query_constraints: [:organization_id, :note_id]
has_and_belongs_to_many (name, scope = nil, **options, &extension) Показать исходный код
# File activerecord/lib/active_record/associations.rb, line 2008
        def has_and_belongs_to_many(name, scope = nil, **options, &extension)
          habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)

          builder = Builder::HasAndBelongsToMany.new(name, self, options)

          join_model = builder.through_model

          const_set(join_model.name, join_model)
          private_constant(join_model.name)

          middle_reflection = builder.middle_reflection(join_model)

          Builder::HasMany.define_callbacks(self, middle_reflection)
          Reflection.add_reflection(self, middle_reflection.name, middle_reflection)
          middle_reflection.parent_reflection = habtm_reflection

          include Module.new {
            class_eval <<-RUBY, __FILE__, __LINE__ + 1
              def destroy_associations
                association(:#{middle_reflection.name}).delete_all(:delete_all)
                association(:#{name}).reset
                super
              end
            RUBY
          }

          hm_options = {}
          hm_options[:through] = middle_reflection.name
          hm_options[:source] = join_model.right_reflection.name

          [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading, :deprecated].each do |k|
            hm_options[k] = options[k] if options.key?(k)
          end

          has_many name, scope, **hm_options, &extension
          _reflections[name].parent_reflection = habtm_reflection
        end

Задаёт связь многие-ко-многим с другим классом. Два класса связываются через промежуточную таблицу соединения. Если таблица соединения не указана явно в качестве параметра, её имя определяется с учётом лексического порядка имён классов. Например, для соединения Developer и Project именем таблицы по умолчанию будет «developers_projects», поскольку в алфавитном порядке «D» предшествует «P». Обратите внимание: порядок определяется с помощью оператора < для String. Это означает, что если строки имеют разную длину, но совпадают при сравнении до конца самой короткой строки, более длинная строка считается лексически предшествующей короткой. Например, можно предположить, что таблицы «paper_boxes» и «papers» сформируют таблицу соединения «papers_paper_boxes», поскольку имя «paper_boxes» длиннее, но фактически будет создана таблица «paper_boxes_papers». Учитывайте это ограничение и при необходимости используйте пользовательский параметр :join_table. Если у таблиц есть общий префикс, он появится в начале имени только один раз. Например, для таблиц «catalog_categories» и «catalog_products» будет создана таблица соединения «catalog_categories_products».

У таблицы соединения не должно быть первичного ключа или связанной с ней модели. Необходимо создать таблицу соединения вручную с помощью миграции, например такой:

class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.1]
  def change
    create_join_table :developers, :projects
  end
end

Также рекомендуется добавить индексы для каждого из этих столбцов, чтобы ускорить соединение таблиц. Однако в MySQL рекомендуется добавить составной индекс для обоих столбцов, поскольку во время поиска MySQL использует только один индекс на таблицу.

Добавляются следующие методы для получения и поиска:

collection — это заполнитель для символа, переданного в аргументе name, поэтому has_and_belongs_to_many :categories добавит, помимо прочих, categories.empty?.

collection

Возвращает Relation со всеми ассоциированными объектами. Если объекты не найдены, возвращается пустой Relation.

collection<<(object, ...)

Добавляет один или несколько объектов в коллекцию, создавая ассоциации в таблице соединения (collection.push и collection.concat — псевдонимы этого метода). Обратите внимание: эта операция немедленно выполняет SQL-запрос на обновление, не дожидаясь вызова сохранения или обновления родительского объекта, если только родительский объект не является новой записью.

collection.delete(object, ...)

Удаляет один или несколько объектов из коллекции, удаляя их ассоциации из таблицы соединения. Сами объекты при этом не уничтожаются.

collection.destroy(object, ...)

Удаляет один или несколько объектов из коллекции, вызывая destroy для каждой ассоциации в таблице соединения и переопределяя любой параметр dependent. Сами объекты при этом не уничтожаются.

collection=objects

Заменяет содержимое коллекции, удаляя и добавляя объекты по мере необходимости.

collection_singular_ids

Возвращает массив идентификаторов ассоциированных объектов.

collection_singular_ids=ids

Заменяет коллекцию объектами, определёнными первичными ключами в ids.

collection.clear

Удаляет все объекты из коллекции. Сами объекты при этом не уничтожаются.

collection.empty?

Возвращает true, если ассоциированных объектов нет.

collection.size

Возвращает количество ассоциированных объектов.

collection.find(id)

Находит ассоциированный объект, отвечающий на id и удовлетворяющий условию, что он должен быть связан с этим объектом. Используются те же правила, что и для ActiveRecord::FinderMethods#find.

collection.exists?(...)

Проверяет, существует ли ассоциированный объект, удовлетворяющий заданным условиям. Используются те же правила, что и для ActiveRecord::FinderMethods#exists?.

collection.build(attributes = {})

Возвращает новый объект типа коллекции, созданный с помощью attributes и связанный с этим объектом через таблицу соединения, но ещё не сохранённый.

collection.create(attributes = {})

Возвращает новый объект типа коллекции, созданный с помощью attributes и связанный с этим объектом через таблицу соединения; объект уже сохранён, если прошёл проверку.

collection.reload

Возвращает Relation со всеми ассоциированными объектами, принудительно выполняя чтение из базы данных. Если объекты не найдены, возвращается пустой Relation.

Пример

class Developer < ActiveRecord::Base
  has_and_belongs_to_many :projects
end

Объявление has_and_belongs_to_many :projects добавляет следующие методы (и другие):

developer = Developer.find(11)
project   = Project.find(9)

developer.projects
developer.projects << project
developer.projects.delete(project)
developer.projects.destroy(project)
developer.projects = [project]
developer.project_ids
developer.project_ids = [9]
developer.projects.clear
developer.projects.empty?
developer.projects.size
developer.projects.find(9)
developer.projects.exists?(9)
developer.projects.build  # similar to Project.new(developer_id: 11)
developer.projects.create # similar to Project.create(developer_id: 11)
developer.projects.reload

В объявление можно добавить хеш options, чтобы настроить поведение ассоциации.

Области видимости

Второй аргумент scope можно передать в виде вызываемого объекта (например, proc или lambda), чтобы получить определённый набор записей или настроить формируемый запрос при обращении к ассоциированной коллекции.

Примеры областей видимости:

has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :categories, ->(post) {
  where("default_category = ?", post.default_category)
}

Расширения

Аргумент extension позволяет передать блок в ассоциацию has_and_belongs_to_many. Это полезно для добавления новых методов поиска, создания и других фабричных методов, используемых в рамках ассоциации.

Примеры расширений:

has_and_belongs_to_many :contractors do
  def find_or_create_by_name(name)
    first_name, last_name = name.split(" ", 2)
    find_or_create_by(first_name: first_name, last_name: last_name)
  end
end

Параметры

:class_name

Задаёт имя класса ассоциации. Используйте этот параметр, только если имя класса нельзя вывести из имени ассоциации. Например, по умолчанию has_and_belongs_to_many :projects будет связана с классом Project, но если фактическое имя класса — SuperProject, его нужно указать с помощью этого параметра.

:join_table

Задаёт имя таблицы соединения, если имя по умолчанию, основанное на лексическом порядке, вам не подходит. ПРЕДУПРЕЖДЕНИЕ: Если вы переопределяете имя таблицы для любого из классов, метод table_name ДОЛЖЕН быть объявлен ниже любого объявления has_and_belongs_to_many, иначе он не будет работать.

:foreign_key

Задаёт внешний ключ, используемый для ассоциации. По умолчанию он определяется как имя этого класса в нижнем регистре с суффиксом «_id». Поэтому класс Person, создающий ассоциацию has_and_belongs_to_many с Project, по умолчанию будет использовать «person_id» в качестве :foreign_key.

При задании параметра :foreign_key автоматическое определение обратной ассоциации отключается, поэтому обычно рекомендуется также задать параметр :inverse_of.

:association_foreign_key

Задаёт внешний ключ, используемый для ассоциации на принимающей стороне. По умолчанию он определяется как имя ассоциированного класса в нижнем регистре с суффиксом «_id». Например, если класс Person создаёт ассоциацию has_and_belongs_to_many с Project, ассоциация по умолчанию будет использовать «project_id» в качестве :association_foreign_key.

:validate

Если установлено значение true, при сохранении родительского объекта проверяются новые объекты, добавленные в ассоциацию. По умолчанию используется true. Чтобы проверять ассоциированные объекты повторно при каждом обновлении, используйте validates_associated.

:autosave

Если значение равно true, при сохранении родительского объекта ассоциированные объекты всегда сохраняются, а помеченные для уничтожения — уничтожаются. Если значение равно false, ассоциированные объекты никогда не сохраняются и не уничтожаются. По умолчанию сохраняются только новые ассоциированные записи.

Обратите внимание: NestedAttributes::ClassMethods#accepts_nested_attributes_for задаёт для :autosave значение true.

:strict_loading

Каждый раз при загрузке ассоциированной записи через эту ассоциацию принудительно включается строгая загрузка.

:deprecated

Если значение равно true, ассоциация помечается как устаревшая. Использование устаревших ассоциаций регистрируется. Подробности см. выше в документации класса.

Примеры параметров:

has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :nations, class_name: "Country"
has_and_belongs_to_many :categories, join_table: "prods_cats"
has_and_belongs_to_many :categories, -> { readonly }
has_and_belongs_to_many :categories, strict_loading: true
has_many (name, scope = nil, **options, &extension) Показать исходный код
# File activerecord/lib/active_record/associations.rb, line 1427
def has_many(name, scope = nil, **options, &extension)
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
  Reflection.add_reflection(self, name, reflection)
end

Определяет связь «один ко многим». Будут добавлены следующие методы для получения и запроса коллекций связанных объектов:

collection — это заполнитель для символа, переданного в качестве аргумента name, поэтому has_many :clients добавит, помимо прочего, clients.empty?.

collection

Возвращает Relation всех связанных объектов. Если ничего не найдено, возвращается пустой Relation.

collection<<(object, ...)

Добавляет один или несколько объектов в коллекцию, устанавливая их внешние ключи равными первичному ключу коллекции. Обратите внимание: эта операция немедленно выполняет SQL-запрос UPDATE, не дожидаясь вызова save или update для родительского объекта, если только родительский объект не является новой записью. Также будут запущены проверки и обратные вызовы связанных объектов.

collection.delete(object, ...)

Удаляет один или несколько объектов из коллекции, устанавливая их внешние ключи в значение NULL. Объекты также будут уничтожены, если они связаны с dependent: :destroy, и удалены, если они связаны с dependent: :delete_all.

Если используется параметр :through, по умолчанию записи соединения удаляются (а не обнуляются), но это поведение можно переопределить, указав dependent: :destroy или dependent: :nullify.

collection.destroy(object, ...)

Удаляет один или несколько объектов из коллекции, вызывая destroy для каждой записи независимо от параметра зависимого удаления и гарантируя запуск обратных вызовов.

Если используется параметр :through, вместо этого уничтожаются записи соединения, а не сами объекты.

collection=objects

Заменяет содержимое коллекции, удаляя и добавляя объекты по необходимости. Если параметр :through имеет значение true, запускаются обратные вызовы в моделях соединения, за исключением обратных вызовов destroy, поскольку по умолчанию удаление выполняется напрямую. Это поведение можно переопределить, указав dependent: :destroy или dependent: :nullify.

collection_singular_ids

Возвращает массив идентификаторов связанных объектов.

collection_singular_ids=ids

Заменяет коллекцию объектами, идентифицированными первичными ключами в ids. Этот метод загружает модели и вызывает collection=. См. выше.

collection.clear

Удаляет все объекты из коллекции. Связанные объекты уничтожаются, если они связаны с dependent: :destroy, напрямую удаляются из базы данных, если dependent: :delete_all, в противном случае для их внешних ключей устанавливается значение NULL. Если параметр :through имеет значение true, обратные вызовы destroy для моделей соединения не запускаются. Модели соединения удаляются напрямую.

collection.empty?

Возвращает true, если связанных объектов нет.

collection.size

Возвращает количество связанных объектов.

collection.find(...)

Находит связанный объект по тем же правилам, что и ActiveRecord::FinderMethods#find.

collection.exists?(...)

Проверяет, существует ли связанный объект с заданными условиями. Использует те же правила, что и ActiveRecord::FinderMethods#exists?.

collection.build(attributes = {}, ...)

Возвращает один или несколько новых объектов типа коллекции, созданных с помощью attributes и связанных с этим объектом через внешний ключ, но ещё не сохранённых.

collection.create(attributes = {})

Возвращает новый объект типа коллекции, созданный с помощью attributes, связанный с этим объектом через внешний ключ и уже сохранённый (если он прошёл проверку). Примечание: это работает, только если базовая модель уже существует в БД, а не является новой (несохранённой) записью!

collection.create!(attributes = {})

Выполняет то же, что и collection.create, но вызывает исключение ActiveRecord::RecordInvalid, если запись недействительна.

collection.reload

Возвращает Relation всех связанных объектов, принудительно выполняя чтение из базы данных. Если ничего не найдено, возвращается пустой Relation.

Пример

class Firm < ActiveRecord::Base
  has_many :clients
end

Объявление has_many :clients добавляет следующие методы (и другие):

firm = Firm.find(2)
client = Client.find(6)

firm.clients                       # similar to Client.where(firm_id: 2)
firm.clients << client
firm.clients.delete(client)
firm.clients.destroy(client)
firm.clients = [client]
firm.client_ids
firm.client_ids = [6]
firm.clients.clear
firm.clients.empty?                # similar to firm.clients.size == 0
firm.clients.size                  # similar to Client.count "firm_id = 2"
firm.clients.find                  # similar to Client.where(firm_id: 2).find(6)
firm.clients.exists?(name: 'ACME') # similar to Client.exists?(name: 'ACME', firm_id: 2)
firm.clients.build                 # similar to Client.new(firm_id: 2)
firm.clients.create                # similar to Client.create(firm_id: 2)
firm.clients.create!               # similar to Client.create!(firm_id: 2)
firm.clients.reload

В объявление также можно включить хеш options, чтобы настроить поведение связи.

Области видимости

Второй аргумент scope можно передать как вызываемый объект (например, proc или lambda), чтобы получать определённый набор записей или настраивать создаваемый запрос при обращении к связанной коллекции.

Примеры областей видимости:

has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }

Расширения

Аргумент extension позволяет передать блок в связь has_many. Это полезно для добавления новых методов поиска, создания и других фабричных методов, используемых в рамках связи.

Примеры расширений:

has_many :employees do
  def find_or_create_by_name(name)
    first_name, last_name = name.split(" ", 2)
    find_or_create_by(first_name: first_name, last_name: last_name)
  end
end

Параметры

:class_name

Задаёт имя класса связи. Используйте этот параметр, только если имя нельзя определить по имени связи. Например, has_many :products по умолчанию будет связано с классом Product, но если фактическое имя класса — SpecialProduct, его нужно указать с помощью этого параметра.

:foreign_key

Задаёт внешний ключ, используемый для связи. По умолчанию предполагается, что это имя данного класса в нижнем регистре с суффиксом «_id». Поэтому класс Person, в котором определена связь has_many, по умолчанию будет использовать «person_id» в качестве :foreign_key.

Задание параметра :foreign_key отключает автоматическое определение обратной связи, поэтому обычно рекомендуется также задавать параметр :inverse_of.

:foreign_type

Задаёт столбец для хранения типа связанного объекта, если связь полиморфная. По умолчанию предполагается, что это имя полиморфной связи, указанной в параметре «as», с суффиксом «_type». Поэтому класс, в котором определена связь has_many :tags, as: :taggable, по умолчанию будет использовать «taggable_type» в качестве :foreign_type.

:primary_key

Задаёт имя столбца, используемого в качестве первичного ключа связи. По умолчанию это id.

:dependent

Определяет, что происходит со связанными объектами при уничтожении их владельца. Обратите внимание: это реализовано с помощью обратных вызовов, а Rails выполняет их по порядку. Поэтому другие похожие обратные вызовы могут влиять на поведение :dependent, а поведение :dependent может влиять на другие обратные вызовы.

  • nil ничего не делает (по умолчанию).

  • :destroy также уничтожает все связанные объекты.

  • :destroy_async уничтожает все связанные объекты в фоновой задаче. ПРЕДУПРЕЖДЕНИЕ: Не используйте этот параметр, если связь обеспечивается ограничениями внешнего ключа в базе данных. Действия ограничений внешнего ключа будут выполнены в той же транзакции, что и удаление владельца.

  • :delete_all напрямую удаляет все связанные объекты из базы данных (обратные вызовы не выполняются).

  • :nullify устанавливает для внешних ключей значение NULL. Для полиморфных связей также обнуляется столбец типа полиморфной связи. Callbacks не выполняются.

  • :restrict_with_exception вызывает исключение ActiveRecord::DeleteRestrictionError, если существуют связанные записи.

  • :restrict_with_error добавляет ошибку владельцу, если существуют связанные объекты.

При использовании с параметром :through связь в модели соединения должна быть belongs_to, а удаляются записи соединения, а не связанные записи.

При использовании dependent: :destroy для связи с областью видимости уничтожаются только объекты, попадающие в эту область. Например, если модель Post определяет has_many :comments, -> { where published: true }, dependent: :destroy, а для поста вызывается destroy, уничтожаются только опубликованные комментарии. Это означает, что неопубликованные комментарии в базе данных по-прежнему будут содержать внешний ключ, указывающий на уже удалённый пост.

:counter_cache

Этот параметр позволяет настроить пользовательское имя :counter_cache.. Он нужен только в том случае, если вы изменили имя :counter_cache в связи belongs_to.

:as

Задаёт полиморфный интерфейс (см. belongs_to).

:through

Задаёт связь, через которую выполняется запрос.

Это может быть связь любого другого типа, в том числе другие связи :through, но не полиморфная связь. Параметры :class_name, :primary_key и :foreign_key игнорируются, поскольку связь использует рефлексию исходной связи.

Если связь в модели соединения — это belongs_to, коллекцию можно изменять, а записи в модели :through будут автоматически создаваться и удаляться при необходимости. В противном случае коллекция доступна только для чтения, поэтому следует напрямую изменять связь :through.

Если вы собираетесь изменять связь, а не только читать её, рекомендуется задать параметр :inverse_of для исходной связи в модели соединения. Это позволяет создавать связанные записи, которые при сохранении автоматически создадут соответствующие записи модели соединения. Подробнее см. в разделах Модели соединения ассоциаций и Настройка обратных связей.

:disable_joins

Определяет, следует ли пропускать соединения для связи. Если задано значение true, будут сформированы два или более запроса. Обратите внимание: в некоторых случаях сортировка или ограничение количества результатов будут применены в памяти из-за ограничений базы данных. Этот параметр применим только к связям has_many :through, поскольку сама по себе has_many не выполняет соединение.

:source

Задаёт имя исходной связи, используемое в запросах has_many :through. Используйте этот параметр, только если имя нельзя определить по связи. has_many :subscribers, through: :subscriptions будет искать на Subscription либо :subscribers, либо :subscriber, если не задан параметр :source.

:source_type

Задаёт тип исходной связи, используемый в запросах has_many :through, в которых исходная связь является полиморфной belongs_to.

:validate

Если задано значение true, новые объекты, добавленные в связь, проверяются при сохранении родительского объекта. По умолчанию — true. Чтобы связанные объекты повторно проверялись при каждом обновлении, используйте validates_associated.

:autosave

Если значение равно true, при сохранении родительского объекта всегда сохраняются связанные объекты или уничтожаются объекты, помеченные для уничтожения. Если значение равно false, связанные объекты никогда не сохраняются и не уничтожаются. По умолчанию сохраняются только новые связанные записи. Этот параметр реализован как обратный вызов before_save. Поскольку обратные вызовы запускаются в порядке их определения, связанные объекты может потребоваться явно сохранить в пользовательских обратных вызовах before_save.

Обратите внимание: NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает для :autosave значение true.

:inverse_of

Задаёт имя связи belongs_to в связанном объекте, обратной для этой связи has_many. Подробнее см. в разделе Двунаправленные связи.

:extend

Задаёт модуль или массив модулей, которые будут подключены к возвращаемому объекту связи. Это полезно для определения методов связей, особенно если их нужно использовать в нескольких объектах связи.

:strict_loading

Если задано значение true, при каждой загрузке связанной записи через эту связь применяется строгая загрузка.

:ensuring_owner_was

Задаёт метод экземпляра, вызываемый у владельца. Метод должен возвращать true, чтобы связанные записи могли быть удалены в фоновой задаче.

:query_constraints

Используется как составной внешний ключ. Определяет список столбцов, используемых для запроса связанного объекта. Этот параметр необязателен. По умолчанию Rails попытается определить значение автоматически. Если значение задано, размер Array должен совпадать с размером первичного ключа связанной модели или query_constraints.

:index_errors

Позволяет различать несколько ошибок проверки связанных записей, добавляя индекс в имя атрибута ошибки, например roles[2].level. Если задано значение true, индекс определяется порядком связи, то есть порядком в базе данных; новые несохранённые записи размещаются в конце. Если задано значение :nested_attributes_order, индекс определяется порядком записей, полученных сеттером вложенных атрибутов при использовании accepts_nested_attributes_for.

:before_add

Определяет обратный вызов связи, который срабатывает перед добавлением объекта в коллекцию связи.

:after_add

Определяет обратный вызов связи, который срабатывает после добавления объекта в коллекцию связи.

:before_remove

Определяет обратный вызов связи, который срабатывает перед удалением объекта из коллекции связи.

:after_remove

Определяет обратный вызов связи, который срабатывает после удаления объекта из коллекции связи.

:deprecated

Если значение равно true, помечает связь как устаревшую. Использование устаревших связей регистрируется. Подробнее см. в документации класса выше.

Примеры параметров:

has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
has_many :comments, dependent: :nullify
has_many :tags, as: :taggable
has_many :reports, -> { readonly }
has_many :subscribers, through: :subscriptions, source: :user
has_many :subscribers, through: :subscriptions, disable_joins: true
has_many :comments, strict_loading: true
has_many :comments, query_constraints: [:blog_id, :post_id]
has_many :comments, index_errors: :nested_attributes_order
has_one (name, scope = nil, **options) Показать исходный код
# File activerecord/lib/active_record/associations.rb, line 1628
def has_one(name, scope = nil, **options)
  reflection = Builder::HasOne.build(self, name, scope, options)
  Reflection.add_reflection(self, name, reflection)
end

Определяет связь «один к одному» с другим классом. Этот метод следует использовать, только если внешний ключ находится в другом классе. Если внешний ключ находится в текущем классе, вместо этого используйте belongs_to. Подробнее о том, когда использовать has_one, а когда belongs_to, см. в разделе Это связь belongs_to или has_one?.

Будут добавлены следующие методы для получения и запроса одного связанного объекта:

association — это заполнитель для символа, переданного в качестве аргумента name, поэтому has_one :manager добавит, помимо прочего, manager.nil?.

association

Возвращает связанный объект. Если ничего не найдено, возвращается nil.

association=(associate)

Присваивает связанный объект, извлекает первичный ключ, устанавливает его в качестве внешнего ключа и сохраняет связанный объект. Чтобы избежать несогласованности данных в базе, при присваивании нового объекта навсегда удаляется существующий связанный объект, даже если новый объект не сохранён в базе данных.

build_association(attributes = {})

Возвращает новый объект связанного типа, созданный с помощью attributes и связанный с этим объектом через внешний ключ, но ещё не сохранённый.

create_association(attributes = {})

Возвращает новый объект связанного типа, созданный с помощью attributes, связанный с этим объектом через внешний ключ и уже сохранённый (если он прошёл проверку).

create_association!(attributes = {})

Выполняет то же, что и create_association, но вызывает исключение ActiveRecord::RecordInvalid, если запись недействительна.

reload_association

Возвращает связанный объект, принудительно выполняя чтение из базы данных.

reset_association

Выгружает связанный объект. При следующем обращении он будет запрошен из базы данных.

Пример

class Account < ActiveRecord::Base
  has_one :beneficiary
end

Объявление has_one :beneficiary добавляет следующие методы (и другие):

account = Account.find(5)
beneficiary = Beneficiary.find(8)

account.beneficiary               # similar to Beneficiary.find_by(account_id: 5)
account.beneficiary = beneficiary # similar to beneficiary.update(account_id: 5)
account.build_beneficiary         # similar to Beneficiary.new(account_id: 5)
account.create_beneficiary        # similar to Beneficiary.create(account_id: 5)
account.create_beneficiary!       # similar to Beneficiary.create!(account_id: 5)
account.reload_beneficiary
account.reset_beneficiary

Области видимости

Второй аргумент scope можно передать как вызываемый объект (например, proc или lambda), чтобы получать определённую запись или настраивать создаваемый запрос при обращении к связанному объекту.

Примеры областей видимости:

has_one :author, -> { where(comment_id: 1) }
has_one :employer, -> { joins(:company) }
has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }

Параметры

В объявление также можно включить хеш options, чтобы настроить поведение связи.

Параметры:

:class_name

Задаёт имя класса связи. Используйте этот параметр, только если имя нельзя определить по имени связи. Например, has_one :manager по умолчанию будет связано с классом Manager, но если фактическое имя класса — Person, его нужно указать с помощью этого параметра.

:dependent

Определяет, что происходит со связанным объектом при уничтожении его владельца:

  • nil ничего не делает (по умолчанию).

  • :destroy также уничтожает связанный объект.

  • :destroy_async уничтожает связанный объект в фоновой задаче. ПРЕДУПРЕЖДЕНИЕ: Не используйте этот параметр, если связь обеспечивается ограничениями внешнего ключа в базе данных. Действия ограничений внешнего ключа будут выполнены в той же транзакции, что и удаление владельца.

  • :delete напрямую удаляет связанный объект из базы данных (обратные вызовы не выполняются).

  • :nullify устанавливает для внешнего ключа значение NULL. Для полиморфных связей также обнуляется столбец типа полиморфной связи. Callbacks не выполняются.

  • :restrict_with_exception вызывает исключение ActiveRecord::DeleteRestrictionError, если существует связанная запись.

  • :restrict_with_error добавляет ошибку владельцу, если существует связанный объект.

Обратите внимание: параметр :dependent игнорируется при использовании параметра :through.

:foreign_key

Задаёт внешний ключ, используемый для связи. По умолчанию предполагается, что это имя данного класса в нижнем регистре с суффиксом «_id». Поэтому класс Person, в котором определена связь has_one, по умолчанию будет использовать «person_id» в качестве :foreign_key.

Задание параметра :foreign_key отключает автоматическое определение обратной связи, поэтому обычно рекомендуется также задавать параметр :inverse_of.

:foreign_type

Задаёт столбец для хранения типа связанного объекта, если связь полиморфная. По умолчанию предполагается, что это имя полиморфной связи, указанной в параметре «as», с суффиксом «_type». Поэтому класс, в котором определена связь has_one :tag, as: :taggable, по умолчанию будет использовать «taggable_type» в качестве :foreign_type.

:primary_key

Задаёт метод, возвращающий первичный ключ, используемый для связи. По умолчанию это id.

:as

Задаёт полиморфный интерфейс (см. belongs_to).

:through

Задаёт связь, через которую выполняется запрос.

Связь through должна быть has_one, has_one :through или неполиморфной belongs_to. Иными словами, это должна быть неполиморфная одиночная связь. Параметры :class_name, :primary_key и :foreign_key игнорируются, поскольку связь использует рефлексию исходной связи. Запрос :through можно выполнить только через связь has_one или belongs_to в модели соединения.

Если связь в модели соединения — это belongs_to, коллекцию можно изменять, а записи в модели :through будут автоматически создаваться и удаляться при необходимости. В противном случае коллекция доступна только для чтения, поэтому следует напрямую изменять связь :through.

Если вы собираетесь изменять связь, а не только читать её, рекомендуется задать параметр :inverse_of для исходной связи в модели соединения. Это позволяет создавать связанные записи, которые при сохранении автоматически создадут соответствующие записи модели соединения. Подробнее см. в разделах Модели соединения ассоциаций и Настройка обратных связей.

:disable_joins

Определяет, следует ли пропускать соединения для связи. Если задано значение true, будут сформированы два или более запроса. Обратите внимание: в некоторых случаях сортировка или ограничение количества результатов будут применены в памяти из-за ограничений базы данных. Этот параметр применим только к связям has_one :through, поскольку сама по себе has_one не выполняет соединение.

:source

Задаёт имя исходной связи, используемое в запросах has_one :through. Используйте этот параметр, только если имя нельзя определить по связи. has_one :favorite, through: :favorites будет искать на Favorite связь :favorite, если не задан параметр :source.

:source_type

Задаёт тип исходной связи, используемый в запросах has_one :through, в которых исходная связь является полиморфной belongs_to.

:validate

Если задано значение true, новые объекты, добавленные в связь, проверяются при сохранении родительского объекта. По умолчанию — false. Чтобы связанные объекты повторно проверялись при каждом обновлении, используйте validates_associated.

:autosave

Если true, при сохранении родительского объекта связанный объект всегда сохраняется или уничтожается, если он помечен для уничтожения. Если false, связанный объект никогда не сохраняется и не уничтожается.

По умолчанию связанный объект сохраняется, только если это новая запись. Если установить для этого параметра значение true, также включаются проверки связанного объекта, если они явно не отключены с помощью validate: false. Это необходимо, потому что сохранение объекта с недействительными связанными объектами завершится ошибкой, поэтому все связанные объекты проходят проверку.

Обратите внимание: NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает для :autosave значение true.

:touch

Если значение равно true, при сохранении или уничтожении этой записи обновляется связанный объект (для атрибутов updated_at / updated_on устанавливается текущее время). Если указать символ, помимо атрибута updated_at / updated_on будет обновлён и этот атрибут, которому присвоится текущее время. Обратите внимание: при обновлении временной метки проверка не выполняется; запускаются только обратные вызовы after_touch, after_commit и after_rollback.

:inverse_of

Задаёт имя связи belongs_to в связанном объекте, обратной для этой связи has_one. Подробнее см. в разделе Двунаправленные связи.

:required

Если задано значение true, также проверяется наличие связи. Проверяется сама связь, а не идентификатор. Можно использовать :inverse_of, чтобы избежать дополнительного запроса во время проверки.

:strict_loading

При каждой загрузке связанной записи через эту связь применяется строгая загрузка.

:ensuring_owner_was

Задаёт метод экземпляра, вызываемый у владельца. Метод должен возвращать true, чтобы связанные записи могли быть удалены в фоновой задаче.

:query_constraints

Используется как составной внешний ключ. Определяет список столбцов, используемых для запроса связанного объекта. Этот параметр необязателен. По умолчанию Rails попытается определить значение автоматически. Если значение задано, размер Array должен совпадать с размером первичного ключа связанной модели или query_constraints.

:deprecated

Если значение равно true, помечает связь как устаревшую. Использование устаревших связей регистрируется. Подробнее см. в документации класса выше.

Примеры параметров:

has_one :credit_card, dependent: :destroy  # destroys the associated credit card
has_one :credit_card, dependent: :nullify  # updates the associated records foreign
                                              # key value to NULL rather than destroying it
has_one :last_comment, -> { order('posted_on desc') }, class_name: "Comment"
has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person"
has_one :attachment, as: :attachable
has_one :boss, -> { readonly }
has_one :club, through: :membership
has_one :club, through: :membership, disable_joins: true
has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
has_one :credit_card, required: true
has_one :credit_card, strict_loading: true
has_one :employment_record_book, query_constraints: [:organization_id, :employee_id]

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API