модуль 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, когда работаете со схемами старого формата или когда вы никогда не работаете непосредственно с самим отношением.
Это ассоциация 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 внутри расширений ассоциаций.
Ассоциации с промежуточными моделями
Ассоциации «многие ко многим» могут быть настроены с помощью параметра :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. Тип class_name модели 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)
Это выполнит один запрос для загрузки адресов и загрузит addressables одним запросом на каждый тип addressable. Например, если все addressables являются либо типа 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 Таблицы «многие ко многим» используют ту же идею, но добавляют суффикс _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')), вы захотите, чтобы тег «еда» был отключён от публикации, а не чтобы сам тег был удалён из базы данных.
Однако есть примеры, когда эта стратегия не имеет смысла. Например, предположим, что у человека есть много проектов, а каждый проект имеет много задач. Если мы удалили одну из задач человека, мы, вероятно, не захотим удалять проект. В этой ситуации метод удаления фактически не сработает: он может быть использован только в том случае, если ассоциация в модели связи является 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 1657 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. Этот параметр не следует указывать, когда belongs_to используется совместно с has_many отношением в другом классе из-за возможности оставить orphaned records. - :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. См. обзор 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 1826
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].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[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 для каждой связи в таблице соединения, переопределяя любую опцию зависимостей. Объекты не уничтожаются.
- 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.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, никогда не сохраняет и не уничтожает связанные объекты. По умолчанию сохраняются только новые связанные объекты.
Обратите внимание, что 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 1370 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для каждой записи, независимо от любой опции dependent, гарантируя, что обратные вызовы будут выполнены.Если используется опция
:through, то записи соединения уничтожаются, а не сами объекты. - collection=objects
-
Заменяет содержимое коллекции удалением и добавлением объектов по мере необходимости. Если опция
:throughимеет значение true, обратные вызовы в моделях соединения вызываются, кроме обратных вызовов destroy, так как удаление по умолчанию происходит напрямую. Вы можете указатьdependent: :destroyилиdependent: :nullifyдля переопределения этого. - collection_singular_ids
-
Возвращает массив идентификаторов связанных объектов.
- collection_singular_ids=ids
-
Заменяет коллекцию объектами, идентифицируемыми первичными ключами в
ids. Этот метод загружает модели и вызываетcollection=. См. выше. - collection.clear
-
Удаляет все объекты из коллекции. Это уничтожает связанные объекты, если они связаны с
dependent: :destroy, удаляет их напрямую из базы данных, еслиdependent: :delete_all, в противном случае устанавливает их внешние ключи наNULL. Если опция:throughимеет значение true, обратные вызовы destroy для моделей соединения не вызываются. Модели соединения удаляются напрямую. - collection.empty?
-
Возвращает
true, если нет связанных объектов. - collection.size
-
Возвращает количество связанных объектов.
- collection.find(…)
-
Находит связанный объект в соответствии с теми же правилами, что и ActiveRecord::FinderMethods#find.
- collection.exists?(…)
-
Проверяет, существует ли связанный объект с заданными условиями. Использует те же правила, что и ActiveRecord::FinderMethods#exists?.
- collection.build(attributes = {}, …)
-
Возвращает один или несколько новых объектов типа коллекции, которые были инициализированы с
attributesи связаны с этим объектом через внешний ключ, но еще не сохранены. - collection.create(attributes = {})
-
Возвращает новый объект типа коллекции, который был инициализирован с
attributes, связан с этим объектом через внешний ключ и уже сохранен (если прошел валидацию). Примечание: Это работает только в том случае, если базовая модель уже существует в базе данных, а не если это новая (несохраненная) запись! - collection.create!(attributes = {})
-
Делает то же, что и
collection.create, но вызывает ActiveRecord::RecordInvalid, если запись неверна. - collection.reload
-
Возвращает Relation всех связанных объектов, принудительно считывая данные из базы данных. Если объектов нет, возвращается пустая Relation.
Пример
Класс 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.Если вы собираетесь изменить ассоциацию (а не только читать из неё), то рекомендуется установить параметр
:inverse_of. - :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вызывает исключение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обратных вызовах.Обратите внимание, что ActiveRecord::NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает
:autosaveвtrue. - :inverse_of
-
Указывает имя ассоциации belongs_to в связанном объекте, являющейся обратной стороной этой ассоциации has_many. См. обзор двунаправленных ассоциаций в 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 1510 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приводит к возбуждению исключения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, никогда не сохраняет и не уничтожает связанный объект. По умолчанию сохраняет связанный объект только если это новая запись.
Обратите внимание, что ActiveRecord::NestedAttributes::ClassMethods#accepts_nested_attributes_for устанавливает
:autosaveвtrue. - :inverse_of
-
Указывает имя ассоциации belongs_to в связанном объекте, которая является обратной для этой ассоциации has_one. Подробнее см. обзор 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–2019 David Heinemeier Hansson
Licensed under the MIT License.