модуль ActiveRecord::Associations::ClassMethods
Ассоциации Active Record
Ассоциации — это набор макроподобных методов класса для связывания объектов через внешние ключи. Они выражают отношения, такие как «Проект имеет одного менеджера проекта» или «Проект принадлежит портфелю». Каждый макрос добавляет ряд методов в класс, которые специализируются в зависимости от символа коллекции или ассоциации и хэша опций. Он работает примерно так же, как собственные методы Ruby attr*.
class Project < ActiveRecord::Base belongs_to :portfolio has_one :project_manager has_many :milestones has_and_belongs_to_many :categories end
Теперь класс проекта имеет следующие методы (и другие) для облегчения навигации и управления его отношениями:
project = Project.first project.portfolio project.portfolio = Portfolio.first project.reload_portfolio project.project_manager project.project_manager = ProjectManager.first project.reload_project_manager project.milestones.empty? project.milestones.size project.milestones project.milestones << Milestone.first project.milestones.delete(Milestone.first) project.milestones.destroy(Milestone.first) project.milestones.find(Milestone.first.id) project.milestones.build project.milestones.create project.categories.empty? project.categories.size project.categories project.categories << Category.first project.categories.delete(category1) project.categories.destroy(category1)
Предупреждение
Не создавайте ассоциации с теми же именами, что и методы экземпляра ActiveRecord::Base. Поскольку ассоциация добавляет метод с таким именем в свою модель, использование ассоциации с тем же именем, что и метод, предоставляемый ActiveRecord::Base, переопределит метод, унаследованный от ActiveRecord::Base, и нарушит работу. Например, attributes и connection были бы плохим выбором имён ассоциаций, так как эти имена уже существуют в списке ActiveRecord::Base методов экземпляров.
Автоматически генерируемые методы
См. также «Общедоступные методы экземпляра» ниже (из belongs_to) для получения более подробной информации.
Одноэлементные ассоциации (один к одному)
| | belongs_to |
generated methods | belongs_to | :polymorphic | has_one
----------------------------------+------------+--------------+---------
other | X | X | X
other=(other) | X | X | X
build_other(attributes={}) | X | | X
create_other(attributes={}) | X | | X
create_other!(attributes={}) | X | | X
reload_other | X | X | X
other_changed? | X | X |
other_previously_changed? | X | X | Коллекционные ассоциации (один ко многим/многие ко многим)
| | | has_many
generated methods | habtm | has_many | :through
----------------------------------+-------+----------+----------
others | X | X | X
others=(other,other,...) | X | X | X
other_ids | X | X | X
other_ids=(id,id,...) | X | X | X
others<< | X | X | X
others.push | X | X | X
others.concat | X | X | X
others.build(attributes={}) | X | X | X
others.create(attributes={}) | X | X | X
others.create!(attributes={}) | X | X | X
others.size | X | X | X
others.length | X | X | X
others.count | X | X | X
others.sum(*args) | X | X | X
others.empty? | X | X | X
others.clear | X | X | X
others.delete(other,other,...) | X | X | X
others.delete_all | X | X | X
others.destroy(other,other,...) | X | X | X
others.destroy_all | X | X | X
others.find(*args) | X | X | X
others.exists? | X | X | X
others.distinct | X | X | X
others.reset | X | X | X
others.reload | X | X | X Переопределение сгенерированных методов
Методы ассоциаций генерируются в модуле, включенном в класс модели, что упрощает переопределения. Таким образом, исходный сгенерированный метод можно вызвать с помощью super:
class Car < ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
Модуль методов ассоциации включается сразу после модуля сгенерированных методов атрибутов, что означает, что ассоциация переопределит методы атрибута с тем же именем.
Мощность и ассоциации
Ассоциации Active Record могут использоваться для описания отношений один-к-одному, один-ко-многим и многие-ко-многим между моделями. Каждая модель использует ассоциацию для описания своей роли в отношении. Ассоциация belongs_to всегда используется в модели, которая имеет внешний ключ.
Один-к-одному
Используйте has_one в базовой модели и belongs_to в связанной модели.
class Employee < ActiveRecord::Base has_one :office end class Office < ActiveRecord::Base belongs_to :employee # foreign key - employee_id end
Один-ко-многим
Используйте has_many в базовой модели и belongs_to в связанной модели.
class Manager < ActiveRecord::Base has_many :employees end class Employee < ActiveRecord::Base belongs_to :manager # foreign key - manager_id end
Многие-ко-многим
Есть два способа построения отношения многие-ко-многим.
Первый способ использует ассоциацию has_many с опцией :through и моделью соединения, поэтому есть два этапа ассоциаций.
class Assignment < ActiveRecord::Base belongs_to :programmer # foreign key - programmer_id belongs_to :project # foreign key - project_id end class Programmer < ActiveRecord::Base has_many :assignments has_many :projects, through: :assignments end class Project < ActiveRecord::Base has_many :assignments has_many :programmers, through: :assignments end
Для второго способа используйте has_and_belongs_to_many в обеих моделях. Это требует таблицы соединения, у которой нет соответствующей модели или первичного ключа.
class Programmer < ActiveRecord::Base has_and_belongs_to_many :projects # foreign keys in the join table end class Project < ActiveRecord::Base has_and_belongs_to_many :programmers # foreign keys in the join table end
Выбор способа построения отношения многие-ко-многим не всегда прост. Если вам нужно работать с моделью отношения как со своей сущностью, используйте has_many :through. Используйте has_and_belongs_to_many при работе со схемами старого образца или когда вы никогда не работаете напрямую с самим отношением.
Это ассоциация belongs_to или has_one?
Оба выражают отношение 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
Примечание: Объединение или жадное загрузка таких ассоциаций невозможны, потому что эти операции выполняются до создания экземпляра. Такие ассоциации могут быть предварительно загружены, но это выполнит N+1 запросов, так как для каждого записей будет другой область видимости (аналогично предварительной загрузке полиморфных областей видимости).
Обработчики событий ассоциаций
Аналогично обычным обработчикам событий, которые прикрепляются к жизненному циклу объекта Active Record, вы также можете определять обработчики событий, которые срабатывают при добавлении или удалении объекта из коллекции ассоциаций.
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: :congratulate_client,
after_remove: :log_after_remove
def congratulate_client(record)
# ...
end
def log_after_remove(record)
# ...
end
end
Возможна настройка цепочки обработчиков, передав их в виде массива. Пример:
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: [:congratulate_client, -> (firm, record) { firm.log << "after_adding#{record.id}" }],
after_remove: :log_after_remove
end
Возможные обработчики: before_add, after_add, before_remove, и after_remove.
Если любой из обработчиков before_add событий выбросит исключение, объект не будет добавлен в коллекцию.
Аналогично, если любой из обработчиков before_remove событий выбросит исключение, объект не будет удалён из коллекции.
Примечание: Для запуска обработчиков удаления вы должны использовать методы destroy / destroy_all. Например:
-
firm.clients.destroy(client) -
firm.clients.destroy(*clients) -
firm.clients.destroy_all
Методы delete / delete_all , подобные следующим, не запускают обработчики удаления:
-
firm.clients.delete(client) -
firm.clients.delete(*clients) -
firm.clients.delete_all
Расширения ассоциаций
Объекты-прокси, управляющие доступом к ассоциациям, можно расширять с помощью анонимных модулей. Это особенно полезно для добавления новых методов поиска, создания и других методов типа фабрики, которые используются только в рамках этой ассоциации.
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
Если вам нужно использовать одни и те же расширения для нескольких ассоциаций, вы можете использовать именованный модуль расширения.
module FindOrCreateByNameExtension
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
class Account < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
class Company < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
Некоторые расширения могут работать только с использованием внутренних данных ассоциации. Расширения могут получить доступ к соответствующему состоянию, используя следующие методы (где items — имя ассоциации):
-
record.association(:items).owner— Возвращает объект, частью которого является ассоциация. -
record.association(:items).reflection— Возвращает объект отражения, который описывает ассоциацию. -
record.association(:items).target— Возвращает связанный объект дляbelongs_toиhas_one, или коллекцию связанных объектов дляhas_manyиhas_and_belongs_to_many.
Однако, внутри кода расширения вы не будете иметь доступа к record как описано выше. В этом случае, вы можете получить доступ к proxy_association. Например, record.association(:items) и record.items.proxy_association вернут один и тот же объект, что позволит вам делать вызовы, такие как proxy_association.owner внутри расширений ассоциации.
Модели-соединители ассоциаций
Ассоциации «многие-ко-многим» могут быть настроены с помощью опции :through для использования явной модели соединения для получения данных. Это работает аналогично ассоциации has_and_belongs_to_many. Преимущество заключается в том, что вы можете добавить валидации, обратные вызовы и дополнительные атрибуты в модели соединения. Рассмотрим следующую схему:
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
end
class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :book
end
@author = Author.first
@author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
@author.books # selects all books by using the Authorship join model
Вы также можете использовать ассоциацию has_many в модели соединения:
class Firm < ActiveRecord::Base
has_many :clients
has_many :invoices, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :client
end
@firm = Firm.first
@firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
@firm.invoices # selects all invoices by going through the Client join model
Аналогично, вы можете использовать ассоциацию has_one в модели соединения:
class Group < ActiveRecord::Base
has_many :users
has_many :avatars, through: :users
end
class User < ActiveRecord::Base
belongs_to :group
has_one :avatar
end
class Avatar < ActiveRecord::Base
belongs_to :user
end
@group = Group.first
@group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
@group.avatars # selects all avatars by going through the User join model.
Важное замечание при использовании ассоциаций has_one или has_many в модели соединения заключается в том, что эти ассоциации являются только для чтения. Например, следующее не будет работать после предыдущего примера:
@group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around @group.avatars.delete(@group.avatars.last) # so would this
Установка обратных связей
Если вы используете belongs_to в модели соединения, рекомендуется установить опцию :inverse_of для belongs_to, что позволит правильно работать следующему примеру (где tags — ассоциация has_many :through):
@post = Post.first @tag = @post.tags.build name: "ruby" @tag.save
Последняя строка должна сохранить запись через (Tagging). Это сработает только в том случае, если установлена опция :inverse_of:
class Tagging < ActiveRecord::Base belongs_to :post belongs_to :tag, inverse_of: :taggings end
Если вы не установите запись :inverse_of, ассоциация постарается сопоставить себя с правильной обратной связью. Автоматическое обнаружение обратной связи работает только с ассоциациями has_many, has_one и belongs_to.
Опции :foreign_key и :through для ассоциаций также препятствуют автоматическому поиску обратной связи, как и пользовательские области применения в некоторых случаях. Подробности см. в руководстве по ассоциациям Active Record Active Record Associations guide.
Автоматическое определение обратной связи использует эвристику, основанную на названии класса, поэтому она может не работать для всех ассоциаций, особенно для тех, которые имеют нестандартные названия.
Вы можете отключить автоматическое обнаружение обратной связи, установив опцию :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)
Это выполнит один запрос для загрузки адресов и загрузит addressable одним запросом на каждый тип addressable. Например, если все addressable — либо класса Person, либо Company, то будет выполнено всего 3 запроса. Список типов addressable для загрузки определяется на основе загруженных адресов. Это не поддерживается, если Active Record должен вернуться к предыдущей реализации жадной загрузки и вызовет ActiveRecord::EagerLoadPolymorphicError. Причина в том, что тип родительской модели является значением столбца, поэтому соответствующее имя таблицы не может быть помещено в предложения FROM/JOIN этого запроса.
Псевдонимы таблиц
Active Record использует алиасы таблиц в случае, если к таблице обращаются несколько раз в запросе с объединением. Если к таблице обращаются только один раз, используется стандартное имя таблицы. Во второй раз таблица алиасируется как #{reflection_name}_#{parent_table_name}. Индексы добавляются для каждого последующего использования имени таблицы.
Post.joins(:comments) # SELECT ... FROM posts INNER JOIN comments ON ... Post.joins(:special_comments) # STI # SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name # SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
Пример дерева:
TreeMixin.joins(:children)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
TreeMixin.joins(children: :parent)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
TreeMixin.joins(children: {parent: :children})
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# INNER JOIN mixins childrens_mixins_2
Таблицы объединения Has and Belongs to Many используют ту же идею, но добавляют суффикс _join.
Post.joins(:categories)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
Post.joins(categories: :posts)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
Post.joins(categories: {posts: :categories})
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
Если вы хотите указать собственные пользовательские объединения, используя метод ActiveRecord::QueryMethods#joins, эти имена таблиц будут иметь приоритет над жадными ассоциациями:
Post.joins(:comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
Post.joins(:comments, :special_comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
Алиасы таблиц автоматически усекаются в соответствии с максимальной длиной идентификаторов таблиц в зависимости от конкретной базы данных.
Модули
По умолчанию ассоциации будут искать объекты в рамках текущего области видимости модуля. Рассмотрим:
module MyApplication
module Business
class Firm < ActiveRecord::Base
has_many :clients
end
class Client < ActiveRecord::Base; end
end
end
Когда вызывается Firm#clients, она, в свою очередь, вызовет MyApplication::Business::Client.find_all_by_firm_id(firm.id). Если вы хотите связать класс в другой области видимости модуля, это можно сделать, указав полное имя класса.
module MyApplication
module Business
class Firm < ActiveRecord::Base; end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :firm, class_name: "MyApplication::Business::Firm"
end
end
end
Взаимные ассоциации
Когда вы задаёте ассоциацию, обычно на связанной модели есть ассоциация, которая определяет ту же взаимосвязь в обратном направлении. Например, с помощью следующих моделей:
class Dungeon < ActiveRecord::Base has_many :traps has_one :evil_wizard end class Trap < ActiveRecord::Base belongs_to :dungeon end class EvilWizard < ActiveRecord::Base belongs_to :dungeon end
Ассоциация traps на Dungeon и ассоциация dungeon на Trap являются обратными друг другу, и обратная ассоциация dungeon на EvilWizard — это ассоциация evil_wizard на Dungeon (и наоборот). По умолчанию Active Record может угадать обратную ассоциацию, основываясь на имени класса. Результат следующий:
d = Dungeon.first t = d.traps.first d.object_id == t.dungeon.object_id # => true
Экземпляры Dungeon d и t.dungeon в приведённом примере относятся к одному и тому же экземпляру в памяти, так как ассоциация соответствует имени класса. Результат будет таким же, если мы добавим :inverse_of в наши определения моделей:
class Dungeon < ActiveRecord::Base has_many :traps, inverse_of: :dungeon has_one :evil_wizard, inverse_of: :dungeon end class Trap < ActiveRecord::Base belongs_to :dungeon, inverse_of: :traps end class EvilWizard < ActiveRecord::Base belongs_to :dungeon, inverse_of: :evil_wizard end
Для получения более подробной информации см. документацию по параметру :inverse_of и руководство по ассоциациям Active Record .
Удаление из ассоциаций
Зависимые ассоциации
has_many, has_one и belongs_to ассоциации поддерживают параметр :dependent. Это позволяет указать, что связанные записи должны быть удалены при удалении владельца.
Например:
class Author has_many :posts, dependent: :destroy end Author.find(1).destroy # => Will destroy all of the author's posts, too
Параметр :dependent может принимать разные значения, которые определяют, как выполняется удаление. Для получения более подробной информации см. документацию по этому параметру для различных типов ассоциаций. При отсутствии параметра поведение заключается в том, чтобы ничего не делать со связанными записями при уничтожении записи.
Обратите внимание, что :dependent реализовано с использованием системы обратных вызовов Rails, которая работает путём обработки обратных вызовов в определённом порядке. Следовательно, другие обратные вызовы, объявленные до или после параметра :dependent, могут повлиять на его действия.
Обратите внимание, что параметр :dependent игнорируется для has_one :through ассоциаций.
Удалить или уничтожить?
has_many и has_and_belongs_to_many ассоциации имеют методы destroy, delete, destroy_all и delete_all.
Для has_and_belongs_to_many, delete и destroy — это одно и то же: они удаляют записи в таблице объединения.
Для has_many, destroy и destroy_all всегда вызовут метод destroy удаляемой записи(ей), чтобы запустить обратные вызовы. Однако delete и delete_all будут либо выполнять удаление в соответствии со стратегией, заданной параметром :dependent, или, если параметр :dependent не задан, следуют по умолчанию. По умолчанию ничего не делается (внешние ключи остаются с родительскими идентификаторами), за исключением has_many :through, где стратегия по умолчанию — delete_all (удаление записей объединения без запуска их обратных вызовов).
Также есть метод clear, который эквивалентен delete_all, за исключением того, что он возвращает ассоциацию, а не записи, которые были удалены.
Что удаляется?
Здесь есть потенциальная ловушка: has_and_belongs_to_many и has_many :through ассоциации содержат записи в таблицах объединения, а также связанные записи. Так что когда мы вызываем один из этих методов удаления, что именно должно быть удалено?
Ответ заключается в том, что предполагается, что удаление по ассоциации связано с удалением связи между владельцем и связанными объектами, а не обязательно самих связанных объектов. Таким образом, с has_and_belongs_to_many и has_many :through, записи таблицы объединения будут удалены, но связанные записи — нет.
Это имеет смысл, если подумать: если вы вызовете post.tags.delete(Tag.find_by(name: 'food')), вы захотите отсоединить тег «еда» от поста, а не удалить сам тег из базы данных.
Однако есть примеры, где эта стратегия не подходит. Например, предположим, что у человека есть много проектов, и каждый проект имеет много задач. Если мы удалим одну из задач человека, мы, вероятно, не захотим удалять проект. В этом случае метод удаления фактически не сработает: он может быть использован только если ассоциация на модели объединения — 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 1647 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) -
Присваивает связанный объект, извлекает первичный ключ и устанавливает его как внешний ключ. Никаких изменений или удалений существующих записей не происходит.
-
build_association(attributes = {}) -
Возвращает новый объект связанного типа, который был создан с
attributesи связан с этим объектом через внешний ключ, но еще не сохранен. -
create_association(attributes = {}) -
Возвращает новый объект связанного типа, который был создан с
attributes, связан с этим объектом через внешний ключ и который уже был сохранен (если он прошел проверку). -
create_association!(attributes = {}) -
Делает то же, что и
create_association, но вызываетActiveRecord::RecordInvalid, если запись неверна. -
reload_association -
Возвращает связанный объект, принудительно выполняя чтение из базы данных.
-
reset_association -
Разгружает связанный объект. Следующий доступ обратится к нему в базу данных.
-
association_changed? -
Возвращает true, если новый связанный объект был назначен и при следующем сохранении внешний ключ будет обновлен.
-
association_previously_changed? -
Возвращает true, если при предыдущем сохранении ассоциация была обновлена для ссылки на новый связанный объект.
Пример
class Post < ActiveRecord::Base belongs_to :author end
Объявление belongs_to :author добавляет следующие методы (и многое другое):
post = Post.find(7) author = Author.find(19) post.author # similar to Author.find(post.author_id) post.author = author # similar to post.author_id = author.id post.build_author # similar to post.author = Author.new post.create_author # similar to post.author = Author.new; post.author.save; post.author post.create_author! # similar to post.author = Author.new; post.author.save!; post.author post.reload_author post.reset_author post.author_changed? post.author_previously_changed?
Ограничения
Вы можете передать второй аргумент scope в качестве вызываемого объекта (например, proc или lambda), чтобы получить конкретную запись или настроить сгенерированный запрос при доступе к связанному объекту.
Примеры ограничений:
belongs_to :firm, -> { where(id: 2) }
belongs_to :user, -> { joins(:friends) }
belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
Параметры
Объявление также может включать хеш options для специализации поведения ассоциации.
-
:class_name -
Укажите имя класса ассоциации. Используйте его только в том случае, если это имя нельзя вывести из имени ассоциации. Так,
belongs_to :authorпо умолчанию будет связан с классом Author, но если реальное имя класса — Person, вам придется указать его с помощью этого параметра. -
:foreign_key -
Укажите внешний ключ, используемый для ассоциации. По умолчанию он предполагается по имени ассоциации с суффиксом «_id». Таким образом, класс, который определяет ассоциацию
belongs_to :person, будет использовать «person_id» в качестве значения по умолчанию:foreign_key. Аналогично,belongs_to :favorite_person, class_name: "Person"будет использовать внешний ключ «favorite_person_id».Установка параметра
:foreign_keyпредотвращает автоматическое обнаружение обратной связи ассоциации, поэтому рекомендуется также установить параметр:inverse_of. -
:foreign_type -
Укажите столбец, используемый для хранения типа связанного объекта, если это полиморфная ассоциация. По умолчанию он предполагается по имени ассоциации с суффиксом «_type». Таким образом, класс, который определяет ассоциацию
belongs_to :taggable, polymorphic: true, будет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. -
:primary_key -
Укажите метод, который возвращает первичный ключ связанного объекта, используемого для ассоциации. По умолчанию это
id. -
:dependent -
Если установлено значение
:destroy, связанный объект уничтожается, когда уничтожается этот объект. Если установлено значение:delete, связанный объект удаляется без вызова метода destroy. Если установлено значение:destroy_async, связанный объект запланирован для удаления в фоновой задаче. Этот параметр не следует указывать, когдаbelongs_toиспользуется совместно с отношениемhas_manyв другом классе из-за потенциального оставления orphaned записей. -
:counter_cache -
Кэширует количество принадлежащих объектов в классе ассоциации с помощью
CounterCache::ClassMethods#increment_counterиCounterCache::ClassMethods#decrement_counter. Кэш счётчика увеличивается при создании объекта этого класса и уменьшается при его удалении. Это требует, чтобы в классе ассоциации использовался столбец с именем#{table_name}_count(например,comments_countдля класса Comment), то есть миграция для#{table_name}_countсоздаётся в классе ассоциации (таким образом,Post.comments_countвернёт кэшированное значение). Вы также можете указать имя пользовательского столбца счётчика, указав имя столбца вместо значенияtrue/false(например,counter_cache: :my_custom_counter).Начинать использование кэша счётчика на существующих больших таблицах может быть проблематично, потому что значения столбцов должны быть заполнены отдельно от добавления столбца (чтобы не блокировать таблицу слишком долго) и до использования
:counter_cache(иначе методы, такие какsize/any?и т.д., которые используют кэш счётчика внутри, могут давать некорректные результаты). Для безопасного заполнения значений при сохранении обновлений столбцов счётчика с созданием/удалением дочерних записей и избегания получения некорректных значений из кэша счётчика и получения результатов из базы данных, используйтеcounter_cache: { active: false }. Если вам также нужно указать имя пользовательского столбца, используйтеcounter_cache: { active: false, column: :my_custom_counter }.Примечание: Если вы включили кэш счётчика, возможно, вам нужно добавить атрибут кэша счётчика в список
attr_readonlyв связанных классах (например,class Post; attr_readonly :comments_count; end). -
:polymorphic -
Укажите, что эта ассоциация является полиморфной, передав
true. Примечание: поскольку полиморфные ассоциации полагаются на хранение имен классов в базе данных, убедитесь, что вы обновили имена классов в столбце типа*_typeсоответствующих строк. -
:validate -
При установке в
trueпроверяет новые объекты, добавленные в ассоциацию, при сохранении родительского объекта. По умолчаниюfalse. Если вы хотите гарантировать, что связанные объекты повторно проверяются при каждом обновлении, используйтеvalidates_associated. -
:autosave -
Если true, всегда сохраняйте связанный объект или уничтожайте его, если он помечен для уничтожения, при сохранении родительского объекта. Если false, никогда не сохраняйте и не уничтожайте связанный объект. По умолчанию сохраняется только связанный объект, если это новая запись.
Обратите внимание, что
NestedAttributes::ClassMethods#accepts_nested_attributes_forустанавливает:autosaveнаtrue. -
:touch -
Если true, связанный объект будет затронут (атрибуты
updated_at/updated_onустановлены в текущее время), когда эта запись сохраняется или уничтожается. Если вы укажете символ, этот атрибут будет обновлён текущим временем в дополнение к атрибутамupdated_at/updated_on. Обратите внимание, что проверка не будет выполняться при касании, и будут выполнены только обратные вызовыafter_touch,after_commit, иafter_rollback. -
:inverse_of -
Указывает имя ассоциации
has_oneилиhas_manyв связанном объекте, которая является обратной этой ассоциацииbelongs_to. См. обзорActiveRecord::Associations::ClassMethodsпо двунаправленным ассоциациям для получения более подробной информации. -
:optional -
При установке в
true, присутствие ассоциации не будет проверяться. -
:required -
При установке в
true, присутствие ассоциации также будет проверяться. Это будет проверка самой ассоциации, а не идентификатора. Вы можете использовать:inverse_ofдля избегания дополнительного запроса во время проверки. ПРИМЕЧАНИЕ:requiredустановлено вtrueпо умолчанию и устарело. Если вы не хотите проверять наличие ассоциации, используйтеoptional: true. -
:default -
Предоставляет вызываемый объект (т.е. proc или lambda), чтобы указать, что ассоциация должна быть инициализирована конкретной записью перед проверкой. Обратите внимание, что вызываемый объект не будет выполнен, если запись существует.
-
:strict_loading -
Принудительно загружает каждый раз, когда связанная запись загружается через эту ассоциацию.
-
:ensuring_owner_was -
Указывает метод экземпляра, который вызывается в владельце. Метод должен возвращать true, чтобы связанные записи были удалены в фоновой задаче.
-
:query_constraints -
Служит составным внешним ключом. Определяет список столбцов, которые будут использоваться для запроса связанного объекта. Это необязательный параметр. По умолчанию
Railsпопытается вывести значение автоматически. Когда значение установлено, размерArrayдолжен соответствовать первичному ключу связанной модели или размеруquery_constraints.
Примеры параметров:
belongs_to :firm, foreign_key: "client_of"
belongs_to :person, primary_key: "name", foreign_key: "person_name"
belongs_to :author, class_name: "Person", foreign_key: "author_id"
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
class_name: "Coupon", foreign_key: "coupon_id"
belongs_to :attachable, polymorphic: true
belongs_to :project, -> { readonly }
belongs_to :post, counter_cache: true
belongs_to :comment, touch: true
belongs_to :company, touch: :employees_last_updated_at
belongs_to :user, optional: true
belongs_to :account, default: -> { company.account }
belongs_to :account, strict_loading: true
belongs_to :note, query_constraints: [:organization_id, :note_id]
# File activerecord/lib/active_record/associations.rb, line 1828
def has_and_belongs_to_many(name, scope = nil, **options, &extension)
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
builder = Builder::HasAndBelongsToMany.new name, self, options
join_model = builder.through_model
const_set join_model.name, join_model
private_constant join_model.name
middle_reflection = builder.middle_reflection join_model
Builder::HasMany.define_callbacks self, middle_reflection
Reflection.add_reflection self, middle_reflection.name, middle_reflection
middle_reflection.parent_reflection = habtm_reflection
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy_associations
association(:#{middle_reflection.name}).delete_all(:delete_all)
association(:#{name}).reset
super
end
RUBY
}
hm_options = {}
hm_options[:through] = middle_reflection.name
hm_options[:source] = join_model.right_reflection.name
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
hm_options[k] = options[k] if options.key? k
end
has_many name, scope, **hm_options, &extension
_reflections[name].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[7.2]
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.
Пример
class Developer < ActiveRecord::Base has_and_belongs_to_many :projects end
Объявление has_and_belongs_to_many :projects добавляет следующие методы (и другие):
developer = Developer.find(11) project = Project.find(9) developer.projects developer.projects << project developer.projects.delete(project) developer.projects.destroy(project) developer.projects = [project] developer.project_ids developer.project_ids = [9] developer.projects.clear developer.projects.empty? developer.projects.size developer.projects.find(9) developer.projects.exists?(9) developer.projects.build # similar to Project.new(developer_id: 11) developer.projects.create # similar to Project.create(developer_id: 11) developer.projects.reload
Объявление может включать хеш options для настройки поведения ассоциации.
Схемы
Вы можете передать второй аргумент scope как вызываемый объект (например, proc или lambda) для получения определённого набора записей или настройки сгенерированного запроса при доступе к связанной коллекции.
Примеры схем:
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :categories, ->(post) {
where("default_category = ?", post.default_category)
}
Расширения
Аргумент extension позволяет передать блок в ассоциацию has_and_belongs_to_many. Это полезно для добавления новых методов поиска, создания и других методов типа фабрики, которые будут использоваться в рамках ассоциации.
Примеры расширений:
has_and_belongs_to_many :contractors do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
Опции
-
:class_name -
Укажите имя класса ассоциации. Используйте его только в том случае, если это имя нельзя вывести из имени ассоциации. Так,
has_and_belongs_to_many :projectsпо умолчанию будет связан с классом Project, но если реальное имя класса — SuperProject, вам нужно будет указать его с помощью этой опции. -
:join_table -
Укажите имя таблицы соединения, если значение по умолчанию, основанное на лексикографическом порядке, вам не подходит. ПРЕДУПРЕЖДЕНИЕ: Если вы перезаписываете имя таблицы любого из классов, метод
table_nameДОЛЖЕН быть объявлен под любым объявлениемhas_and_belongs_to_manyдля его работы. -
:foreign_key -
Укажите внешний ключ, используемый для ассоциации. По умолчанию он определяется по имени этого класса в нижнем регистре и суффиксу «_id». Таким образом, класс Person, который создаёт ассоциацию
has_and_belongs_to_manyс классом Project, будет использовать «person_id» в качестве значения по умолчанию:foreign_key.Установка опции
:foreign_keyпредотвращает автоматическое определение обратной ассоциации, поэтому рекомендуется также установить опцию:inverse_of. -
:association_foreign_key -
Укажите внешний ключ, используемый для ассоциации на стороне получателя ассоциации. По умолчанию он определяется по имени связанного класса в нижнем регистре и суффиксу «_id». Поэтому, если класс Person создаёт ассоциацию
has_and_belongs_to_manyс классом Project, ассоциация будет использовать «project_id» как значение по умолчанию:association_foreign_key. -
:validate -
При установке в
true, валидирует новые объекты, добавленные в ассоциацию, при сохранении родительского объекта. По умолчаниюtrue. Если вы хотите гарантировать, что связанные объекты будут перевалидированы при каждом обновлении, используйтеvalidates_associated. -
:autosave -
Если true, всегда сохраняет связанные объекты или уничтожает их, если они помечены на уничтожение, при сохранении родительского объекта. Если false, никогда не сохраняет или уничтожает связанные объекты. По умолчанию сохраняются только новые записи связанных объектов.
Обратите внимание, что
NestedAttributes::ClassMethods#accepts_nested_attributes_forустанавливает:autosaveвtrue. -
:strict_loading -
Принудительно загружает связанные записи каждый раз, когда они загружаются через эту ассоциацию.
Примеры опций:
has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :nations, class_name: "Country"
has_and_belongs_to_many :categories, join_table: "prods_cats"
has_and_belongs_to_many :categories, -> { readonly }
has_and_belongs_to_many :categories, strict_loading: true
# File activerecord/lib/active_record/associations.rb, line 1268 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, по умолчанию записи объединения удаляются (а не устанавливаются в NULL), но вы можете указатьdependent: :destroyилиdependent: :nullify, чтобы переопределить это. -
collection.destroy(object, ...) -
Удаляет один или несколько объектов из коллекции, выполняя
destroyдля каждой записи, независимо от любой опции зависимостей, гарантируя запуск обратных вызовов.Если используется опция
:through, то записи объединения уничтожаются, а не сами объекты. -
collection=objects -
Заменяет содержимое коллекции путем удаления и добавления объектов по мере необходимости. Если опция
:throughимеет значение true, обратные вызовы в моделях объединения вызываются, за исключением обратных вызовов удаления, так как удаление выполняется напрямую по умолчанию. Вы можете указатьdependent: :destroyилиdependent: :nullifyдля переопределения этого. -
collection_singular_ids -
Возвращает массив идентификаторов связанных объектов.
-
collection_singular_ids=ids -
Заменяет коллекцию объектами, идентифицированными первичными ключами в
ids. Этот метод загружает модели и вызываетcollection=. Смотрите выше. -
collection.clear -
Удаляет все объекты из коллекции. Это уничтожает связанные объекты, если они связаны с
dependent: :destroy, удаляет их напрямую из базы данных, еслиdependent: :delete_all, в противном случае устанавливает их внешние ключи наNULL. Если опция:throughимеет значение true, обратные вызовы уничтожения не вызываются для моделей объединения. Модели объединения удаляются напрямую. -
collection.empty? -
Возвращает
true, если связанных объектов нет. -
collection.size -
Возвращает количество связанных объектов.
-
collection.find(...) -
Находит связанный объект в соответствии с теми же правилами, что и
ActiveRecord::FinderMethods#find. -
collection.exists?(...) -
Проверяет, существует ли связанный объект с заданными условиями. Использует те же правила, что и
ActiveRecord::FinderMethods#exists?. -
collection.build(attributes = {}, ...) -
Возвращает один или несколько новых объектов типа коллекции, которые были созданы с помощью
attributesи связаны с этим объектом через внешний ключ, но еще не сохранены. -
collection.create(attributes = {}) -
Возвращает новый объект типа коллекции, который был создан с помощью
attributes, связан с этим объектом через внешний ключ и уже сохранен (если он прошел валидацию). **Примечание**: Это работает только в том случае, если базовая модель уже существует в базе данных, а не если это новая (несохраненная) запись! -
collection.create!(attributes = {}) -
Делает то же самое, что и
collection.create, но вызываетActiveRecord::RecordInvalid, если запись недействительна. -
collection.reload -
Возвращает
Relationвсех связанных объектов, принудительно считывая данные из базы данных. Если не найдено ни одного объекта, возвращается пустойRelation.
Пример
class Firm < ActiveRecord::Base has_many :clients end
Объявление has_many :clients добавляет следующие методы (и другие):
firm = Firm.find(2) client = Client.find(6) firm.clients # similar to Client.where(firm_id: 2) firm.clients << client firm.clients.delete(client) firm.clients.destroy(client) firm.clients = [client] firm.client_ids firm.client_ids = [6] firm.clients.clear firm.clients.empty? # similar to firm.clients.size == 0 firm.clients.size # similar to Client.count "firm_id = 2" firm.clients.find # similar to Client.where(firm_id: 2).find(6) firm.clients.exists?(name: 'ACME') # similar to Client.exists?(name: 'ACME', firm_id: 2) firm.clients.build # similar to Client.new(firm_id: 2) firm.clients.create # similar to Client.create(firm_id: 2) firm.clients.create! # similar to Client.create!(firm_id: 2) firm.clients.reload
Объявление также может включать хеш options для специализации поведения ассоциации.
Сферы
Вы можете передать второй аргумент scope в виде вызываемого объекта (т.е. метода или лямбды), чтобы получить определенный набор записей или настроить генерируемый запрос при обращении к связанной коллекции.
Примеры использования областей:
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
Расширения
Аргумент extension позволяет передать блок в ассоциацию has_many. Это полезно для добавления новых методов поиска, создания и других методов типа фабрики, которые будут использоваться как часть ассоциации.
Примеры расширений:
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
Опции
-
:class_name -
Укажите имя класса ассоциации. Используйте его только в случае, если имя не может быть выведено из имени ассоциации. Таким образом,
has_many :productsпо умолчанию будет связан с классомProduct, но если реальное имя класса —SpecialProduct, вам нужно будет указать его с помощью этого параметра. -
:foreign_key -
Укажите внешний ключ, используемый для ассоциации. По умолчанию он определяется как имя этого класса в нижнем регистре с добавленным суффиксом «_id». Таким образом, класс Person, создающий ассоциацию
has_many, будет использовать «person_id» в качестве значения по умолчанию:foreign_key.Установка параметра
:foreign_keyпредотвращает автоматическое определение обратной ассоциации, поэтому рекомендуется установить также параметр:inverse_of. -
:foreign_type -
Укажите столбец, используемый для хранения типа связанного объекта, если это полиморфная ассоциация. По умолчанию это определяется по имени полиморфной ассоциации, указанной в параметре «as», с добавленным суффиксом «_type». Таким образом, класс, определяющий ассоциацию
has_many :tags, as: :taggable, будет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. -
:primary_key -
Укажите имя столбца, используемого в качестве первичного ключа для ассоциации. По умолчанию это
id. -
:dependent -
Определяет, что происходит со связанными объектами, когда их владелец уничтожается. Обратите внимание, что эти параметры реализованы как колбэки, и Rails выполняет колбэки в определённом порядке. Поэтому другие подобные колбэки могут влиять на поведение
:dependent, а поведение:dependentможет влиять на другие колбэки.-
nilничего не делать (по умолчанию). -
:destroyприводит к уничтожению всех связанных объектов. -
:destroy_asyncуничтожает все связанные объекты в фоновом задании. ПРЕДУПРЕЖДЕНИЕ: Не используйте этот параметр, если ассоциация поддерживается ограничениями внешнего ключа в вашей базе данных. Действия ограничения внешнего ключа будут выполняться в той же транзакции, что и удаление владельца. -
:delete_allприводит к удалению всех связанных объектов непосредственно из базы данных (так что колбэки не будут выполнены). -
:nullifyустанавливает внешние ключи в значениеNULL. Полиморфный тип также будет сброшен в полиморфных ассоциациях.Callbacksне выполняются. -
:restrict_with_exceptionприводит к исключению ActiveRecord::DeleteRestrictionError, если существуют какие-либо связанные записи. -
:restrict_with_errorприводит к добавлению ошибки к владельцу, если есть какие-либо связанные объекты.
Если используется параметр
:through, ассоциация в модели соединения должна бытьbelongs_to, и удаляются записи в модели соединения, а не связанные записи.Если используется
dependent: :destroyс ассоциацией с фильтром, уничтожаются только отфильтрованные объекты. Например, если модель Post определяетhas_many :comments, -> { where published: true }, dependent: :destroyи вызываетсяdestroyдля поста, уничтожаются только опубликованные комментарии. Это означает, что любые неопубликованные комментарии в базе данных по-прежнему будут содержать внешний ключ, указывающий на удалённый пост. -
-
:counter_cache -
Этот параметр можно использовать для настройки пользовательского имени
:counter_cache.. Вам нужен только этот параметр, когда вы настраиваете имя своей:counter_cacheв ассоциацииbelongs_to. -
:as -
Устанавливает полиморфный интерфейс (см.
belongs_to). -
:through -
Указывает ассоциацию, через которую выполняется запрос. Это может быть любой другой тип ассоциации, включая другие ассоциации
:through. Параметры:class_name,:primary_keyи:foreign_keyигнорируются, так как ассоциация использует отражение источника.Если ассоциация в модели соединения является
belongs_to, коллекцию можно изменить, и записи в модели:throughбудут автоматически созданы и удалены соответственно. В противном случае коллекция является только для чтения, поэтому вы должны напрямую управлять ассоциацией:through.Если вы собираетесь изменить ассоциацию (а не только читать из неё), рекомендуется установить параметр
:inverse_ofдля исходной ассоциации в модели соединения. Это позволяет создавать связанные записи, которые автоматически создадут соответствующие записи модели соединения при сохранении. (См. разделы «Модели соединения ассоциаций» и «Установление обратных связей» выше.) -
:disable_joins -
Указывает, следует ли пропускать соединения для ассоциации. Если установлено в значение true, будут сгенерированы две или более запросов. Обратите внимание, что в некоторых случаях, если применяется сортировка или ограничение, это будет выполнено в памяти из-за ограничений базы данных. Этот параметр применим только к ассоциациям
has_many :through, так какhas_manyсами по себе не выполняют соединение. -
:source -
Указывает имя исходной ассоциации, используемой
has_many:throughзапросами. Используйте его только в том случае, если имя нельзя определить из ассоциации.has_many :subscribers, through: :subscriptionsбудет искать либо:subscribersлибо:subscriberв Subscription, если не задан:source. -
:source_type -
Указывает тип исходной ассоциации, используемой
has_many:throughзапросами, где исходная ассоциация — полиморфнаяbelongs_to. -
:validate -
При установке в значение
true, новые объекты, добавленные в ассоциацию, валидируются при сохранении родительского объекта.trueпо умолчанию. Если требуется гарантировать перевалидацию связанных объектов при каждом обновлении, используйтеvalidates_associated. -
:autosave -
Если значение true, всегда сохраняются связанные объекты или уничтожаются, если помечены для уничтожения, при сохранении родительского объекта. Если значение false, связанные объекты никогда не сохраняются и не уничтожаются. По умолчанию сохраняются только новые связанные объекты. Этот параметр реализован как колбэк
before_save. Поскольку колбэки выполняются в определённом порядке, связанным объектам может потребоваться явное сохранение в любых пользовательских колбэкахbefore_save.Обратите внимание, что
NestedAttributes::ClassMethods#accepts_nested_attributes_forустанавливает:autosaveв значениеtrue. -
:inverse_of -
Указывает имя ассоциации
belongs_toв связанном объекте, являющейся обратной частью этой ассоциацииhas_many. Смотрите обзор двунаправленных ассоциаций вActiveRecord::Associations::ClassMethodsдля получения более подробной информации. -
:extend -
Указывает модуль или массив модулей, которые будут расширены в возвращённый объект ассоциации. Полезно для определения методов в ассоциациях, особенно когда они должны быть общими для нескольких объектов ассоциаций.
-
:strict_loading -
При установке в значение
true, при каждом загрузке связанной записи через эту ассоциацию, выполняется жёсткая загрузка. -
:ensuring_owner_was -
Указывает метод экземпляра, который должен быть вызван у владельца. Метод должен возвращать true, чтобы связанные записи были удалены в фоновом задании.
-
:query_constraints -
Служит составным внешним ключом. Определяет список столбцов, используемых для запроса связанного объекта. Это необязательный параметр. По умолчанию
Railsпопытается автоматически вывести значение. Когда значение установлено, размерArrayдолжен соответствовать первичному ключу модели-реципиента или размеруquery_constraints. -
:index_errors -
Позволяет различать несколько ошибок валидации от записей ассоциаций, включая индекс в имени атрибута ошибки, например
roles[2].level. Если установлено вtrue, индекс основан на порядке ассоциации, т. е. порядке в базе данных, а ещё не сохранённые новые записи размещаются в конце. Если установлено в:nested_attributes_order, индекс основан на порядке записи, полученном установщиком вложенных атрибутов, когда используется accepts_nested_attributes_for.
Примеры параметров:
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
has_many :comments, dependent: :nullify
has_many :tags, as: :taggable
has_many :reports, -> { readonly }
has_many :subscribers, through: :subscriptions, source: :user
has_many :subscribers, through: :subscriptions, disable_joins: true
has_many :comments, strict_loading: true
has_many :comments, query_constraints: [:blog_id, :post_id]
has_many :comments, index_errors: :nested_attributes_order
# File activerecord/lib/active_record/associations.rb, line 1459 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 -
Возвращает связанный объект, принуждая к чтению из базы данных.
-
reset_association -
Разгружает связанный объект. Следующий доступ обратится к нему из базы данных.
Пример
class Account < ActiveRecord::Base has_one :beneficiary end
Объявление has_one :beneficiary добавляет следующие методы (и больше):
account = Account.find(5) beneficiary = Beneficiary.find(8) account.beneficiary # similar to Beneficiary.find_by(account_id: 5) account.beneficiary = beneficiary # similar to beneficiary.update(account_id: 5) account.build_beneficiary # similar to Beneficiary.new(account_id: 5) account.create_beneficiary # similar to Beneficiary.create(account_id: 5) account.create_beneficiary! # similar to Beneficiary.create!(account_id: 5) account.reload_beneficiary account.reset_beneficiary
Ограничения
Вы можете передать второй аргумент scope как вызываемый объект (например, proc или lambda) для получения конкретной записи или настройки генерируемого запроса при доступе к связанному объекту.
Примеры ограничений:
has_one :author, -> { where(comment_id: 1) }
has_one :employer, -> { joins(:company) }
has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
Параметры
Объявление также может включать хэш options для специализации поведения ассоциации.
Параметры:
-
:class_name -
Укажите имя класса ассоциации. Используйте его только в том случае, если имя не может быть определено из имени ассоциации. Так,
has_one :managerпо умолчанию будет связана с классом Manager, но если фактическое имя класса — Person, вам нужно будет указать его с помощью этого параметра. -
:dependent -
Управляет действиями с ассоциированным объектом при удалении его владельца:
-
nilничего не делать (по умолчанию). -
:destroyприводит к удалению ассоциированного объекта. -
:destroy_asyncприводит к удалению ассоциированного объекта в фоновой задаче. ВНИМАНИЕ: Не используйте этот параметр, если ассоциация поддерживается ограничениями внешних ключей в вашей базе данных. Действия ограничения внешнего ключа будут выполняться в той же транзакции, что и удаление владельца. -
:deleteприводит к прямому удалению ассоциированного объекта из базы данных (следовательно, обратные вызовы не будут выполнены). -
:nullifyприводит к установке внешнего ключа в значениеNULL. Столбец полиморфного типа также обнуляется для полиморфных ассоциаций.Callbacksне будут выполнены. -
:restrict_with_exceptionвызывает исключение ActiveRecord::DeleteRestrictionError, если существует связанная запись. -
:restrict_with_errorдобавляет ошибку к владельцу, если есть ассоциированный объект.
Обратите внимание, что параметр
:dependentигнорируется при использовании параметра:through. -
-
:foreign_key -
Укажите внешний ключ, используемый для ассоциации. По умолчанию он определяется по имени этого класса в нижнем регистре с добавлением суффикса «_id». Например, класс Person, который создает
has_oneассоциацию, будет использовать «person_id» в качестве значения по умолчанию:foreign_key.Установка параметра
:foreign_keyпредотвращает автоматическое обнаружение обратной ассоциации, поэтому рекомендуется также установить параметр:inverse_of. -
:foreign_type -
Укажите столбец, используемый для хранения типа ассоциированного объекта, если это полиморфная ассоциация. По умолчанию он определяется по имени полиморфной ассоциации, указанной в параметре «as», с добавлением суффикса «_type». Например, класс, который определяет
has_one :tag, as: :taggableассоциацию, будет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. -
:primary_key -
Укажите метод, возвращающий первичный ключ, используемый для ассоциации. По умолчанию это
id. -
:as -
Определяет полиморфный интерфейс (см.
belongs_to). -
:through -
Определяет модель соединения, через которую выполняется запрос. Параметры
:class_name,:primary_key, и:foreign_keyигнорируются, так как ассоциация использует рефлексию источника. Вы можете использовать запрос:throughтолько черезhas_oneилиbelongs_toассоциацию в модели соединения.Если ассоциация в модели соединения является
belongs_to, коллекцию можно изменять, и записи в модели:throughбудут автоматически создаваться и удаляться соответствующим образом. В противном случае коллекция является только для чтения, поэтому вы должны напрямую управлять ассоциацией:through.Если вы собираетесь изменить ассоциацию (а не просто читать из неё), рекомендуется установить параметр
:inverse_ofв исходной ассоциации модели соединения. Это позволяет создавать связанные записи, которые автоматически создают соответствующие записи модели соединения при их сохранении. (См. разделы «Модели соединения ассоциаций» и «Установка обратных ассоциаций» выше.) -
:disable_joins -
Определяет, следует ли пропускать соединения для ассоциации. Если значение установлено в true, будет сгенерировано две или более запросов. Обратите внимание, что в некоторых случаях, если применяются сортировка или ограничение, они будут выполнены в памяти из-за ограничений базы данных. Этот параметр применим только к
has_one :throughассоциациям, так какhas_oneсама по себе не выполняет соединение. -
:source -
Указывает имя исходной ассоциации, используемое запросами
has_one:through. Используйте его только в случае, если имя не может быть определено из имени ассоциации.has_one :favorite, through: :favoritesбудет искать:favoriteв Favorite, если не задан:source. -
:source_type -
Определяет тип исходной ассоциации, используемой запросами
has_one:through, где исходная ассоциация является полиморфнойbelongs_to. -
:validate -
При установке в
true, проверяются новые объекты, добавленные в ассоциацию, при сохранении родительского объекта. По умолчаниюfalse. Если вы хотите убедиться, что ассоциированные объекты перепроверяются при каждом обновлении, используйтеvalidates_associated. -
:autosave -
Если
true, всегда сохраняет ассоциированный объект или удаляет его, если он помечен для удаления, при сохранении родительского объекта. Еслиfalse, никогда не сохраняет и не удаляет ассоциированный объект.По умолчанию сохраняется только ассоциированный объект, если это новая запись. Установка этого параметра в значение
trueтакже включает проверку на ассоциированный объект, если явным образом не отключена с помощьюvalidate: false. Это связано с тем, что сохранение объекта с некорректными ассоциированными объектами приведет к ошибке, поэтому все ассоциированные объекты будут проверены.Обратите внимание, что
NestedAttributes::ClassMethods#accepts_nested_attributes_forустанавливает:autosaveвtrue. -
:touch -
Если true, ассоциированный объект будет отмечен (атрибуты
updated_at/updated_onустановлены на текущее время) при сохранении или удалении этой записи. Если вы укажете символ, этот атрибут будет обновлен текущим временем в дополнение к атрибутуupdated_at/updated_on. Обратите внимание, что при маркировке не будет выполнена никакая проверка, и будут выполнены только обратные вызовыafter_touch,after_commit, иafter_rollback. -
:inverse_of -
Определяет имя ассоциации
belongs_toв ассоциированном объекте, которая является обратной к этой ассоциацииhas_one. См. обзор двунаправленных ассоциаций вActiveRecord::Associations::ClassMethodsдля получения дополнительных сведений. -
:required -
При установке в
true, валидация наличия ассоциации также будет включена. Она проверит саму ассоциацию, а не её id. Вы можете использовать:inverse_ofдля избежания дополнительного запроса во время проверки. -
:strict_loading -
Принудительно загружает ассоциированную запись каждый раз при её загрузке через эту ассоциацию.
-
:ensuring_owner_was -
Определяет метод, который вызывается у владельца. Метод должен возвращать true, чтобы ассоциированные записи могли быть удалены в фоновой задаче.
-
:query_constraints -
Используется в качестве составного внешнего ключа. Определяет список столбцов, используемых для запроса ассоциированного объекта. Это необязательный параметр. По умолчанию
Railsпопытается определить значение автоматически. Если значение задано, размерArrayдолжен соответствовать первичному ключу связанной модели или размеруquery_constraints.
Примеры параметров:
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 :club, through: :membership, disable_joins: true
has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
has_one :credit_card, required: true
has_one :credit_card, strict_loading: true
has_one :employment_record_book, query_constraints: [:organization_id, :employee_id]
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.