Подтвердить что ты не робот

Has_many: через: Как вы получаете доступ к атрибутам таблицы соединений?

У меня есть следующие модели в Rails 4 с простым has_many: через объединение:

class Model < ActiveRecord::Base
  has_many :model_options
  has_many :options, through: :model_options
end

class Option < ActiveRecord::Base
  has_many :model_options
  has_many :models, through: :model_options
end

class ModelOption < ActiveRecord::Base
  belongs_to :model
  belongs_to :option
end

Я хочу иметь возможность выполнять итерацию над экземпляром экземпляра Model:

  model = Model.find.first
  model.options.each {}

и получить доступ к атрибутам в таблице соединений.

В Rails 3 вы можете сделать это:

class Model < ActiveRecord::Base
  has_many :model_options
  has_many :options, through: :model_options , select: 'options.*, model_options.*'
end

Но выберите: устарел, и это вызывает предупреждение об отказе.

Тем не менее, сгенерированный SQL содержит данные таблицы ссылок:

SELECT options.*, model_options.* FROM "options"
INNER JOIN "model_options" ON "options"."id" = "model_options"."option_id"
WHERE "model_options"."model_id" = $1 ORDER BY "options".name ASC  [["model_id", 1]]

Но коллекция, возвращенная AR из model.options, удаляет данные таблицы ссылок.

Чтобы удалить предупреждение об изъятиях в Rails 4 и все равно произвести тот же SQL, я сделал следующее:

class Model < ActiveRecord::Base
  has_many :model_options
  has_many :options, -> { select('options.*, model_options.*') }, through: :model_options
end

Итак, запрос верный, но я изо всех сил стараюсь найти правильный способ доступа к данным таблицы ссылок.

Я пробовал разные способы:

 model options
 model.options.joins(:model_options)
 model.options.select('options.*, model_options.*')
 model.model_options.joins(:option)
 ...

Ни один из них не содержит данные таблицы соединений.

Спасибо.

4b9b3361

Ответ 1

Ответ может отличаться от того, что вы хотите достичь. Вы хотите получить эти атрибуты или использовать их для запросов?

Загрузка результатов

ActiveRecord - это сопоставление строк таблицы с объектами, поэтому вы не можете иметь атрибуты из одного объекта в другой.

Позвольте использовать более конкретный пример: есть Дом, Личность и Собака. Человек принадлежит к дому. Собака принадлежит человеку. В доме много людей. В доме много собак через людей.

Теперь, если вам нужно найти собаку, вы не ожидаете наличия в ней персональных атрибутов. Было бы бессмысленно иметь атрибут car_id в атрибутах собаки.

Говоря это, это не проблема: вы действительно хотите, я думаю, избегать создания большого количества запросов db. У Rails ваша спина:

# this will generate a sql query, retrieving options and model_options rows
options = model.options.includes( :model_options )

# no new sql query here, all data is already loaded
option = options.first

# still no new query, everything is already loaded by `#includes`
additional_data = option.model_options.first

Изменить: он будет вести себя как консоль. В самом деле код приложения sql-запрос будет запущен во второй команде, потому что первый не использовал результат (запрос sql запускается только тогда, когда нам нужны его результаты). Но это ничего не меняет: он запускается только один раз.

#includes делает именно это: загрузка всех атрибутов из внешней таблицы в результирующем наборе. Затем все отображается, чтобы иметь осмысленное объектно-ориентированное представление.

Использование атрибутов в запросе

Если вы хотите сделать запрос на основе параметров и ModelOptions, вам нужно будет использовать #references. Скажем, ваш ModelOption имеет атрибут active:

# This will return all Option related to model 
# for which ModelOption is active
model.options.references( :model_options ).where( model_options: { active: true })

Заключение

#includes будет загружать все внешние строки в наборе результатов, чтобы вы могли использовать их позже, без дальнейшего запроса базы данных. #references также позволит вам использовать таблицу в запросах.

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

Ответ 2

Точно так же, как Оливье сказал, что вам нужно заряжать ассоциацию.

По какой-то причине ассоциация не возвращает отдельные данные model_options, когда вы используете включает.

Он работает для меня, когда я заставляю AR делать один запрос с eager_load.

options = model.options.eager_load( :model_options )

# then
data = options.first.model_options.first