модуль 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#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. Поскольку ассоциация добавляет метод с этим именем в свою модель, она переопределит унаследованный метод и вызовет проблемы. Например, attributes и connection были бы плохим выбором имён ассоциаций.
Автогенерированные методы
См. также методы Instance Public ниже для получения более подробной информации.
Одиночные ассоциации (один к одному)
| | 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
Если ваш класс модели — Project, то модуль называется Project::GeneratedAssociationMethods. Модуль GeneratedAssociationMethods включается в класс модели сразу после (анонимного) модуля сгенерированных методов атрибутов, что означает, что ассоциация переопределит методы атрибута с таким же именем.
Мощность и ассоциации
Ассоциации Active Record могут использоваться для описания отношений один-к-одному, один-ко-многим и многие-ко-многим между моделями. Каждая модель использует ассоциацию для описания своей роли в отношении. Ассоциация belongs_to всегда используется в модели, которая имеет внешний ключ.
Один-к-одному
Используйте has_one в базовой, и belongs_to в связанной модели.
class Employee < ActiveRecord::Base has_one :office end class Office < ActiveRecord::Base belongs_to :employee # foreign key - employee_id end
Один-ко-многим
Используйте has_many в базовой, и belongs_to в связанной модели.
class Manager < ActiveRecord::Base has_many :employees end class Employee < ActiveRecord::Base belongs_to :manager # foreign key - manager_id end
Многие-ко-многим
Есть два способа построения отношения многие-ко-многим.
Первый способ использует ассоциацию has_many с опцией :through и моделью соединения, поэтому есть два этапа ассоциаций.
class Assignment < ActiveRecord::Base belongs_to :programmer # foreign key - programmer_id belongs_to :project # foreign key - project_id end class Programmer < ActiveRecord::Base has_many :assignments has_many :projects, through: :assignments end class Project < ActiveRecord::Base has_many :assignments has_many :programmers, through: :assignments end
Для второго способа используйте has_and_belongs_to_many в обеих моделях. Это требует таблицы соединения, у которой нет соответствующей модели или первичного ключа.
class Programmer < ActiveRecord::Base has_and_belongs_to_many :projects # foreign keys in the join table end class Project < ActiveRecord::Base has_and_belongs_to_many :programmers # foreign keys in the join table end
Выбор способа построения отношения многие-ко-многим не всегда прост. Если вам нужно работать с моделью отношения как со своей сущностью, используйте has_many :through. Используйте has_and_belongs_to_many при работе со схемами legacy или когда вы никогда не работаете непосредственно с самим отношением.
Это ассоциация belongs_to или has_one?
Оба выражают отношение 1-1. Различие в основном в том, где разместить внешний ключ, который находится в таблице для класса, объявляющего отношение belongs_to.
class User < ActiveRecord::Base # I reference an account. belongs_to :account end class Account < ActiveRecord::Base # One user references me. has_one :user end
Таблицы для этих классов могут выглядеть примерно так:
CREATE TABLE users ( id 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
Последняя строка должна сохранить запись через (a 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 передается как строка.
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')), вы хотите, чтобы тег «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 1644
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.
Будут добавлены методы для извлечения и запроса единственного связанного объекта, для которого этот объект содержит идентификатор:
belongs_to :author является плейсхолдером для символа, переданного в качестве аргумента name, поэтому belongs_to :author добавит, среди прочего, author.nil?.
- ассоциация
-
Возвращает связанный объект.
nilвозвращается, если не найден ни один. - ассоциация=(ассоциировать)
-
Присваивает объект ассоциации, извлекает первичный ключ и устанавливает его в качестве внешнего ключа.
- 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 в качестве вызываемого объекта (т. е. процедуры или лямбда-функции) для получения конкретной записи или настройки сгенерированного запроса при доступе к связанному объекту.
Примеры ограничений:
belongs_to :firm, -> { where(id: 2) }
belongs_to :user, -> { joins(:friends) }
belongs_to :level, ->(level) { where("game_level > ?", level.current) }
Параметры
- :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
-
Кэширует количество принадлежащих объектов в классе-ассоциации с помощью ActiveRecord::CounterCache::ClassMethods#increment_counter и ActiveRecord::CounterCache::ClassMethods#decrement_counter. Кэш счётчика увеличивается при создании объекта этого класса и уменьшается при его уничтожении. Это требует, чтобы в классе-ассоциации использовался столбец с именем
#{table_name}_count(например,comments_countдля класса Comment), принадлежащего классу (например, класс Post). — то есть миграция для#{table_name}_countсоздана в классе-ассоциации (так, что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. Обратите внимание, что при использовании функции touch не выполняется проверка, и выполняются только обратные вызовы
after_touch,after_commitиafter_rollback. - :inverse_of
-
Указывает имя ассоциации has_one или has_many в ассоциированном объекте, которая является обратной к этой ассоциации belongs_to. Не работает в сочетании с параметрами
:polymorphic. Дополнительные сведения см. в обзоре ActiveRecord::Associations::ClassMethods по двунаправленным ассоциациям. - :optional
-
Когда установлено значение
true, проверка наличия ассоциации не выполняется. - :required
-
Когда установлено значение
true, проверка наличия ассоциации также выполняется. Это проверяет саму ассоциацию, а не идентификатор. Вы можете использовать:inverse_ofдля избежания дополнительного запроса во время проверки. ПРИМЕЧАНИЕ:requiredпо умолчанию установлено в значениеtrueи устарело. Если вы не хотите выполнять проверку наличия ассоциации, используйтеoptional: true.
Примеры параметров:
belongs_to :firm, foreign_key: "client_of"
belongs_to :person, primary_key: "name", foreign_key: "person_name"
belongs_to :author, class_name: "Person", foreign_key: "author_id"
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
class_name: "Coupon", foreign_key: "coupon_id"
belongs_to :attachable, polymorphic: true
belongs_to :project, -> { readonly }
belongs_to :post, counter_cache: true
belongs_to :comment, touch: true
belongs_to :company, touch: :employees_last_updated_at
belongs_to :user, optional: true
# File activerecord/lib/active_record/associations.rb, line 1810
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
if scope.is_a?(Hash)
options = scope
scope = nil
end
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 " 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
has_many name, scope, hm_options, &extension
self._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, ->(category) {
where("default_category = ?", category.name)
}
Расширения
Аргумент 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 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 как вызываемый объект (т.е. proc или lambda), чтобы получить определённый набор записей или настроить сгенерированный запрос при доступе к связанной коллекции.
Примеры ограничений:
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(post) { where("max_post_length > ?", 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, и удаляются записи в модели соединения, а не связанные записи. -
- :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 1504
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)
-
Присваивает связанный объект, извлекает первичный ключ, устанавливает его как внешний ключ и сохраняет связанный объект. Для предотвращения несоответствий в базе данных при присвоении нового объекта постоянно удаляется существующий связанный объект, даже если новый не сохранен в базе данных.
- 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 :dob, ->(dob) { where("Date.new(2000, 01, 01) > ?", dob) }
Параметры
Объявление также может включать 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, к ассоциации также будет применена проверка на присутствие. Это будет проверять саму ассоциацию, а не идентификатор. Вы можете использовать: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.