модуль ActiveRecord::Associations::ClassMethods
Ассоциации — это набор макроподобных методов класса для связывания объектов через внешние ключи. Они выражают отношения, такие как «Проект имеет одного менеджера проекта» или «Проект принадлежит портфелю». Каждый макрос добавляет ряд методов в класс, которые специализируются в зависимости от коллекции или символа ассоциации и хэша опций. Он работает почти так же, как собственные методы Ruby.
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#portfolio.nil? -
Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?, -
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
Модуль методов ассоциации включается сразу после модуля методов сгенерированных атрибутов, что означает, что ассоциация переопределит методы атрибута с таким же именем.
Мощность и ассоциации
Активные рекорды ассоциации могут быть использованы для описания отношений «один к одному», «один ко многим» и «многие ко многим» между моделями. Каждая модель использует ассоциацию для описания своей роли в отношении. Ассоциация 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 int NOT NULL auto_increment, account_id int default NULL, name varchar default NULL, PRIMARY KEY (id) ) CREATE TABLE accounts ( id int 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 внутри расширений ассоциаций.
Ассоциации с присоединяемыми моделями
Ассоциации «многие ко многим» могут быть настроены с опцией :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.
Дополнительные опции для ассоциаций, определённые в константе AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS, также помешают автоматически находить обратную ссылку ассоциации.
Автоматическое определение обратной ссылки использует эвристику, основанную на имени класса, поэтому она может не работать для всех ассоциаций, особенно для тех, у которых нестандартные имена.
Вы можете отключить автоматическое обнаружение обратных ссылок, установив опцию :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
Это работает с помощью столбца типа в дополнение к внешнему ключу для указания связанной записи. В примере с активами вам понадобится столбец целого типа attachable_id и столбец строки типа attachable_type.
Использование полиморфных ассоциаций в сочетании с наследованием по типу одной таблицы (STI) немного сложно. Чтобы ассоциации работали как ожидается, убедитесь, что вы храните базовый тип модели для моделей STI в столбце типа полиморфной ассоциации. Чтобы продолжить пример с активами, предположим, что есть гостевые и пользовательские записи, которые используют таблицу записей для STI. В этом случае в таблице записей должен быть столбец type.
Примечание: Метод attachable_type= вызывается при назначении attachable. Тип attachable передаётся как строка.
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(true).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
Вызов, пытающийся ленично загрузить модель адреса,
Address.includes(:addressable)
Это выполнит один запрос для загрузки адресов и загрузит адресные данные с одним запросом на каждый тип адресных данных. Например, если все адресные данные являются либо класса Person, либо Company, тогда всего будет выполнено 3 запроса. Список типов адресных данных для загрузки определяется на основе загруженных адресов. Это не поддерживается, если 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 Таблицы соединения «многие ко многим» используют ту же идею, но добавляют суффикс _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:
-
не работает со связями
:through. -
не работает со связями
:polymorphic. -
обратные связи для связей belongs_to и has_many игнорируются.
Для получения дополнительной информации см. документацию по параметру :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')), вы захотите разорвать связь тега «еда» с публикацией, а не удалить сам тег из базы данных.
Однако есть примеры, где эта стратегия не имеет смысла. Например, предположим, что у человека есть много проектов, а у каждого проекта — много задач. Если мы удалили одну из задач человека, мы, вероятно, не захотели бы удалять сам проект. В этом случае метод удаления не сработает: он может использоваться только в том случае, если связь в модели соединения является 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 1679
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». - :foreign_type
-
Укажите столбец, используемый для хранения типа связанного объекта, если это полиморфная ассоциация. По умолчанию он предполагается по имени ассоциации с суффиксом «_type». Так, класс, который определяет ассоциацию
belongs_to :taggable, polymorphic: true, будет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. - :primary_key
-
Укажите метод, возвращающий первичный ключ связанного объекта, используемого для ассоциации. По умолчанию это id.
- :dependent
-
Если установлено в
:destroy, связанный объект уничтожается, когда этот объект уничтожается. Если установлено в:delete, связанный объект удаляется без вызова его метода destroy. Этот параметр не следует указывать, когда belongs_to используется совместно с has_many отношением в другом классе из-за потенциального оставления незакрытых записей. - :counter_cache
-
Кэширует количество принадлежащих объектов в классе associate с помощью ActiveRecord::CounterCache::ClassMethods#increment_counter и ActiveRecord::CounterCache::ClassMethods#decrement_counter. Кэш счётчика увеличивается при создании объекта этого класса и уменьшается при его удалении. Это требует, чтобы в классе associate использовался столбец с именем
#{table_name}_count(например,comments_countдля класса Comment) - то есть миграция для#{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, никогда не сохраняйте и не удаляйте связанный объект. По умолчанию сохраняется только связанный объект, если это новая запись.
Обратите внимание, что ActiveRecord::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. Не работает в сочетании с параметрами
:polymorphic. См. обзор ActiveRecord::Associations::ClassMethods по двусторонним ассоциациям для получения дополнительной информации. - :optional
-
При установке в
true, наличие ассоциации не будет проверяться. - :required
-
При установке в
true, ассоциация также будет проверяться на наличие. Это будет проверять саму ассоциацию, а не id. Вы можете использовать:inverse_ofдля избежания дополнительного запроса во время проверки. ПРИМЕЧАНИЕ:requiredпо умолчанию устанавливается вtrueи устарело. Если вы не хотите, чтобы наличие ассоциации проверялось, используйтеoptional: true. - :default
-
Укажите вызываемый объект (например, proc или lambda) для указания, что ассоциация должна быть инициализирована конкретной записью перед валидацией.
Примеры параметров:
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 }
# File activerecord/lib/active_record/associations.rb, line 1844
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 = ActiveSupport::Deprecation.silence { 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 " def destroy_associations
association(:#{middle_reflection.name}).delete_all(:delete_all)
association(:#{name}).reset
super
end
", __FILE__, __LINE__ + 1
}
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].each do |k|
hm_options[k] = options[k] if options.key? k
end
ActiveSupport::Deprecation.silence { 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[5.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. - :association_foreign_key
-
Укажите внешний ключ, используемый для ассоциации со стороны принимающего класса. По умолчанию он определяется по имени связанного класса в нижнем регистре с добавленным «_id». Таким образом, если класс Person создаёт ассоциацию has_and_belongs_to_many с классом Project, ассоциация будет использовать «project_id» в качестве значения по умолчанию
:association_foreign_key. - :validate
-
При установке в
true, новые объекты, добавленные в ассоциацию, проходят валидацию при сохранении родительского объекта. По умолчаниюtrue. Если вы хотите гарантировать повторную валидацию связанных объектов при каждом обновлении, используйтеvalidates_associated. - :autosave
-
Если true, всегда сохраняет связанные объекты или уничтожает их, если они помечены на уничтожение, при сохранении родительского объекта. Если false, никогда не сохраняет или не уничтожает связанные объекты. По умолчанию сохраняются только новые связанные объекты.
Обратите внимание, что ActiveRecord::NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает
:autosaveв значение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 }
# File activerecord/lib/active_record/associations.rb, line 1401
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(force_reload = false)
-
Возвращает 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 как вызываемую функцию (т.е. процедуру или лямбда-функцию) для получения определенного набора записей или для настройки сгенерированного запроса при доступе к связанной коллекции.
Примеры использования скопов:
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_type
-
Укажите столбец, используемый для хранения типа связанного объекта, если это полиморфная ассоциация. По умолчанию он предполагается по имени полиморфной ассоциации, указанной в параметре «as», с суффиксом «_type». Таким образом, класс, определяющий ассоциацию
has_many :tags, as: :taggable, будет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. - :primary_key
-
Укажите имя столбца, используемого в качестве первичного ключа для ассоциации. По умолчанию это
id. - :dependent
-
Управляет тем, что происходит со связанными объектами, когда их владелец уничтожается. Обратите внимание, что они реализованы как обратные вызовы, и Rails выполняет обратные вызовы в порядке. Следовательно, другие аналогичные обратные вызовы могут повлиять на поведение
:dependent, а поведение:dependentможет повлиять на другие обратные вызовы.-
:destroyприводит к уничтожению всех связанных объектов. -
:delete_allприводит к прямому удалению всех связанных объектов из базы данных (так что обратные вызовы не будут выполнены). -
:nullifyприводит к тому, что внешние ключи устанавливаются в значениеNULL. Обратные вызовы не выполняются. -
:restrict_with_exceptionприводит к возникновению исключения, если существуют какие-либо связанные записи. -
: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.Обратите внимание, что ActiveRecord::NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает
:autosaveв значениеtrue. - :inverse_of
-
Указывает имя ассоциации belongs_to в связанном объекте, обратной к этой ассоциации has_many. Не работает в сочетании с параметрами
:throughили:as. Подробнее см. обзор двунаправленных ассоциаций в ActiveRecord::Associations::ClassMethods. - :extend
-
Указывает модуль или массив модулей, которые будут расширены в возвращаемый объект ассоциации. Полезно для определения методов в ассоциациях, особенно когда они должны быть общими для нескольких объектов ассоциации.
Примеры параметров:
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
# File activerecord/lib/active_record/associations.rb, line 1535
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
-
Управляет тем, что происходит со связанным объектом, когда его владелец уничтожается:
-
:destroyприводит к уничтожению связанного объекта -
:deleteприводит к прямому удалению связанного объекта из базы данных (так что обратные вызовы не будут выполнены) -
:nullifyприводит к установке внешнего ключа вNULL. Обратные вызовы не выполняются. -
:restrict_with_exceptionприводит к возникновению исключения, если существует связанная запись -
:restrict_with_errorдобавляет ошибку к владельцу, если существует связанный объект
Обратите внимание, что параметр
:dependentигнорируется при использовании параметра:through. -
- :foreign_key
-
Укажите внешний ключ, используемый для ассоциации. По умолчанию он предполагается как имя этого класса в нижнем регистре и с суффиксом «_id». Таким образом, класс Person, который создаёт ассоциацию has_one, будет использовать «person_id» в качестве стандартного
:foreign_key. - :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 в модели соединения. - :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, никогда не сохраняет и не уничтожает связанный объект. По умолчанию сохраняет связанный объект только в случае новой записи.
Обратите внимание, что ActiveRecord::NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает
:autosaveвtrue. - :inverse_of
-
Указывает имя ассоциации belongs_to в связанном объекте, которая является обратной к этой ассоциации has_one. Не работает в сочетании с параметрами
:throughили:as. См. обзор ActiveRecord::Associations::ClassMethods по двунаправленным ассоциациям для получения дополнительной информации. - :required
-
Если установлено в
true, ассоциация также будет иметь проверку наличия. Это проверяет саму ассоциацию, а не ID. Вы можете использовать:inverse_ofдля избежания дополнительного запроса во время проверки.
Примеры параметров:
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
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.