Что делает inverse_of? Какой SQL он генерирует?

148

Я пытаюсь осмыслить inverse_of но не понимаю.

Как выглядит сгенерированный sql, если есть?

Имеет ли inverse_ofвыставлять опции такое же поведение , если используется :has_many, :belongs_toи:has_many_and_belongs_to ?

Извините, если это такой простой вопрос.

Я видел такой пример:

class Player < ActiveRecord::Base
  has_many :cards, :inverse_of => :player
end

class Card < ActiveRecord::Base
  belongs_to :player, :inverse_of => :cards
end
Федерико
источник

Ответы:

130

Из документации кажется, что эта :inverse_ofопция - это способ избежать запросов SQL, а не их генерировать. Это подсказка ActiveRecord использовать уже загруженные данные вместо их повторной выборки через связь.

Их пример:

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

В этом случае вызов dungeon.traps.first.dungeonдолжен возвращать исходный dungeonобъект вместо загрузки нового, как это было бы по умолчанию.

тадман
источник
5
Вы понимаете комментарий в документации: «для ассоциаций own_to игнорируются многие обратные ассоциации.». И все же документ использует именно этот пример. Что мне здесь не хватает?
dynex
50
Все это очень странно для меня, потому что мне кажется, что вы всегда хотели бы это поведение по умолчанию, и вам нужно использовать: inverse_of только тогда, когда невозможно вывести имя ассоциации. Также беспокоят несоответствия в определении, но в некоторых случаях это помогло мне. Есть ли причина, по которой я не должен просто повсюду вешать его?
Ibrahim
18
@Ibrahim Проверьте это, он был объединен 23 дня назад! github.com/rails/rails/pull/9522
Hut8
6
Имеет смысл игнорировать инверсию ассоциации own_to, потому что дочерним элементом родительской записи записи A не обязательно является запись A - это может быть брат записи A. Однако родительский элемент дочерней записи A , гарантированно будет рекорд А.
Дэвид Олдридж
2
Будущий читатель может получить помощь из этого блога ...: D
Аруп Ракшит
45

Я думаю, что :inverse_ofэто наиболее полезно, когда вы работаете с ассоциациями, которые еще не сохранились. Например:

class Project < ActiveRecord::Base
  has_many :tasks, :inverse_of=>:project
end

class Task < ActiveRecord::Base
  belongs_to :project, :inverse_of=>:tasks
end

Теперь в консоли:

irb> p = Project.new
=> #<Project id: nil, name: nil, ...>
irb> t = p.tasks.build
=> #<Task id: nil, project_id: nil, ...>
irb> t.project
=> #<Project id: nil, name: nil, ...>

Без :inverse_ofаргументов t.projectфункция вернется nil, поскольку запускает запрос sql, а данные еще не сохранены. С :inverse_ofаргументами данные извлекаются из памяти.

KenB
источник
1
У меня была проблема с accept_nested_attributes_for. По умолчанию отображаются только вложенные атрибуты для существующих связанных объектов (действие редактирования). Если, например, вы хотите СОЗДАТЬ объект, скажем, с 3 связанными объектами, у вас должны быть Model.new (новое действие) и: inverse_of в ваших моделях.
Виктор Маркони
Согласован с поведением в Rails 4 и более поздних версиях, но он отлично работал в v3 (за исключением некоторых более поздних воплощений, хотя старый синтаксис снова работает в v3.2.13). И обратите внимание, что в модели соединения больше нельзя проверить наличие идентификатора - только объект-модель. Кажется, у вас может быть ассоциация без идентификатора в «логике» версии 4.
JosephK
Точно .. :inverse_ofрешил проблему для меня при создании новых родительских и дочерних сущностей в одной и той же форме.
WM
19

После этого pr ( https://github.com/rails/rails/pull/9522 ) inverse_of в большинстве случаев не требуется.

Active Record поддерживает автоматическую идентификацию для большинства ассоциаций со стандартными именами. Однако Active Record не будет автоматически определять двунаправленные ассоциации, которые содержат область действия или любой из следующих параметров:

  • :через
  • :внешний ключ
class Author < ApplicationRecord
  has_many :books, inverse_of: 'writer'
end

class Book < ApplicationRecord
  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end

a = Author.first
b = a.books.first
a.first_name == b.writer.first_name # => true
a.first_name = 'David'
a.first_name == b.writer.first_name # => true

В приведенном выше примере ссылка на один и тот же объект сохраняется в переменной aи в атрибуте writer.

Артамоновев
источник
1
Я использую Rails 5, и добавляете вы inverse_ofили нет, результат a.first_name == b.author.first_nameвсегда будет.
Арслан Али
@ArslanAli спасибо за отличный комментарий, я обновил ответ.
artamonovdev
5

Просто обновление для всех - мы только что использовали inverse_ofодно из наших приложений с has_many :throughассоциацией


По сути, он делает "исходный" объект доступным для "дочернего" объекта.

Итак, если вы используете пример Rails:

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
  validates :id,
      :presence => { :message => "Dungeon ID Required", :unless => :draft? }

  private
  def draft?
      self.dungeon.draft
  end 
end

class EvilWizard < ActiveRecord::Base
  belongs_to :dungeon, :inverse_of => :evil_wizard
end

Использование :inverse_ofпозволит вам получить доступ к объекту данных, противоположному ему, без выполнения каких-либо дальнейших SQL-запросов.

Ричард Пек
источник
5

Когда у нас есть 2 модели с отношениями has_many и own_to, всегда лучше использовать inverse_of, который информирует ActiveRecod о том, что они принадлежат одной стороне ассоциации. Таким образом, если запрос с одной стороны запускается, он будет кэшироваться и обслуживаться из кеша, если он запускается с противоположной стороны. Что улучшает производительность. Начиная с Rails 4.1, inverse_of будет установлен автоматически, если мы используем foreign_key или изменения в имени класса, которые нам нужно установить явно.

Лучшая статья для подробностей и примеров.

http://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations

Гурудатх Б.Н.
источник
3

Если у вас есть has_many_throughсвязь между двумя моделями, User и Role, и вы хотите проверить назначение подключаемой модели по несуществующим или недопустимым записям с помощью validates_presence of :user_id, :role_id, это полезно. Вы по-прежнему можете сгенерировать User @user с его ассоциацией, @user.role(params[:role_id])чтобы сохранение пользователя не привело к неудачной проверке модели присвоения.

Информатом
источник
-1

Пожалуйста, посмотрите 2 два полезных ресурса

И помните о некоторых ограничениях inverse_of:

не работает: через ассоциации.

не работает с: полиморфными ассоциациями.

для ассоциаций принадлежит_то has_many обратные ассоциации игнорируются.

Лео Ле
источник