модуль ActiveRecord::Associations::ClassMethods
Ассоциации представляют собой набор макроподобных методов класса для связывания объектов через внешние ключи. Они выражают отношения, такие как «Проект имеет одного менеджера проекта» или «Проект принадлежит портфолио». Каждый макрос добавляет ряд методов в класс, которые специализируются в соответствии со знаком коллекции или ассоциации и хешем опций. Он работает практически так же, как собственные методы Ruby attr*.
class Project < ActiveRecord::Base belongs_to :portfolio has_one :project_manager has_many :milestones has_and_belongs_to_many :categories end
Класс проекта теперь имеет следующие методы (и другие), чтобы упростить просмотр и обработку его взаимосвязей:
-
Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil? -
Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?, -
Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),Project#milestones.build, Project#milestones.create -
Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),Project#categories.delete(category1), Project#categories.destroy(category1)
Предупреждение
Не создавайте ассоциации, которые имеют такое же имя, как методы экземпляров ActiveRecord::Base. Поскольку ассоциация добавляет метод с этим именем в свою модель, она переопределит унаследованный метод и сломает вещи. Например, attributes и connection были бы плохим выбором для имён ассоциаций.
Автоматически генерируемые методы
Одноэлементные ассоциации (один к одному)
| | 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 Коллекционные ассоциации (один ко многим / многие ко многим)
| | | 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.uniq | X | X | X
others.reset | X | X | X Переопределение сгенерированных методов
Методы ассоциаций генерируются в модуле, который включается в класс модели, что позволяет легко переопределить их своими собственными методами и вызвать исходный сгенерированный метод с помощью super. Например:
class Car < ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
Если ваш класс модели Project, модуль называется Project::GeneratedFeatureMethods. Модуль GeneratedFeatureMethods включается в класс модели сразу после модуля (анонимных) сгенерированных методов атрибутов, что означает, что ассоциация переопределит методы для атрибута с тем же именем.
Мощность и ассоциации
Ассоциации 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 int(11) NOT NULL auto_increment, account_id int(11) default NULL, name varchar default NULL, PRIMARY KEY (id) ) CREATE TABLE accounts ( id int(11) 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
Обработка событий ассоциаций
Аналогично обычным обработчикам, которые подключаются к жизненному циклу объекта Active Record, вы также можете определять обработчики, которые срабатывают при добавлении или удалении объекта из коллекции ассоциаций.
class Project
has_and_belongs_to_many :developers, after_add: :evaluate_velocity
def evaluate_velocity(developer)
...
end
end Можно указать несколько обработчиков, передав их как массив. Пример:
class Project
has_and_belongs_to_many :developers,
after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
end
Возможные обработчики: before_add, after_add, before_remove и after_remove.
Если любой из обработчиков before_add выбросит исключение, объект не будет добавлен в коллекцию. То же касается обработчиков before_remove; если выброшено исключение, объект не удаляется.
Расширения ассоциаций
Объекты-прокси, которые контролируют доступ к ассоциациям, могут быть расширены с помощью анонимных модулей. Это особенно полезно для добавления новых методов поиска, создания и других методов типа фабрики, которые используются только в рамках этой ассоциации.
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
Если вам нужно использовать одни и те же расширения для многих ассоциаций, вы можете использовать именованный модуль расширений.
module FindOrCreateByNameExtension
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
class Account < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
class Company < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
Некоторые расширения могут работать только при знании внутренних деталей ассоциации. Расширения могут получить доступ к соответствующему состоянию, используя следующие методы (где items — имя ассоциации):
-
record.association(:items).owner- Возвращает объект, частью которого является ассоциация. -
record.association(:items).reflection- Возвращает объект рефлексии, который описывает ассоциацию. -
record.association(:items).target- Возвращает связанный объект дляbelongs_toиhas_one, или коллекцию связанных объектов дляhas_manyиhas_and_belongs_to_many.
Однако, внутри фактического кода расширения вы не будете иметь доступа к record как выше. В этом случае вы можете получить доступ к proxy_association. Например, record.association(:items) и record.items.proxy_association вернут один и тот же объект, позволяя вам делать вызовы, такие как proxy_association.owner внутри расширений ассоциаций.
Ассоциации моделей соединения
Ассоциации 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.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
@firm.invoices # selects all invoices by going through the Client join model
Аналогично, вы можете перейти через ассоциацию has_one в модели соединения:
class Group < ActiveRecord::Base
has_many :users
has_many :avatars, through: :users
end
class User < ActiveRecord::Base
belongs_to :group
has_one :avatar
end
class Avatar < ActiveRecord::Base
belongs_to :user
end
@group = Group.first
@group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
@group.avatars # selects all avatars by going through the User join model.
Важное замечание при переходе через ассоциации has_one или has_many в модели соединения заключается в том, что эти ассоциации являются только для чтения. Например, следующее не будет работать после предыдущего примера:
@group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around @group.avatars.delete(@group.avatars.last) # so would this
Установка обратных ссылок
Если вы используете belongs_to в модели соединения, рекомендуется установить опцию :inverse_of в belongs_to, что обеспечит корректную работу следующего примера (где tags — это has_many :through ассоциация):
@post = Post.first @tag = @post.tags.build name: "ruby" @tag.save
Последняя строка должна сохранить запись через (a Taggable). Это будет работать только в том случае, если установлена опция :inverse_of:
class Taggable < ActiveRecord::Base belongs_to :post belongs_to :tag, inverse_of: :taggings end
Если вы не установите запись :inverse_of, ассоциация постарается сопоставить себя с правильным обратным соответствием. Автоматическое определение обратного соответствия работает только с has_many, has_one и belongs_to ассоциациями.
Дополнительные опции в ассоциациях, как определено в константе AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS, также препятствуют автоматическому поиску обратного соответствия ассоциации.
Автоматическое определение обратной ассоциации использует эвристику, основанную на имени класса, поэтому она может не работать со всеми ассоциациями, особенно с теми, которые имеют нестандартные имена.
Вы можете отключить автоматическое определение обратных ассоциаций, установив опцию :inverse_of в false, как показано ниже:
class Taggable < ActiveRecord::Base belongs_to :tag, inverse_of: false end
Вложенные ассоциации
Вы можете указать любую ассоциацию с помощью опции :through, включая ассоциацию, имеющую опцию :through.
Например:
class Author < ActiveRecord::Base has_many :posts has_many :comments, through: :posts has_many :commenters, through: :comments end class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :commenter end @author = Author.first @author.commenters # => People who commented on posts written by the author
Эквивалентный способ настройки этой ассоциации:
class Author < ActiveRecord::Base has_many :posts has_many :commenters, through: :posts end class Post < ActiveRecord::Base has_many :comments has_many :commenters, through: :comments end class Comment < ActiveRecord::Base belongs_to :commenter end
При использовании вложенной ассоциации вы не сможете изменить ассоциацию, так как нет достаточной информации для определения изменения. Например, если вы попытаетесь добавить Commenter в приведенном выше примере, не будет возможности определить, как настроить промежуточные объекты Post и Comment.
Полиморфные ассоциации
Полиморфные ассоциации в моделях не ограничены типами моделей, с которыми они могут быть связаны. Скорее, они задают интерфейс, которому должна соответствовать ассоциация has_many.
class Asset < ActiveRecord::Base belongs_to :attachable, polymorphic: true end class Post < ActiveRecord::Base has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. end @asset.attachable = @post
Это достигается с помощью столбца типа в дополнение к внешнему ключу для указания связанной записи. В примере с активами вам понадобится целочисленный столбец attachable_id и строковый столбец attachable_type.
Использование полиморфных ассоциаций в сочетании с наследованием одной таблицы (STI) немного сложно. Для корректной работы ассоциаций убедитесь, что вы храните базовую модель для моделей STI в столбце типа полиморфной ассоциации. Чтобы продолжить пример с активами, предположим, что гостевые сообщения и сообщения членов используют таблицу сообщений для STI. В этом случае в таблице сообщений должен быть столбец type.
Примечание: метод attachable_type= вызывается при назначении attachable. class_name attachable передается как строка.
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(class_name)
super(class_name.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
Кэширование
Все методы основаны на простом принципе кэширования, которое сохраняет результат последнего запроса, если явно не указано обратное. Кэш даже используется совместно между методами, чтобы сделать макро-добавленные методы еще более дешевыми в использовании, не беспокоясь слишком сильно о производительности сразу.
project.milestones # fetches milestones from the database project.milestones.size # uses the milestone cache project.milestones.empty? # uses the milestone cache project.milestones(true).size # fetches milestones from the database project.milestones # uses the milestone cache
Ленивая загрузка ассоциаций
Ленивая загрузка — способ поиска объектов определенного класса и ряда именованных ассоциаций. Это один из самых простых способов избежать проблемы 1+N, в которой извлечение 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| Это загрузит не только все комментарии, но и всех их авторов и изображения gravatar. Вы можете смешивать и сочетать символы, массивы и массивы в любом сочетании, чтобы описать ассоциации, которые вы хотите загрузить.
Вся эта мощь не должна вводить вас в заблуждение, что вы можете извлечь огромные объемы данных без штрафа за производительность только потому, что вы уменьшили количество запросов. База данных все равно должна отправлять все данные в 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-запросу, а не только к ассоциации.
Если вы хотите загрузить все сообщения (включая сообщения без одобренных комментариев), напишите свой собственный запрос LEFT OUTER JOIN, используя ON
Post.joins('LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = true')
Для этого обратного вызова требуется разделить ссылки на столбцы, например order: "author.name DESC" будет работать, но order: "name DESC" — нет.
Если вы хотите загрузить только некоторые члены ассоциации, обычно более естественно включить ассоциацию, для которой определены условия:
class Post < ActiveRecord::Base
has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
end
Post.includes(:approved_comments)
Это загрузит сообщения и загрузит ассоциацию approved_comments, которая содержит только те комментарии, которые были одобрены.
Если вы загружаете ассоциацию со значением опции :limit, оно будет проигнорировано, возвращая все связанные объекты:
class Picture < ActiveRecord::Base
has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
end
Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
Ленивая загрузка поддерживается полиморфными ассоциациями.
class Address < ActiveRecord::Base belongs_to :addressable, polymorphic: true end
Вызов, который пытается загрузить адресную модель
Address.includes(:addressable)
Это выполнит один запрос для загрузки адресов и загрузит адресные объекты с помощью одного запроса на каждый тип адресного объекта. Например, если все адресные объекты относятся либо к классу Person, либо к классу Company, будет выполнено общее количество 3 запросов. Список типов адресных объектов, подлежащих загрузке, определяется на основе загруженных адресов. Это не поддерживается, если Active Record должен вернуться к предыдущей реализации ленивой загрузки, и вызовет ActiveRecord::EagerLoadPolymorphicError. Причина в том, что тип родительской модели является значением столбца, поэтому имя соответствующей таблицы не может быть помещено в предложения FROM/JOIN этого запроса.
Псевдонимы таблиц
Active Record использует псевдонимы таблиц в случае, если к таблице обращаются несколько раз в объединении. Если к таблице обращаются только один раз, используется стандартное имя таблицы. Во второй раз таблица имеет псевдоним #{reflection_name}_#{parent_table_name}. Индексы добавляются для любых последующих обращений к имени таблицы.
Post.joins(:comments) # => SELECT ... FROM posts INNER JOIN comments ON ... Post.joins(:special_comments) # STI # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
Пример дерева:
TreeMixin.joins(:children)
# => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
TreeMixin.joins(children: :parent)
# => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
INNER JOIN parents_mixins ...
TreeMixin.joins(children: {parent: :children})
# => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
INNER JOIN parents_mixins ...
INNER JOIN mixins childrens_mixins_2 Таблицы соединений «имеет» и «принадлежит многим» используют ту же идею, но добавляют суффикс _join:
Post.joins(:categories)
# => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
Post.joins(categories: :posts)
# => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
Post.joins(categories: {posts: :categories})
# => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
Если вы хотите указать собственные пользовательские соединения, используя метод 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.level == t.dungeon.level # => true d.level = 10 d.level == t.dungeon.level # => false
Объекты Dungeon d и t.dungeon в приведенном выше примере ссылаются на одни и те же данные в базе данных, но на самом деле являются разными копиями этих данных в памяти. Указание опции :inverse_of в ассоциациях позволяет сообщить Active Record об обратных связях, и он оптимизирует загрузку объектов. Например, если мы изменим определения наших моделей на:
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
Тогда, в нашем фрагменте кода выше, d и t.dungeon на самом деле являются одним и тем же экземпляром в памяти, и наш окончательный метод d.level == t.dungeon.level вернет true.
Существуют ограничения поддержки :inverse_of:
-
не работает с
:throughассоциациями. -
не работает с
:polymorphicассоциациями. -
для
belongs_toассоциацийhas_manyобратные ассоциации игнорируются.
Удаление из ассоциаций
Зависимые ассоциации
Ассоциации 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, могут повлиять на ее действие.
Удаление или уничтожение?
Ассоциации 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 не задана, следуют стандартной стратегии. Стандартная стратегия — :nullify (устанавливает внешние ключи в nil), за исключением 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')), вы захотите отсоединить тег «еда» от поста, а не удалить сам тег из базы данных.
Однако есть примеры, где эта стратегия не имеет смысла. Например, предположим, что у человека есть много проектов, и каждый проект имеет много задач. Если мы удалим одну из задач человека, мы, вероятно, не захотим удалить проект. В этом случае метод delete не сработает: он может использоваться только если ассоциация в модели связи — belongs_to. В других ситуациях ожидается выполнение операций непосредственно со связанными записями или ассоциацией :through.
При обычной ассоциации has_many нет различия между «связанными записями» и «связью», поэтому есть только один вариант того, что удаляется.
При has_and_belongs_to_many и has_many :through, если вы хотите удалить сами связанные записи, вы всегда можете сделать что-то вроде person.tasks.each(&:destroy).
Безопасность типов с ActiveRecord::AssociationTypeMismatch
Если вы попытаетесь назначить объект ассоциации, который не соответствует заданному или выявленному типу :class_name, вы получите ошибку ActiveRecord::AssociationTypeMismatch.
Опции
Все макросы ассоциаций можно специализировать с помощью опций. Это усложняет случаи, которые не очевидны и не предусматриваются по умолчанию.
Методы публичного экземпляра
Указывает одно-к-одному ассоциацию с другим классом. Этот метод следует использовать только в том случае, если этот класс содержит внешний ключ. Если внешний ключ содержится в другом классе, используйте has_one вместо этого. См. также обзор ActiveRecord::Associations::ClassMethods о том, когда использовать has_one и когда использовать belongs_to.
Будут добавлены методы для получения и запроса одного связанного объекта, для которого этот объект содержит идентификатор:
- association(force_reload = false)
-
Возвращает связанный объект.
nilвозвращается, если объект не найден. - association=(associate)
-
Присваивает объект associate, извлекает первичный ключ и устанавливает его как внешний ключ.
- build_association(attributes = {})
-
Возвращает новый объект связанного типа, который был создан с
attributesи связан с этим объектом через внешний ключ, но еще не был сохранен. - create_association(attributes = {})
-
Возвращает новый объект связанного типа, который был создан с
attributes, связан с этим объектом через внешний ключ и уже сохранен (если он прошел валидацию). - create_association!(attributes = {})
-
Делает то же самое, что и
create_association, но вызываетActiveRecord::RecordInvalidесли запись некорректна.
(association заменяется символом, переданным в качестве первого аргумента, поэтому belongs_to :author добавит, среди прочего, author.nil?.)
Пример
Класс Post объявляет belongs_to :author, что добавит:
-
Post#author(аналогичноAuthor.find(author_id)) -
Post#author=(author)(аналогичноpost.author_id = author.id) -
Post#build_author(аналогичноpost.author = Author.new) -
Post#create_author(аналогичноpost.author = Author.new; post.author.save; post.author) -
Post#create_author!(аналогичноpost.author = Author.new; post.author.save!; post.author)
Объявление также может включать хеш-объект 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_type
-
Указывает столбец, используемый для хранения типа связанного объекта, если это полиморфная ассоциация. По умолчанию это определяется по имени ассоциации с суффиксом «_type». Таким образом, класс, который определяет ассоциацию
belongs_to :taggable, polymorphic: true, будет использовать «taggable_type» в качестве значения по умолчанию:foreign_type. - :primary_key
-
Указывает метод, который возвращает первичный ключ связанного объекта, используемый для ассоциации. По умолчанию это id.
- :dependent
-
Если установлено в
:destroy, связанный объект уничтожается при уничтожении этого объекта. Если установлено в:delete, связанный объект удаляется **без** вызова метода destroy. Этот параметр не следует указывать, когдаbelongs_toиспользуется в сочетании с отношениемhas_manyв другом классе из-за возможного оставления orphaned записей. - :counter_cache
-
Кэширует количество принадлежащих объектов в классе associate с помощью
increment_counterиdecrement_counter. Кэш счетчика увеличивается при создании объекта этого класса и уменьшается при его уничтожении. Для этого требуется столбец с именем#{table_name}_count(например,comments_countдля класса Comment), используемый в классе associate (например, в классе Post) — т. е. миграция для#{table_name}_countсоздается в классе associate (так чтоPost.comments_countвернет сохраненное значение, см. примечание ниже). Вы также можете указать собственный столбец кэша счетчика, указав имя столбца вместоtrue/falseв этом параметре (например,counter_cache: :my_custom_counter). Примечание: указание кэша счетчика добавит его в список только для чтения атрибутов модели с помощьюattr_readonly. - :polymorphic
-
Указывает, что эта ассоциация является полиморфной, передавая
true. Примечание: если вы включили кэш счетчика, вам может потребоваться добавить атрибут кэша счетчика в списокattr_readonlyв связанных классах (например,class Post; attr_readonly :comments_count; end). - :validate
-
Если
false, не валидировать связанные объекты при сохранении родительского объекта.falseпо умолчанию. - :autosave
-
Если true, всегда сохраняйте связанный объект или уничтожайте его, если он помечен на уничтожение, при сохранении родительского объекта. Если false, никогда не сохраняйте или не уничтожайте связанный объект. По умолчанию сохраняйте связанный объект только в случае создания новой записи.
Обратите внимание, что
accepts_nested_attributes_forустанавливает:autosaveвtrue. - :touch
-
Если true, связанный объект будет touched (атрибуты updated_at/on установлены сейчас) при сохранении или уничтожении этой записи. Если вы укажите символ, этот атрибут будет обновлен текущим временем, помимо атрибута updated_at/on.
- :inverse_of
-
Указывает имя ассоциации
has_oneилиhas_manyв связанном объекте, которая является обратной этой ассоциацииbelongs_to. Не работает в сочетании с параметрами:polymorphic. Подробнее см. в обзоре ActiveRecord::Associations::ClassMethods по двунаправленным ассоциациям.
Примеры параметров:
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: true
belongs_to :post, counter_cache: true
belongs_to :company, touch: true
belongs_to :company, touch: :employees_last_updated_at
# File activerecord/lib/active_record/associations.rb, line 1432
def belongs_to(name, scope = nil, options = {})
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
end Указывает отношение «многие ко многим» с другим классом. Это связывает два класса через промежуточную таблицу соединения. Если таблица соединения не указана явно как опция, она определяется по лексикографическому порядку имён классов. Таким образом, соединение между Разработчиком и Проектом даст имя таблицы соединения по умолчанию «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
def change
create_table :developers_projects, id: false do |t|
t.integer :developer_id
t.integer :project_id
end
end
end
Также рекомендуется добавлять индексы к каждому из этих столбцов для ускорения процесса объединения. Однако в MySQL рекомендуется добавлять составной индекс для обоих столбцов, так как MySQL использует только один индекс на таблицу во время поиска.
Добавляет следующие методы для извлечения и запроса:
- collection(force_reload = false)
-
Возвращает массив всех связанных объектов. Если не найдено, возвращается пустой массив.
- 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::Base.find. - collection.exists?(…)
-
Проверяет, существует ли связанный объект с заданными условиями. Использует те же правила, что и
ActiveRecord::Base.exists?. - collection.build(attributes = {})
-
Возвращает новый объект типа коллекции, который был создан с
attributesи связан с этим объектом через таблицу соединения, но еще не сохранен. - collection.create(attributes = {})
-
Возвращает новый объект типа коллекции, который был создан с
attributes, связан с этим объектом через таблицу соединения и уже сохранен (если он прошёл валидацию).
(collection заменяется символом, переданным в качестве первого аргумента, поэтому has_and_belongs_to_many :categories добавит, среди прочего, categories.empty?.)
Пример
Класс Разработчик объявляет has_and_belongs_to_many :projects, что добавит:
-
Developer#projects -
Developer#projects<< -
Developer#projects.delete -
Developer#projects.destroy -
Developer#projects= -
Developer#project_ids -
Developer#project_ids= -
Developer#projects.clear -
Developer#projects.empty? -
Developer#projects.size -
Developer#projects.find(id) -
Developer#projects.exists?(...) -
Developer#projects.build(аналогичноProject.new("developer_id" => id)) -
Developer#projects.create(аналогичноc = Project.new("developer_id" => id); c.save; c)
Объявление может включать хеш-опций для специализации поведения ассоциации.
Опции
- :class_name
-
Указывает имя класса ассоциации. Используйте его только в том случае, если это имя нельзя вывести из имени ассоциации. Так
has_and_belongs_to_many :projectsпо умолчанию будет связан с классом Project, но если реальное имя класса SuperProject, вам нужно будет указать его с помощью этой опции. - :join_table
-
Указывает имя таблицы соединения, если значение по умолчанию, основанное на лексикографическом порядке, не соответствует вашим требованиям. ВНИМАНИЕ: Если вы перезаписываете имя таблицы одного из классов, метод
table_nameДОЛЖЕН быть объявлен ниже любого объявленияhas_and_belongs_to_manyдля его работы. - :foreign_key
-
Указывает внешний ключ, используемый для ассоциации. По умолчанию это имя этого класса в нижнем регистре и с добавленным суффиксом «_id». Таким образом, класс Person, который создаёт ассоциацию
has_and_belongs_to_manyк Project, будет использовать «person_id» в качестве значения по умолчанию:foreign_key. - :association_foreign_key
-
Указывает внешний ключ, используемый для ассоциации со стороны получателя ассоциации. По умолчанию это имя связанного класса в нижнем регистре и с добавленным суффиксом «_id». Так, если класс Person создаёт ассоциацию
has_and_belongs_to_manyк Project, ассоциация будет использовать «project_id» в качестве значения по умолчанию:association_foreign_key. - :readonly
-
Если true, все связанные объекты будут доступны только для чтения через ассоциацию.
- :validate
-
Если
false, не валидировать связанные объекты при сохранении родительского объекта.trueпо умолчанию. - :autosave
-
Если true, всегда сохранять связанные объекты или уничтожать их, если они помечены для уничтожения, при сохранении родительского объекта. Если false, никогда не сохранять или уничтожать связанные объекты. По умолчанию сохраняются только связанные объекты, которые являются новыми записями.
Обратите внимание, что
accepts_nested_attributes_forустанавливает:autosaveвtrue.
Примеры опций:
has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
has_and_belongs_to_many :nations, class_name: "Country"
has_and_belongs_to_many :categories, join_table: "prods_cats"
has_and_belongs_to_many :categories, -> { readonly }
# File activerecord/lib/active_record/associations.rb, line 1570
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension)
if scope.is_a?(Hash)
options = scope
scope = nil
end
habtm_reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_and_belongs_to_many, name, scope, options, self)
builder = Builder::HasAndBelongsToMany.new name, self, options
join_model = builder.through_model
# FIXME: we should move this to the internal constants. Also people
# should never directly access this constant so I'm not happy about
# setting it.
const_set join_model.name, join_model
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 = [name.to_sym, habtm_reflection]
include Module.new {
class_eval " def destroy_associations
association(:#{middle_reflection.name}).delete_all(:delete_all)
association(:#{name}).reset
super
end
", __FILE__, __LINE__ + 1
}
hm_options = {}
hm_options[:through] = middle_reflection.name
hm_options[:source] = join_model.right_reflection.name
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table].each do |k|
hm_options[k] = options[k] if options.key? k
end
has_many name, scope, hm_options, &extension
self._reflections[name.to_sym].parent_reflection = [name.to_sym, habtm_reflection]
end Определяет ассоциацию один-ко-многим. Будут добавлены следующие методы для получения и запроса коллекций связанных объектов:
- collection(force_reload = false)
-
Возвращает массив всех связанных объектов. Если не найдено ни одного объекта, возвращается пустой массив.
- collection<<(object, …)
-
Добавляет один или несколько объектов в коллекцию, установив их внешние ключи на первичный ключ коллекции. Обратите внимание, что эта операция немедленно выполняет обновление SQL, не дожидаясь вызова save или update для родительского объекта, если только родительский объект не является новой записью.
- collection.delete(object, …)
-
Удаляет один или несколько объектов из коллекции, установив их внешние ключи на
NULL. Объекты также будут дополнительно уничтожены, если они связаны сdependent: :destroy, и удалены, если они связаны сdependent: :delete_all.Если используется опция
:through, то по умолчанию записи соединения удаляются (а не обнуляются), но вы можете указатьdependent: :destroyилиdependent: :nullify, чтобы переопределить это. - collection.destroy(object, …)
-
Удаляет один или несколько объектов из коллекции, выполнив
destroyдля каждой записи, независимо от опции dependent, гарантируя, что будут вызваны обратные вызовы.Если используется опция
:through, то вместо самих объектов уничтожаются записи соединения. - collection=objects
-
Заменяет содержимое коллекции, удаляя и добавляя объекты по мере необходимости. Если опция
:throughимеет значение true, обратные вызовы в моделях соединения вызываются, за исключением обратных вызовов уничтожения, так как удаление происходит напрямую. - 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::Base.find. - collection.exists?(…)
-
Проверяет, существует ли связанный объект с заданными условиями. Использует те же правила, что и
ActiveRecord::Base.exists?. - collection.build(attributes = {}, …)
-
Возвращает один или несколько новых объектов типа коллекции, которые были инициализированы с
attributesи связаны с этим объектом через внешний ключ, но еще не были сохранены. - collection.create(attributes = {})
-
Возвращает новый объект типа коллекции, который был инициализирован с
attributes, связан с этим объектом через внешний ключ и уже сохранен (если прошел проверку). Примечание: Это работает только в том случае, если базовая модель уже существует в базе данных, а не если это новая (несохраненная) запись! - collection.create!(attributes = {})
-
Делает то же, что и
collection.create, но вызываетActiveRecord::RecordInvalid, если запись некорректна.
(Примечание: collection заменяется символом, переданным в качестве первого аргумента, поэтому has_many :clients добавит, среди прочего, clients.empty?.)
Пример
Класс Firm объявляет has_many :clients, что добавит:
-
Firm#clients(аналогичноClient.where(firm_id: id)) -
Firm#clients<< -
Firm#clients.delete -
Firm#clients.destroy -
Firm#clients= -
Firm#client_ids -
Firm#client_ids= -
Firm#clients.clear -
Firm#clients.empty?(аналогичноfirm.clients.size == 0) -
Firm#clients.size(аналогичноClient.count "firm_id = #{id}") -
Firm#clients.find(аналогичноClient.where(firm_id: id).find(id)) -
Firm#clients.exists?(name: 'ACME')(аналогичноClient.exists?(name: 'ACME', firm_id: firm.id)) -
Firm#clients.build(аналогичноClient.new("firm_id" => id)) -
Firm#clients.create(аналогичноc = Client.new("firm_id" => id); c.save; c) -
Firm#clients.create!(аналогичноc = Client.new("firm_id" => id); c.save!)
Объявление также может включать хеш опций для настройки поведения ассоциации.
Опции
- :class_name
-
Укажите имя класса ассоциации. Используйте его только в том случае, если это имя нельзя вывести из имени ассоциации. Так
has_many :productsпо умолчанию будет связан с классом Product, но если реальное имя класса — SpecialProduct, вам нужно указать его с помощью этой опции. - :foreign_key
-
Укажите внешний ключ, используемый для ассоциации. По умолчанию он предполагается по имени этого класса в нижнем регистре и суффиксом «_id». Таким образом, класс Person, который создаёт ассоциацию
has_many, будет использовать «person_id» в качестве значения по умолчанию:foreign_key. - :primary_key
-
Укажите метод, возвращающий первичный ключ, используемый для ассоциации. По умолчанию это
id. - :dependent
-
Управляет тем, что происходит со связанными объектами, когда их владелец уничтожен. Обратите внимание, что они реализуются как обратные вызовы, и Rails выполняет обратные вызовы в порядке. Следовательно, другие подобные обратные вызовы могут повлиять на поведение
:dependent, а поведение:dependentможет повлиять на другие обратные вызовы.-
:destroyприводит к уничтожению всех связанных объектов. -
:delete_allприводит к прямому удалению всех связанных объектов из базы данных (так обратные вызовы не будут выполнены). -
:nullifyприводит к установке внешних ключей наNULL. Обратные вызовы не выполняются. -
:restrict_with_exceptionприводит к сбою, если существуют связанные записи. -
:restrict_with_errorдобавляет ошибку к владельцу, если существуют связанные объекты.
При использовании с опцией
:through, ассоциация в модели соединения должна бытьbelongs_to, а удаляемые записи — это записи соединения, а не связанные записи. -
- :counter_cache
-
Эта опция может использоваться для настройки пользовательского значения
:counter_cache.Вам нужна только эта опция, если вы изменили имя своего:counter_cacheв ассоциацииbelongs_to. - :as
-
Указывает полиморфный интерфейс (см.
belongs_to). - :through
-
Указывает ассоциацию, через которую выполняется запрос. Это может быть любой другой тип ассоциации, в том числе и другие ассоциации
:through.Опции для
:class_name,:primary_keyи:foreign_keyигнорируются, поскольку ассоциация использует отражение источника.Если ассоциация в модели соединения является
belongs_to, коллекция может быть изменена, и записи в модели:throughбудут автоматически создаваться и удаляться по мере необходимости. В противном случае коллекция является только для чтения, поэтому вы должны манипулировать ассоциацией:throughнапрямую.Если вы собираетесь изменять ассоциацию (а не только читать из неё), целесообразно установить опцию
:inverse_ofв исходной ассоциации модели соединения. Это позволяет создавать связанные записи, которые при сохранении будут автоматически создавать соответствующие записи модели соединения. (См. раздел «Модели соединения ассоциаций» выше.) - :source
-
Указывает имя ассоциации источника, используемой запросами
has_many :through. Используйте только в том случае, если имя нельзя вывести из ассоциации.has_many :subscribers, through: :subscriptionsбудет искать либо:subscribers, либо:subscriberв Subscription, если не указан:source. - :source_type
-
Указывает тип ассоциации источника, используемой запросами
has_many :through, где ассоциация источника является полиморфнойbelongs_to. - :validate
-
Если
false, не проверяйте связанные объекты при сохранении родительского объекта. По умолчанию true. - :autosave
-
Если true, всегда сохраняйте связанные объекты или уничтожайте их, если они помечены на уничтожение, при сохранении родительского объекта. Если false, никогда не сохраняйте и не уничтожайте связанные объекты. По умолчанию сохраняются только новые связанные объекты. Эта опция реализуется как обратный вызов
before_save.Поскольку обратные вызовы выполняются в том порядке, в котором они определены, связанным объектам может потребоваться явное сохранение в любых пользовательских обратных вызовах
before_save.Обратите внимание, что
accepts_nested_attributes_forустанавливает:autosaveвtrue. - :inverse_of
-
Указывает имя ассоциации
belongs_toсвязанного объекта, которая является обратной этой ассоциацииhas_many. Не работает в сочетании с опциями:throughили:as. См. Обзор двунаправленных ассоциаций в ActiveRecord::Associations::ClassMethods для получения более подробной информации.
Примеры опций:
has_many :comments, -> { order "posted_on" }
has_many :comments, -> { includes :author }
has_many :people, -> { where("deleted = 0").order("name") }, class_name: "Person"
has_many :tracks, -> { order "position" }, dependent: :destroy
has_many :comments, dependent: :nullify
has_many :tags, as: :taggable
has_many :reports, -> { readonly }
has_many :subscribers, through: :subscriptions, source: :user
# File activerecord/lib/active_record/associations.rb, line 1214
def has_many(name, scope = nil, options = {}, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end Устанавливает однозначное отношение с другим классом. Этот метод следует использовать только если другой класс содержит внешний ключ. Если текущий класс содержит внешний ключ, то следует использовать belongs_to вместо него. Также см. обзор ActiveRecord::Associations::ClassMethods о том, когда использовать has_one и когда использовать belongs_to.
Будут добавлены следующие методы для извлечения и запроса одного связанного объекта:
- association(force_reload = false)
-
Возвращает связанный объект.
nilвозвращается, если объект не найден. - association=(associate)
-
Присваивает объект associate, извлекает первичный ключ, устанавливает его как внешний ключ и сохраняет объект associate. Для предотвращения несоответствий в базе данных, существующий связанный объект постоянно удаляется при назначении нового, даже если новый объект не сохранен в базе данных.
- build_association(attributes = {})
-
Возвращает новый объект связанного типа, который был создан с
attributesи связан с этим объектом через внешний ключ, но еще не сохранен. - create_association(attributes = {})
-
Возвращает новый объект связанного типа, который был создан с
attributes, связан с этим объектом через внешний ключ и уже сохранен (если он прошел валидацию). - create_association!(attributes = {})
-
Делает то же самое, что и
create_association, но вызываетActiveRecord::RecordInvalidесли запись некорректна.
(association заменяется символом, переданным в качестве первого аргумента, поэтому has_one :manager добавит, среди прочего, manager.nil?.)
Пример
Класс Account объявляет has_one :beneficiary, который добавит:
-
Account#beneficiary(аналогичноBeneficiary.where(account_id: id).first) -
Account#beneficiary=(beneficiary)(аналогичноbeneficiary.account_id = account.id; beneficiary.save) -
Account#build_beneficiary(аналогичноBeneficiary.new("account_id" => id)) -
Account#create_beneficiary(аналогичноb = Beneficiary.new("account_id" => id); b.save; b) -
Account#create_beneficiary!(аналогичноb = Beneficiary.new("account_id" => id); b.save!; b)
Параметры
Объявление также может включать хеш параметров для специализации поведения отношения.
Параметры:
- :class_name
-
Укажите имя класса отношения. Используйте его только если это имя нельзя вывести из имени отношения. Так
has_one :managerпо умолчанию будет связан с классом Manager, но если реальное имя класса — Person, вам нужно будет указать его с помощью этого параметра. - :dependent
-
Управляет тем, что происходит со связанным объектом, когда его владелец уничтожается:
-
:destroyприводит к уничтожению связанного объекта -
:deleteприводит к прямому удалению связанного объекта из базы данных (так что обратные вызовы не будут выполнены) -
:nullifyприводит к установке внешнего ключа вNULL. Обратные вызовы не выполняются. -
:restrict_with_exceptionвызывает исключение, если существует связанная запись -
:restrict_with_errorдобавляет ошибку к владельцу, если существует связанный объект
-
- :foreign_key
-
Укажите внешний ключ, используемый для отношения. По умолчанию он предполагается по имени класса в нижнем регистре с добавлением "_id". Так, класс Person, который создает
has_oneотношение, будет использовать “person_id” в качестве стандартного:foreign_key. - :primary_key
-
Укажите метод, возвращающий первичный ключ, используемый для отношения. По умолчанию это
id. - :as
-
Определяет полиморфный интерфейс (см.
belongs_to). - :through
-
Указывает модель объединения, через которую выполняется запрос. Параметры для
:class_name,:primary_key, и:foreign_keyигнорируются, так как отношение использует отражение источника. Вы можете использовать только запрос:throughчерезhas_oneилиbelongs_toотношение в модели объединения. - :source
-
Указывает имя исходного отношения, используемое запросами
has_one :through. Используйте его только если имя нельзя вывести из имени отношения.has_one :favorite, through: :favoritesбудет искать:favoriteв Favorite, если не задан:source. - :source_type
-
Указывает тип исходного отношения, используемого запросами
has_one :through, где исходное отношение является полиморфнымbelongs_to. - :validate
-
Если
false, не валидировать связанный объект при сохранении родительского объекта. По умолчаниюfalse. - :autosave
-
Если true, всегда сохраняйте связанный объект или уничтожайте его, если он помечен на уничтожение, при сохранении родительского объекта. Если false, никогда не сохраняйте или не уничтожайте связанный объект. По умолчанию сохраняется только новый связанный объект.
Обратите внимание, что
accepts_nested_attributes_forустанавливает:autosaveвtrue. - :inverse_of
-
Указывает имя
belongs_toотношения на связанном объекте, которое является обратным этомуhas_oneотношению. Не работает в сочетании с:throughили:asпараметрами. См. обзор двунаправленных отношений в ActiveRecord::Associations::ClassMethods для получения дополнительной информации.
Примеры параметров:
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: :true
has_one :club, through: :membership
has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
# File activerecord/lib/active_record/associations.rb, line 1319
def has_one(name, scope = nil, options = {})
reflection = Builder::HasOne.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
end
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.