Spec-Zone.ru › Ruby on Rails 4.1

модуль 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.

Опции

Все макросы ассоциаций можно специализировать с помощью опций. Это усложняет случаи, которые не очевидны и не предусматриваются по умолчанию.

Методы публичного экземпляра

belongs_to(name, scope = nil, options = {}) Показать исходный код

Указывает одно-к-одному ассоциацию с другим классом. Этот метод следует использовать только в том случае, если этот класс содержит внешний ключ. Если внешний ключ содержится в другом классе, используйте 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
has_and_belongs_to_many(name, scope = nil, options = {}, &extension) Показать исходный код

Указывает отношение «многие ко многим» с другим классом. Это связывает два класса через промежуточную таблицу соединения. Если таблица соединения не указана явно как опция, она определяется по лексикографическому порядку имён классов. Таким образом, соединение между Разработчиком и Проектом даст имя таблицы соединения по умолчанию «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
has_many(name, scope = nil, options = {}, &extension) Показать исходный код

Определяет ассоциацию один-ко-многим. Будут добавлены следующие методы для получения и запроса коллекций связанных объектов:

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
has_one(name, scope = nil, options = {}) Показать исходный код

Устанавливает однозначное отношение с другим классом. Этот метод следует использовать только если другой класс содержит внешний ключ. Если текущий класс содержит внешний ключ, то следует использовать 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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API