модуль ActiveRecord::Associations::ClassMethods
Ассоциации — это набор макроподобных методов класса для связывания объектов через внешние ключи. Они выражают отношения, такие как «Проект имеет одного менеджера проекта» или «Проект принадлежит портфелю». Каждый макрос добавляет ряд методов в класс, которые специализируются в соответствии с коллекцией или символом ассоциации и хэшем опций. Он работает примерно так же, как собственные методы Ruby attr*.
class Project < ActiveRecord::Base belongs_to :portfolio has_one :project_manager has_many :milestones has_and_belongs_to_many :categories end
Класс проекта теперь имеет следующие методы (и больше), чтобы облегчить навигацию и управление его отношениями:
-
Project#portfolio,Project#portfolio=(portfolio),Project#reload_portfolio -
Project#project_manager,Project#project_manager=(project_manager),Project#reload_project_manager -
Project#milestones.empty?,Project#milestones.size,Project#milestones,Project#milestones<<(milestone),Project#milestones.delete(milestone),Project#milestones.destroy(milestone),Project#milestones.find(milestone_id),Project#milestones.build,Project#milestones.create -
Project#categories.empty?,Project#categories.size,Project#categories,Project#categories<<(category1),Project#categories.delete(category1),Project#categories.destroy(category1)
Предупреждение
Не создавайте ассоциаций с теми же именами, что и методы экземпляра класса ActiveRecord::Base. Поскольку ассоциация добавляет метод с таким же именем в свою модель, использование ассоциации с таким же именем, что и метод, предоставляемый ActiveRecord::Base, переопределит метод, унаследованный через ActiveRecord::Base, и нарушит работу. Например, attributes и connection были бы плохим выбором имён ассоциаций, потому что эти имена уже существуют в списке ActiveRecord::Base методов экземпляра.
Автоматически генерируемые методы
См. также раздел Публичные методы экземпляра ниже для получения дополнительной информации.
Одноэлементные ассоциации (один к одному)
| | 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 Коллекционные ассоциации (один ко многим / многие ко многим)
| | | 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, когда работаете со схемами legacy или когда никогда не работаете напрямую с самим отношением.
Это ассоциация belongs_to или has_one?
Оба выражают отношение 1-1. Разница в основном в том, где разместить внешний ключ, который размещается в таблице для класса, объявляющего отношение 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) )
Несохранённые объекты и ассоциации
Вы можете управлять объектами и ассоциациями до их сохранения в базе данных, но есть некоторые особые поведения, о которых следует знать, особенно связанные с сохранением связанных объектов.
Вы можете установить опцию :autosave на ассоциации has_one, belongs_to, has_many или has_and_belongs_to_many. Установка её в значение 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
Примечание: Объединение, жадное загрузка и предварительная загрузка этих ассоциаций невозможны. Эти операции происходят до создания экземпляра, и область будет вызвана с аргументом nil.
Обработчики событий ассоциаций
Аналогично обычным обработчикам событий, которые подключаются к жизненному циклу объекта Active Record, вы также можете определять обработчики событий, которые срабатывают при добавлении или удалении объекта из коллекции ассоциаций.
class Project
has_and_belongs_to_many :developers, after_add: :evaluate_velocity
def evaluate_velocity(developer)
...
end
end Можно использовать обработчики в массиве. Пример:
class Project
has_and_belongs_to_many :developers,
after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
end
Возможные обработчики: before_add, after_add, before_remove и after_remove.
Если любой из обработчиков before_add выбросит исключение, объект не будет добавлен в коллекцию.
Аналогично, если любой из обработчиков before_remove выбросит исключение, объект не будет удалён из коллекции.
Расширения ассоциаций
Объекты-прокси, которые управляют доступом к ассоциациям, можно расширить с помощью анонимных модулей. Это особенно полезно для добавления новых поисковых запросов, создателей и других методов типа фабрики, которые используются только в рамках этой ассоциации.
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 для ассоциаций, или кастомный scope, также помешают автоматически обнаружить обратную связь ассоциации.
Автоматическое определение обратной связи ассоциации использует эвристику, основанную на имени класса, поэтому она может не работать со всеми ассоциациями, особенно с именами нестандартного вида.
Вы можете отключить автоматическое обнаружение обратных связей ассоциаций, установив опцию :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, предположим, что гостевые записи и записи пользователей, использующие таблицу постов для STI. В этом случае в таблице постов должен быть столбец type.
Примечание: Метод attachable_type= вызывается при назначении attachable. Тип attachable передаётся как 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. В целом количество запросов будет равно 1 плюс количество названных ассоциаций (за исключением случаев, когда некоторые ассоциации являются полиморфными 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.
Удаление из ассоциаций
Зависимые ассоциации
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')), вы хотите, чтобы тег «еда» был разъединен с постом, а не чтобы сам тег был удален из базы данных.
Однако есть примеры, где эта стратегия не имеет смысла. Например, предположим, что у человека есть много проектов, и каждый проект имеет много задач. Если мы удалили одну из задач человека, мы, вероятно, не захотим удалять проект. В этом случае метод `delete` фактически не сработает: он может быть использован только если ассоциация на модели соединения является belongs_to. В других ситуациях ожидается, что вы будете выполнять операции напрямую либо с связанными записями, либо с ассоциацией :through.
В случае обычной has_many нет различия между «связанными записями» и «связью», поэтому есть только один вариант того, что удаляется.
С has_and_belongs_to_many и has_many :through, если вы хотите удалить сами связанные записи, вы всегда можете сделать что-то вроде person.tasks.each(&:destroy).
Безопасность типов с ActiveRecord::AssociationTypeMismatch
Если вы попытаетесь назначить объект ассоциации, который не соответствует заданному или выведенному типу :class_name, вы получите ActiveRecord::AssociationTypeMismatch.
Параметры
Все макросы ассоциаций можно уточнить с помощью параметров. Это делает случаи более сложными, чем простые и предсказуемые.
Методы публичного экземпляра
# File activerecord/lib/active_record/associations.rb, line 1761 def belongs_to(name, scope = nil, **options) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end
Устанавливает одно-к-одному ассоциацию с другим классом. Этот метод следует использовать только в том случае, если этот класс содержит внешний ключ. Если внешний ключ содержится в другом классе, следует использовать has_one вместо него. Также см. обзор ActiveRecord::Associations::ClassMethods по тому, когда использовать has_one, а когда использовать belongs_to.
Будут добавлены методы для получения и запроса одного связанного объекта, для которого этот объект содержит идентификатор:
association — это заполнитель для символа, переданного в качестве аргумента name, поэтому belongs_to :author добавит, среди прочего, author.nil?.
- association
-
Возвращает связанный объект.
nilвозвращается, если объект не найден. - association=(associate)
-
Присваивает объект associate, извлекает первичный ключ и устанавливает его как внешний ключ. Никаких модификаций или удалений существующих записей не происходит.
- build_association(attributes = {})
-
Возвращает новый объект связанного типа, который был создан с
attributesи связан с этим объектом через внешний ключ, но еще не сохранен. - create_association(attributes = {})
-
Возвращает новый объект связанного типа, который был создан с
attributes, связан с этим объектом через внешний ключ и уже сохранен (если он прошел проверку). - create_association!(attributes = {})
-
Делает то же самое, что и
create_association, но вызываетActiveRecord::RecordInvalid, если запись не соответствует требованиям. - reload_association
-
Возвращает связанный объект, принудительно выполнив чтение из базы данных.
Пример
Класс Post объявляет belongs_to :author, что добавит:
-
Post#author(аналогичноAuthor.find(author_id)) -
Post#author=(author)(аналогичноpost.author_id = author.id) -
Post#build_author(аналогичноpost.author = Author.new) -
Post#create_author(аналогичноpost.author = Author.new; post.author.save; post.author) -
Post#create_author!(аналогичноpost.author = Author.new; post.author.save!; post.author) -
Post#reload_author
Объявление также может включать хэш options, чтобы специализировать поведение ассоциации.
Области
Вы можете передать второй аргумент scope в качестве вызываемого объекта (т.е. proc или lambda), чтобы получить определенную запись или настроить сгенерированный запрос при доступе к связанному объекту.
Примеры областей:
belongs_to :firm, -> { where(id: 2) }
belongs_to :user, -> { joins(:friends) }
belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
Параметры
- :class_name
-
Укажите имя класса ассоциации. Используйте его только в том случае, если это имя нельзя вывести из имени ассоциации. Так,
belongs_to :authorпо умолчанию будет связан с классом Author, но если реальное имя класса — Person, вам нужно будет указать его с этим параметром. - :foreign_key
-
Укажите внешний ключ, используемый для ассоциации. По умолчанию он предполагается именем ассоциации с суффиксом «_id». Таким образом, класс, определяющий ассоциацию
belongs_to :person, будет использовать «person_id» в качестве значения по умолчанию:foreign_key. Аналогично,belongs_to :favorite_person, class_name: "Person"будет использовать внешний ключ «favorite_person_id».Если вы собираетесь изменить ассоциацию (а не только читать из нее), рекомендуется установить параметр
: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
-
Кэширует количество принадлежащих объектов в классе associate с помощью
CounterCache::ClassMethods#increment_counterиCounterCache::ClassMethods#decrement_counter. Кэшированный счетчик увеличивается при создании объекта этого класса и уменьшается при его уничтожении. Это требует, чтобы столбец с именем#{table_name}_count(например,comments_countдля принадлежащего класса Comment) использовался в классе associate (например, классе Post) — то есть миграция для#{table_name}_countсоздается в классе associate (так чтоPost.comments_countвернет сохраненный счет, см. примечание ниже). Вы также можете указать пользовательский столбец кэша счетчика, предоставив имя столбца вместо значенияtrue/falseдля этого параметра (например,counter_cache: :my_custom_counter). Примечание: Указание кэша счетчика добавит его в список только для чтения атрибутов этой модели с помощьюattr_readonly. - :polymorphic
-
Укажите, что эта ассоциация является полиморфной, передав
true. Примечание: Если вы включили кэш счетчика, возможно, вам следует добавить атрибут кэша счетчика в списокattr_readonlyв связанных классах (например,class Post; attr_readonly :comments_count; end). - :validate
-
При установке значения
true, новые объекты, добавленные к ассоциации, проверяются при сохранении родительского объекта. По умолчаниюfalse. Если вы хотите убедиться, что связанные объекты перепроверяются при каждом обновлении, используйтеvalidates_associated. - :autosave
-
Если значение true, связанный объект всегда сохраняется или уничтожается, если он помечен на уничтожение, при сохранении родительского объекта. Если значение false, связанный объект никогда не сохраняется и не уничтожается. По умолчанию сохраняется только связанный объект, если это новая запись.
Обратите внимание, что
NestedAttributes::ClassMethods#accepts_nested_attributes_forустанавливает:autosaveвtrue. - :touch
-
Если значение true, связанный объект будет касаться (атрибуты updated_at/on будут установлены на текущее время) при сохранении или уничтожении этой записи. Если вы укажете символ, этот атрибут будет обновлен текущим временем в дополнение к атрибуту updated_at/on. Обратите внимание, что при касании проверка не выполняется и вызываются только обратные вызовы
after_touch,after_commitиafter_rollback. - :inverse_of
-
Указывает имя ассоциации
has_oneилиhas_manyв связанном объекте, которая является обратной для этой ассоциацииbelongs_to. См. обзор двусторонних ассоциаций в ActiveRecord::Associations::ClassMethods для получения дополнительной информации. - :optional
-
Если установлено значение
true, проверка наличия ассоциации не будет выполняться. - :required
-
Если установлено значение
true, проверка наличия ассоциации также будет выполняться. Это проверит саму ассоциацию, а не идентификатор. Вы можете использовать:inverse_ofдля избежания дополнительного запроса во время проверки. ПРИМЕЧАНИЕ:requiredустановлено по умолчанию вtrueи устарело. Если вы не хотите проверять наличие ассоциации, используйтеoptional: true. - :default
-
Укажите вызываемый объект (например, proc или lambda) для указания того, что ассоциация должна быть инициализирована определенной записью перед проверкой.
- :strict_loading
-
Принудительно загружает каждый раз, когда связанная запись загружается через эту ассоциацию.
- :ensuring_owner_was
-
Указывает метод экземпляра, который должен быть вызван у владельца. Метод должен возвращать 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
# File activerecord/lib/active_record/associations.rb, line 1933
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].each do |k|
hm_options[k] = options[k] if options.key? k
end
has_many name, scope, **hm_options, &extension
_reflections[name.to_s].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[6.0]
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.
Пример
Класс Developer объявляет has_and_belongs_to_many :projects, что добавит:
-
Developer#projects -
Developer#projects<< -
Developer#projects.delete -
Developer#projects.destroy -
Developer#projects= -
Developer#project_ids -
Developer#project_ids= -
Developer#projects.clear -
Developer#projects.empty? -
Developer#projects.size -
Developer#projects.find(id) -
Developer#projects.exists?(...) -
Developer#projects.build(аналогичноProject.new(developer_id: id)) -
Developer#projects.create(аналогичноc = Project.new(developer_id: id); c.save; c) -
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.Если вы собираетесь модифицировать ассоциацию (а не только считывать из неё), рекомендуется установить параметр
: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
-
Принудительно загружает связанные записи каждый раз, когда они загружаются через эту ассоциацию.
Примеры параметров:
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
# File activerecord/lib/active_record/associations.rb, line 1457 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 без ожидания вызова сохранения или обновления родительского объекта, если родительский объект не является новым записями. Это также выполнит валидацию и обратные вызовы связанного(ых) объекта(ов).
- collection.delete(object, …)
-
Удаляет один или несколько объектов из коллекции, установив их внешние ключи на
NULL. Объекты также будут уничтожены, если они связаны сdependent: :destroy, и удалены, если они связаны сdependent: :delete_all.Если используется опция
:through, то записи соединения удаляются (а не обнуляются) по умолчанию, но вы можете указатьdependent: :destroyилиdependent: :nullifyдля переопределения этого. - collection.destroy(object, …)
-
Удаляет один или несколько объектов из коллекции, выполняя
destroyдля каждой записи, независимо от опции зависимых объектов, гарантируя, что будут выполнены обратные вызовы.Если используется опция
:through, то записи соединения уничтожаются, а не сами объекты. - collection=objects
-
Заменяет содержимое коллекции, удаляя и добавляя объекты по мере необходимости. Если опция
:throughравна true, обратные вызовы в моделях соединения вызываются, за исключением обратных вызовов удаления, так как удаление по умолчанию происходит напрямую. Вы можете указатьdependent: :destroyилиdependent: :nullifyдля переопределения этого. - collection_singular_ids
-
Возвращает массив идентификаторов связанных объектов.
- collection_singular_ids=ids
-
Заменяет коллекцию объектами, идентифицируемыми первичными ключами в
ids. Этот метод загружает модели и вызываетcollection=. См. выше. - collection.clear
-
Удаляет все объекты из коллекции. Это уничтожает связанные объекты, если они связаны с
dependent: :destroy, удаляет их напрямую из базы данных, еслиdependent: :delete_all, в противном случае устанавливает их внешние ключи наNULL. Если опция:throughравна true, обратные вызовы уничтожения для моделей соединения не вызываются. Модели соединения удаляются непосредственно. - 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.
Пример
Класс Firm объявляет has_many :clients, что добавит:
-
Firm#clients(аналогичноClient.where(firm_id: id)) -
Firm#clients<< -
Firm#clients.delete -
Firm#clients.destroy -
Firm#clients= -
Firm#client_ids -
Firm#client_ids= -
Firm#clients.clear -
Firm#clients.empty?(аналогичноfirm.clients.size == 0) -
Firm#clients.size(аналогичноClient.count "firm_id = #{id}") -
Firm#clients.find(аналогичноClient.where(firm_id: id).find(id)) -
Firm#clients.exists?(name: 'ACME')(аналогичноClient.exists?(name: 'ACME', firm_id: firm.id)) -
Firm#clients.build(аналогичноClient.new(firm_id: id)) -
Firm#clients.create(аналогичноc = Client.new(firm_id: id); c.save; c) -
Firm#clients.create!(аналогичноc = Client.new(firm_id: id); c.save!) -
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.Если вы собираетесь изменить ассоциацию (а не только читать из неё), рекомендуется установить параметр
: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на исходной ассоциации на модели соединения. Это позволяет создавать связанные записи, которые автоматически создадут соответствующие записи модели соединения при сохранении. (См. раздел «Модели соединения ассоциаций» выше.) - :source
-
Указывает имя ассоциации источника, используемой в запросах
has_many:through. Используйте только в случае, если имя не может быть выведено из ассоциации.has_many :subscribers, through: :subscriptionsбудет искать либо:subscribers, либо:subscriberв Subscription, если не указан параметр: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. См. обзор двунаправленных ассоциаций в ActiveRecord::Associations::ClassMethods для получения дополнительной информации. - :extend
-
Указывает модуль или массив модулей, которые будут расширять возвращаемый объект ассоциации. Полезно для определения методов для ассоциаций, особенно когда они должны быть общими для нескольких объектов ассоциаций.
- :strict_loading
-
Принудительно загружает связанную запись каждый раз, когда она загружается через эту ассоциацию.
- :ensuring_owner_was
-
Указывает метод экземпляра, который будет вызываться у владельца. Метод должен возвращать 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 :comments, strict_loading: true
# File activerecord/lib/active_record/associations.rb, line 1607 def has_one(name, scope = nil, **options) reflection = Builder::HasOne.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end
Указывает ассоциацию один-к-одному с другим классом. Этот метод следует использовать только в том случае, если другой класс содержит внешний ключ. Если внешний ключ содержится в текущем классе, то следует использовать belongs_to вместо него. Также см. обзор ActiveRecord::Associations::ClassMethods о том, когда использовать has_one, а когда — belongs_to.
Будут добавлены следующие методы для извлечения и запроса единственного связанного объекта:
association — это плейсхолдер для символа, переданного в качестве аргумента name, поэтому has_one :manager, помимо прочего, добавит manager.nil?.
- association
-
Возвращает связанный объект. Если объект не найден, возвращается
nil. - association=(associate)
-
Присваивает объект associate, извлекает первичный ключ, устанавливает его в качестве внешнего ключа и сохраняет объект associate. Для избежания несоответствий в базе данных присваивание нового объекта постоянно удаляет существующий связанный объект, даже если новый объект не сохранён в базе данных.
- build_association(attributes = {})
-
Возвращает новый объект связанного типа, который был инициализирован с
attributesи связан с этим объектом через внешний ключ, но ещё не сохранён. - create_association(attributes = {})
-
Возвращает новый объект связанного типа, который был инициализирован с
attributes, связан с этим объектом через внешний ключ и уже сохранён (если он прошёл валидацию). - create_association!(attributes = {})
-
Делает то же, что и
create_association, но поднимаетActiveRecord::RecordInvalid, если запись невалидна. - reload_association
-
Возвращает связанный объект, принудительно выполняя чтение из базы данных.
Пример
Класс Account объявляет has_one :beneficiary, что добавит:
-
Account#beneficiary(аналогичноBeneficiary.where(account_id: id).first) -
Account#beneficiary=(beneficiary)(аналогичноbeneficiary.account_id = account.id; beneficiary.save) -
Account#build_beneficiary(аналогичноBeneficiary.new(account_id: id)) -
Account#create_beneficiary(аналогичноb = Beneficiary.new(account_id: id); b.save; b) -
Account#create_beneficiary!(аналогичноb = Beneficiary.new(account_id: id); b.save!; b) -
Account#reload_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.Если вы собираетесь изменить ассоциацию (а не только читать из неё), рекомендуется установить параметр
:inverse_of. - :foreign_type
-
Укажите столбец, используемый для хранения типа связанного объекта, если это полиморфная ассоциация. По умолчанию это имя полиморфной ассоциации, указанной в параметре «as», с добавленным суффиксом «_type». Так, класс, определяющий ассоциацию
has_one :tag, as: :taggableбудет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. - :primary_key
-
Укажите метод, возвращающий первичный ключ, используемый для ассоциации. По умолчанию это
id. - :as
-
Определяет полиморфный интерфейс (см.
belongs_to). - :through
-
Указывает модель соединения, через которую выполняется запрос. Параметры для
:class_name,:primary_key, и:foreign_keyигнорируются, так как ассоциация использует отражение источника. Вы можете использовать только запрос:throughчерез ассоциациюhas_oneилиbelongs_toна модели соединения.Если вы собираетесь изменить ассоциацию (а не только читать из неё), рекомендуется установить параметр
:inverse_of. - :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, никогда не сохраняйте и не уничтожайте связанный объект. По умолчанию сохраняется только связанный объект, если это новая запись.
Обратите внимание, что
NestedAttributes::ClassMethods#accepts_nested_attributes_forустанавливает:autosaveвtrue. - :inverse_of
-
Указывает имя ассоциации
belongs_toв связанном объекте, являющейся обратной этой ассоциацииhas_one. Для получения более подробной информации см. обзор двунаправленных ассоциаций в ActiveRecord::Associations::ClassMethods. - :required
-
При установке в
true, к ассоциации также будет применена валидация наличия. Это валидирует саму ассоциацию, а не ID. Вы можете использовать:inverse_ofдля предотвращения дополнительных запросов во время валидации. - :strict_loading
-
Принудительно выполняет строгое загрузка каждый раз, когда связанная запись загружается через эту ассоциацию.
- :ensuring_owner_was
-
Указывает метод экземпляра, который будет вызван у владельца. Метод должен вернуть 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') }, 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 :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
has_one :credit_card, required: true
has_one :credit_card, strict_loading: true
© 2004–2020 David Heinemeier Hansson
Licensed under the MIT License.