модуль ActiveRecord::Associations::ClassMethods
Ассоциации Active Record
Ассоциации — это набор похожих на макросы методов класса, которые связывают объекты с помощью внешних ключей. Они описывают отношения, например «у проекта есть руководитель» или «проект принадлежит портфелю». Каждый макрос добавляет в класс несколько методов, специализированных в соответствии с символом коллекции или ассоциации и хешем параметров. Это работает почти так же, как собственные методы attr* в Ruby.
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?
Обе ассоциации описывают связь «один к одному». Разница заключается главным образом в том, где размещается внешний ключ: он находится в таблице класса, объявляющего отношение 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) )
Несохранённые объекты и ассоциации
Вы можете работать с объектами и ассоциациями до их сохранения в базе данных, однако следует учитывать некоторые особенности поведения, в основном связанные с сохранением связанных объектов.
Для ассоциации has_one, belongs_to, has_many или has_and_belongs_to_many можно задать параметр :autosave. Значение 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(client)
# ...
end
def log_after_remove(client)
# ...
end
end
Callbacks можно определить тремя способами:
-
Символом, указывающим на метод, определённый в классе, содержащем связанную коллекцию. Например,
after_add: :congratulate_clientвызываетFirm#congratulate_client(client). -
Вызываемым объектом с сигнатурой, принимающей как запись со связанной коллекцией, так и добавляемую или удаляемую запись. Например,
after_add: ->(firm, client) { ... }. -
Объектом, который отвечает на имя обратного вызова. Например, передача
after_add: CallbackObject.newвызываетCallbackObject#after_add(firm, client).
Обратные вызовы можно объединять в цепочку, передав их массивом. Пример:
class CallbackObject
def after_add(firm, client)
firm.log << "after_adding #{client.id}"
end
end
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: [
:congratulate_client,
-> (firm, client) { firm.log << "after_adding #{client.id}" },
CallbackObject.new
],
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 в расширениях ассоциаций.
Промежуточные модели ассоциаций
Ассоциации Has Many можно настроить с помощью параметра :through, чтобы извлекать данные через явную промежуточную модель. Это работает аналогично ассоциации has_and_belongs_to_many. Преимущество такого подхода в том, что к промежуточной модели можно добавить валидации, обратные вызовы и дополнительные атрибуты. Рассмотрим следующую схему:
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
end
class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :book
end
@author = Author.first
@author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
@author.books # selects all books by using the Authorship join model
Также можно пройти через ассоциацию has_many промежуточной модели:
class Firm < ActiveRecord::Base
has_many :clients
has_many :invoices, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :client
end
@firm = Firm.first
@firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
@firm.invoices # selects all invoices by going through the Client join model
Аналогично, можно пройти через ассоциацию has_one промежуточной модели:
class Group < ActiveRecord::Base
has_many :users
has_many :avatars, through: :users
end
class User < ActiveRecord::Base
belongs_to :group
has_one :avatar
end
class Avatar < ActiveRecord::Base
belongs_to :user
end
@group = Group.first
@group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
@group.avatars # selects all avatars by going through the User join model.
Важно учитывать, что ассоциации has_one или has_many, проходящие через промежуточную модель, доступны только для чтения. Например, следующий код не сработает в соответствии с предыдущим примером:
@group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around @group.avatars.delete(@group.avatars.last) # so would this
Настройка обратных ассоциаций
Если в промежуточной модели используется belongs_to, рекомендуется задать параметр :inverse_of для ассоциации belongs_to. Тогда следующий пример будет работать правильно (где tags — ассоциация has_many :through):
@post = Post.first @tag = @post.tags.build name: "ruby" @tag.save
Последняя строка должна сохранить промежуточную запись (Tagging). Это сработает только в том случае, если задан :inverse_of:
class Tagging < ActiveRecord::Base belongs_to :post belongs_to :tag, inverse_of: :taggings end
Если не задать запись :inverse_of, ассоциация попытается самостоятельно найти подходящую обратную ассоциацию. Автоматическое определение обратной ассоциации работает только для ассоциаций has_many, has_one и belongs_to.
Параметры :foreign_key и :through ассоциаций также не позволяют автоматически найти обратную ассоциацию; в некоторых случаях этому препятствуют и пользовательские области видимости. Дополнительные сведения см. в руководстве по ассоциациям Active Record.
Автоматический подбор обратной ассоциации основан на эвристике, использующей имя класса, поэтому он может работать не для всех ассоциаций, особенно если их имена нестандартны.
Автоматическое определение обратных ассоциаций можно отключить, задав параметру :inverse_of значение false, например:
class Tagging < ActiveRecord::Base belongs_to :tag, inverse_of: false end
Вложенные ассоциации
С помощью параметра :through можно указать любую ассоциацию, в том числе ассоциацию, у которой самой есть параметр :through. Например:
class Author < ActiveRecord::Base has_many :posts has_many :comments, through: :posts has_many :commenters, through: :comments end class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :commenter end @author = Author.first @author.commenters # => People who commented on posts written by the author
Эту ассоциацию можно настроить и эквивалентным способом:
class Author < ActiveRecord::Base has_many :posts has_many :commenters, through: :posts end class Post < ActiveRecord::Base has_many :comments has_many :commenters, through: :comments end class Comment < ActiveRecord::Base belongs_to :commenter end
При использовании вложенной ассоциации изменить её не получится, поскольку недостаточно информации, чтобы определить, какие изменения нужно внести. Например, при попытке добавить Commenter в приведённом выше примере невозможно определить, как настроить промежуточные объекты Post и Comment.
Полиморфные ассоциации
Полиморфные ассоциации в моделях не ограничивают типы моделей, с которыми они могут быть связаны. Вместо этого они задают интерфейс, которому должна соответствовать ассоциация has_many.
class Asset < ActiveRecord::Base belongs_to :attachable, polymorphic: true end class Post < ActiveRecord::Base has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. end @asset.attachable = @post
Это работает за счёт использования столбца типа наряду с внешним ключом для указания связанной записи. В примере с Asset нужны целочисленный столбец attachable_id и строковый столбец attachable_type.
Использовать полиморфные ассоциации вместе с наследованием с одной таблицей (STI) немного сложнее. Чтобы ассоциации работали ожидаемым образом, убедитесь, что в столбце типа полиморфной ассоциации хранится базовая модель моделей STI. Продолжая пример с Asset выше, предположим, что в таблице posts для STI хранятся гостевые и пользовательские публикации. В этом случае в таблице posts должен быть столбец type.
Примечание. При присваивании attachable вызывается метод attachable_type=. attachable объекта class_name передаётся как 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. В общем случае число запросов будет равно единице плюс количеству именованных ассоциаций (кроме случаев, когда некоторые ассоциации belongs_to являются полиморфными; см. ниже).
Чтобы включить глубокую иерархию ассоциаций, используйте хеш:
Post.includes(:author, { comments: { author: :gravatar } }).each do |post| Приведённый выше код загрузит все комментарии, а также всех связанных с ними авторов и граватары. Для получения нужных ассоциаций можно комбинировать символы, массивы и хеши.
Не следует считать, что благодаря сокращению числа запросов можно без потери производительности извлекать огромные объёмы данных. База данных всё равно должна передать все данные Active Record, которому также придётся их обработать. Поэтому предварительная загрузка не решает все проблемы производительности, но это отличный способ сократить число запросов в описанной выше ситуации.
Поскольку за раз загружается только одна таблица, условия или сортировка не могут ссылаться на таблицы, отличные от основной. В таких случаях Active Record возвращается к ранее использовавшемуся подходу на основе LEFT OUTER JOIN. Например:
Post.includes([:author, :comments]).where(['comments.approved = ?', true])
В результате будет выполнен один SQL-запрос с объединениями примерно такого вида: LEFT OUTER JOIN comments ON comments.post_id = posts.id и LEFT OUTER JOIN authors ON authors.id = posts.author_id. Обратите внимание, что использование таких условий может привести к непредвиденным последствиям. В приведённом выше примере публикации без одобренных комментариев вообще не возвращаются, поскольку условия применяются ко всему SQL-выражению, а не только к ассоциации.
Чтобы сработал этот запасной вариант, необходимо уточнить ссылки на столбцы: например, сработает order: "author.name DESC", но не order: "name DESC".
Чтобы загрузить все публикации, в том числе без одобренных комментариев, составьте собственный запрос LEFT OUTER JOIN с помощью ON:
Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
В этом случае обычно естественнее включить ассоциацию, для которой задано условие:
class Post < ActiveRecord::Base
has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
end
Post.includes(:approved_comments)
Этот код загрузит публикации и предварительно загрузит ассоциацию approved_comments, содержащую только одобренные комментарии.
Если при предварительной загрузке ассоциации задан параметр :limit, он будет проигнорирован, и будут возвращены все связанные объекты:
class Picture < ActiveRecord::Base
has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
end
Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
Предварительная загрузка поддерживается для полиморфных ассоциаций.
class Address < ActiveRecord::Base belongs_to :addressable, polymorphic: true end
Вызов, пытающийся предварительно загрузить модель addressable:
Address.includes(:addressable)
Будет выполнен один запрос для загрузки адресов и по одному запросу для загрузки addressable каждого типа. Например, если все addressable относятся к классу Person или Company, всего будет выполнено 3 запроса. Список типов addressable для загрузки определяется на основе загруженных адресов. Этот способ не поддерживается, если Active Record приходится использовать предыдущую реализацию предварительной загрузки; в таком случае будет вызвано исключение ActiveRecord::EagerLoadPolymorphicError. Причина в том, что тип родительской модели — это значение столбца, поэтому соответствующее имя таблицы невозможно указать в предложениях FROM/JOIN этого запроса.
Псевдонимы таблиц
Active Record использует псевдонимы таблиц, когда таблица упоминается в объединении несколько раз. Если таблица упоминается только один раз, используется её обычное имя. При втором упоминании ей назначается псевдоним #{reflection_name}_#{parent_table_name}. При каждом последующем упоминании к имени таблицы добавляется индекс.
Post.joins(:comments) # SELECT ... FROM posts INNER JOIN comments ON ... Post.joins(:special_comments) # STI # SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name # SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
Пример с древовидной структурой:
TreeMixin.joins(:children)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
TreeMixin.joins(children: :parent)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
TreeMixin.joins(children: {parent: :children})
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# INNER JOIN mixins childrens_mixins_2
В таблицах соединения Has and Belongs to Many используется тот же принцип, но добавляется суффикс _join:
Post.joins(:categories)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
Post.joins(categories: :posts)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
Post.joins(categories: {posts: :categories})
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
Если вы хотите задать собственные объединения с помощью метода ActiveRecord::QueryMethods#joins, их имена таблиц будут иметь приоритет перед именами ассоциаций, загружаемых заранее:
Post.joins(:comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
Post.joins(:comments, :special_comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
Псевдонимы таблиц автоматически сокращаются с учётом максимальной длины идентификаторов таблиц, установленной для конкретной базы данных.
Модули
По умолчанию ассоциации ищут объекты в текущей области видимости модуля. Рассмотрим следующий пример:
module MyApplication
module Business
class Firm < ActiveRecord::Base
has_many :clients
end
class Client < ActiveRecord::Base; end
end
end
При вызове Firm#clients он, в свою очередь, вызовет MyApplication::Business::Client.find_all_by_firm_id(firm.id). Чтобы связать модель с классом из другой области видимости модуля, укажите полное имя класса.
module MyApplication
module Business
class Firm < ActiveRecord::Base; end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :firm, class_name: "MyApplication::Business::Firm"
end
end
end
Двунаправленные ассоциации
При объявлении ассоциации в связанной модели обычно также объявляется ассоциация, описывающая ту же связь в обратном направлении. Например, для следующих моделей:
class Dungeon < ActiveRecord::Base has_many :traps has_one :evil_wizard end class Trap < ActiveRecord::Base belongs_to :dungeon end class EvilWizard < ActiveRecord::Base belongs_to :dungeon end
Ассоциация traps в Dungeon и ассоциация dungeon в Trap являются обратными друг другу; обратной ассоциацией для dungeon в EvilWizard является evil_wizard в Dungeon (и наоборот). По умолчанию Active Record может определить обратную ассоциацию по имени класса. Результат будет следующим:
d = Dungeon.first t = d.traps.first d.object_id == t.dungeon.object_id # => true
Экземпляры Dungeon d и t.dungeon в приведённом выше примере ссылаются на один и тот же экземпляр в памяти, поскольку имя ассоциации соответствует имени класса. Результат был бы таким же, если бы мы добавили :inverse_of в определения наших моделей:
class Dungeon < ActiveRecord::Base has_many :traps, inverse_of: :dungeon has_one :evil_wizard, inverse_of: :dungeon end class Trap < ActiveRecord::Base belongs_to :dungeon, inverse_of: :traps end class EvilWizard < ActiveRecord::Base belongs_to :dungeon, inverse_of: :evil_wizard end
Дополнительные сведения см. в документации по параметру :inverse_of и в руководстве по ассоциациям 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')) вы захотите отвязать тег «food» от публикации, а не удалить сам тег из базы данных.
Однако есть примеры, в которых эта стратегия не имеет смысла. Например, предположим, что у человека много проектов, а в каждом проекте много задач. Если удалить одну из задач человека, мы, вероятно, не захотим удалять проект. В этом сценарии метод удаления фактически не сработает: его можно использовать, только если ассоциация в модели соединения является belongs_to. В других ситуациях предполагается, что операции выполняются непосредственно с ассоциированными записями или ассоциацией :through.
При использовании обычной ассоциации has_many различий между «ассоциированными записями» и «связью» нет, поэтому удалить можно только что-то одно.
При использовании has_and_belongs_to_many и has_many :through, если нужно удалить сами ассоциированные записи, всегда можно сделать что-то вроде person.tasks.each(&:destroy).
Устаревшие ассоциации
Ассоциации можно пометить как устаревшие, передав deprecated: true:
has_many :posts, deprecated: true
При использовании устаревшей ассоциации с помощью логгера Active Record выводится предупреждение, хотя настройки конфигурации позволяют задать дополнительные параметры.
Сообщение содержит контекст, помогающий понять, где использовалась устаревшая ассоциация:
The association Author#posts is deprecated, the method post_ids was invoked (...) The association Author#posts is deprecated, referenced in query to preload records (...)
В приведённых выше примерах точки обозначают место на уровне приложения, где произошло использование, чтобы помочь определить, что вызвало предупреждение. Это место вычисляется с помощью средства очистки трассировки стека Active Record.
Что считается использованием?
-
Вызов любых методов ассоциации, например
posts,posts=и т. д. -
Если ассоциация принимает вложенные атрибуты — присваивание этим атрибутам.
-
Если ассоциация является ассоциацией through, а некоторые из её вложенных ассоциаций устарели, предупреждения будут выводиться каждый раз при использовании ассоциации through верхнего уровня. Это происходит независимо от того, устарела ли сама ассоциация through.
-
Выполнение запросов, в которых используется ассоциация. Например, выполнение
eager_load(:posts),joins(author: :posts)и т. д. -
Если для ассоциации задан параметр
:dependent, при уничтожении ассоциированной записи выводятся предупреждения (поскольку это приводит к побочному эффекту, которого не было бы при удалении ассоциации). -
Если для ассоциации задан параметр
:touch, при сохранении или уничтожении записи выводится предупреждение (поскольку это приводит к побочному эффекту, которого не было бы при удалении ассоциации).
Действия, которые НЕ вызывают предупреждений
Большинство приведённых ниже пограничных случаев объясняется тем, что Active Record обращается к ассоциациям лениво, при их использовании. До этого ссылка на ассоциацию — по сути, просто символ Ruby.
-
Если ассоциация
postsустарела,has_many :comments, through: :postsне вызывает предупреждение. Использование ассоциацииcommentsрегистрируется как использованиеposts, как объяснялось выше, но само объявлениеhas_manyэтого не делает. -
Аналогично,
accepts_nested_attributes_for :postsне вызывает предупреждение. При присваивании атрибутов posts предупреждение выводится, как объяснялось выше, но сам вызовaccepts_nested_attributes_forэтого не делает. -
То же относится к случаю, когда ассоциация объявлена обратной по отношению к устаревшей: сам макрос не вызывает предупреждение.
-
Аналогично, само объявление
validates_associated :postsне вызывает предупреждение, хотя при выполнении проверки регистрируется обращение. -
Методы запросов
Relation, такие какAuthor.includes(:posts), сами по себе не вызывают предупреждение. На этом этапе это отношение, которое внутренне хранит символ для последующего использования. Как объяснялось в предыдущем разделе, предупреждение выводится, когда запрос выполняется. -
Обращение к объекту отражения ассоциации, например
Author.reflect_on_association(:posts)илиAuthor.reflect_on_all_associations, не вызывает предупреждение.
Конфигурация
Вывод сообщений об использовании устаревших ассоциаций можно настроить:
config.active_record.deprecated_associations_options = { ... } Если параметр задан, он должен быть хешем с ключами :mode и/или :backtrace.
Режим
-
В режиме
:warnпри использовании выводится предупреждение, содержащее место на уровне приложения, где произошло обращение, если такое место известно. Это режим по умолчанию. -
В режиме
:raiseпри использовании возникает исключениеActiveRecord::DeprecatedAssociationErrorс аналогичным сообщением и очищенной трассировкой стека в объекте исключения. -
В режиме
:notifyпубликуется уведомление Active Supportdeprecated_association.active_record. Полезная нагрузка события содержит отражение ассоциации (:reflection), место на уровне приложения (:location), где произошло обращение (объект Thread::Backtrace::Location илиnil), и сообщение об устаревании (:message).
Трассировка стека
Если значение :backtrace равно true, предупреждения содержат очищенную трассировку стека в сообщении, а полезная нагрузка уведомлений включает ключ :backtrace со списком очищенных объектов Thread::Backtrace::Location. Для исключений всегда задаётся очищенная трассировка стека.
Очистка трассировки стека выполняется с помощью средства очистки трассировки стека Active Record. В приложениях Rails по умолчанию используется то же средство, что и Rails.backtrace_cleaner.
Проверка типов с помощью ActiveRecord::AssociationTypeMismatch
Если попытаться присвоить ассоциации объект, тип которого не соответствует выведенному или заданному :class_name, возникнет исключение ActiveRecord::AssociationTypeMismatch.
Параметры
Все макросы ассоциаций можно настраивать с помощью параметров. Это позволяет обрабатывать более сложные случаи, выходящие за рамки простых и предсказуемых вариантов.
Публичные методы экземпляра
# File activerecord/lib/active_record/associations.rb, line 1824 def belongs_to(name, scope = nil, **options) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection(self, name, reflection) end
Задаёт связь один-к-одному с другим классом. Этот метод следует использовать только в том случае, если внешний ключ содержится в этом классе. Если внешний ключ содержится в другом классе, вместо него следует использовать has_one. Дополнительные сведения о том, когда использовать has_one, а когда — belongs_to, см. в разделе Какую ассоциацию выбрать: belongs_to или has_one?.
Будут добавлены методы для получения и поиска одного ассоциированного объекта, идентификатор которого хранится в этом объекте:
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, его нужно указать с помощью этого параметра. Параметр:class_nameне поддерживается для полиморфных ассоциаций, поскольку в этом случае имя класса ассоциированной записи хранится в столбце типа. :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в другом классе, поскольку это может привести к появлению осиротевших записей. :counter_cache-
Кэширует количество принадлежащих объектов в ассоциированном классе с помощью методов
CounterCache::ClassMethods#increment_counterиCounterCache::ClassMethods#decrement_counter. Кэш счётчика увеличивается при создании объекта этого класса и уменьшается при его уничтожении. Для этого в ассоциированном классе (например, в классе Post) должен использоваться столбец с именем#{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. Дополнительные сведения см. в разделе Двунаправленные ассоциации. :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. :deprecated-
Если значение равно true, ассоциация помечается как устаревшая. Использование устаревших ассоциаций регистрируется. Подробности см. выше в документации класса.
Примеры параметров:
belongs_to :firm, foreign_key: "client_of"
belongs_to :person, primary_key: "name", foreign_key: "person_name"
belongs_to :author, class_name: "Person", foreign_key: "author_id"
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
class_name: "Coupon", foreign_key: "coupon_id"
belongs_to :attachable, polymorphic: true
belongs_to :project, -> { readonly }
belongs_to :post, counter_cache: true
belongs_to :comment, touch: true
belongs_to :company, touch: :employees_last_updated_at
belongs_to :user, optional: true
belongs_to :account, default: -> { company.account }
belongs_to :account, strict_loading: true
belongs_to :note, query_constraints: [:organization_id, :note_id]
# File activerecord/lib/active_record/associations.rb, line 2008
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, :deprecated].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[8.1]
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.
Пример
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-
Каждый раз при загрузке ассоциированной записи через эту ассоциацию принудительно включается строгая загрузка.
:deprecated-
Если значение равно 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 }
has_and_belongs_to_many :categories, strict_loading: true
# File activerecord/lib/active_record/associations.rb, line 1427 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-запрос UPDATE, не дожидаясь вызова save или update для родительского объекта, если только родительский объект не является новой записью. Также будут запущены проверки и обратные вызовы связанных объектов.
collection.delete(object, ...)-
Удаляет один или несколько объектов из коллекции, устанавливая их внешние ключи в значение
NULL. Объекты также будут уничтожены, если они связаны сdependent: :destroy, и удалены, если они связаны сdependent: :delete_all.Если используется параметр
:through, по умолчанию записи соединения удаляются (а не обнуляются), но это поведение можно переопределить, указавdependent: :destroyилиdependent: :nullify. collection.destroy(object, ...)-
Удаляет один или несколько объектов из коллекции, вызывая
destroyдля каждой записи независимо от параметра зависимого удаления и гарантируя запуск обратных вызовов.Если используется параметр
: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.
Пример
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 можно передать как вызываемый объект (например, proc или lambda), чтобы получать определённый набор записей или настраивать создаваемый запрос при обращении к связанной коллекции.
Примеры областей видимости:
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
Расширения
Аргумент extension позволяет передать блок в связь has_many. Это полезно для добавления новых методов поиска, создания и других фабричных методов, используемых в рамках связи.
Примеры расширений:
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
Параметры
:class_name-
Задаёт имя класса связи. Используйте этот параметр, только если имя нельзя определить по имени связи. Например,
has_many :productsпо умолчанию будет связано с классомProduct, но если фактическое имя класса —SpecialProduct, его нужно указать с помощью этого параметра. :foreign_key-
Задаёт внешний ключ, используемый для связи. По умолчанию предполагается, что это имя данного класса в нижнем регистре с суффиксом «_id». Поэтому класс Person, в котором определена связь
has_many, по умолчанию будет использовать «person_id» в качестве:foreign_key.Задание параметра
: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будет искать на Subscription либо:subscribers, либо:subscriber, если не задан параметр: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. Подробнее см. в разделе Двунаправленные связи. :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. - :before_add
-
Определяет обратный вызов связи, который срабатывает перед добавлением объекта в коллекцию связи.
- :after_add
-
Определяет обратный вызов связи, который срабатывает после добавления объекта в коллекцию связи.
- :before_remove
-
Определяет обратный вызов связи, который срабатывает перед удалением объекта из коллекции связи.
- :after_remove
-
Определяет обратный вызов связи, который срабатывает после удаления объекта из коллекции связи.
:deprecated-
Если значение равно true, помечает связь как устаревшую. Использование устаревших связей регистрируется. Подробнее см. в документации класса выше.
Примеры параметров:
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
has_many :comments, dependent: :nullify
has_many :tags, as: :taggable
has_many :reports, -> { readonly }
has_many :subscribers, through: :subscriptions, source: :user
has_many :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 1628 def has_one(name, scope = nil, **options) reflection = Builder::HasOne.build(self, name, scope, options) Reflection.add_reflection(self, name, reflection) end
Определяет связь «один к одному» с другим классом. Этот метод следует использовать, только если внешний ключ находится в другом классе. Если внешний ключ находится в текущем классе, вместо этого используйте belongs_to. Подробнее о том, когда использовать has_one, а когда belongs_to, см. в разделе Это связь belongs_to или has_one?.
Будут добавлены следующие методы для получения и запроса одного связанного объекта:
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-
Задаёт связь, через которую выполняется запрос.
Связь through должна быть
has_one,has_one :throughили неполиморфнойbelongs_to. Иными словами, это должна быть неполиморфная одиночная связь. Параметры: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. Подробнее см. в разделе Двунаправленные связи. :required-
Если задано значение
true, также проверяется наличие связи. Проверяется сама связь, а не идентификатор. Можно использовать:inverse_of, чтобы избежать дополнительного запроса во время проверки. :strict_loading-
При каждой загрузке связанной записи через эту связь применяется строгая загрузка.
:ensuring_owner_was-
Задаёт метод экземпляра, вызываемый у владельца. Метод должен возвращать true, чтобы связанные записи могли быть удалены в фоновой задаче.
:query_constraints-
Используется как составной внешний ключ. Определяет список столбцов, используемых для запроса связанного объекта. Этот параметр необязателен. По умолчанию
Railsпопытается определить значение автоматически. Если значение задано, размерArrayдолжен совпадать с размером первичного ключа связанной модели илиquery_constraints. :deprecated-
Если значение равно true, помечает связь как устаревшую. Использование устаревших связей регистрируется. Подробнее см. в документации класса выше.
Примеры параметров:
has_one :credit_card, dependent: :destroy # destroys the associated credit card
has_one :credit_card, dependent: :nullify # updates the associated records foreign
# key value to NULL rather than destroying it
has_one :last_comment, -> { order('posted_on desc') }, 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.