Rails нетерпеливо загружает has_many через таблицу соединений

Моя среда разработки находится на Rails 4.1 и postgresql.

У меня есть 3 модели с has_many через отношения:

class Item < ActiveRecord::Base
  has_many :item_parts
  has_many :parts, through: :item_parts
end

class Part < ActiveRecord::Base
  has_many :item_parts
  has_many :items, through: :item_parts
end

class ItemPart < ActiveRecord::Base
  belongs_to :item
  belongs_to :part 
end

Таблица соединения item_parts имеет атрибут unit_count для отслеживания количества частей, содержащихся в элементе.

Когда в представлении я перебираю все части определенного элемента, мне также нужно получить значение unit_count из таблицы соединений.

Это дает мне проблему с запросом n + 1.

Я попытался загрузить таблицу соединений:

item.parts.includes(:item_parts)

ItemPart Load (0.5ms)  SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" IN (24, 12, 3, 26)

но когда после того, как я это сделаю:

part.item_parts.where(item_id: item.id).first.unit_count

Я получил следующий запрос sql для каждой части; нетерпеливая загрузка не сработала

ItemPart Load (0.5ms)  SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" = $1 AND "item_parts"."item_id" = $2  [["part_id", 24], ["item_id", 18]]

Какие-либо предложения?


person harlock975    schedule 23.06.2015    source источник


Ответы (3)


Перейдя по этой ссылке, я нашел решение для своего случая: Rails Eager Loading and where

Я не знал метод запроса Active Record: Select (http://apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/select)

Кажется, мне не удалось загрузить таблицу соединения, потому что после включения item_parts я использую предложение where во время итерации частей элемента.

Кажется, что предложение where делает новый запрос к базе данных каждый раз, когда оно используется во время итерации.

Вместо этого метод select не обращается к БД каждый раз, а работает с коллекцией Active Record, загруженной в память.

Итак, я изменил это:

item.parts.includes(:item_parts)
part.item_parts.where(item_id: item.id).first.unit_count

с этим:

item.parts.includes(:item_parts)
part.item_parts.select{|item_part| item_part.item_id == item.id}.first.unit_count

а затем у меня есть один запрос для загрузки item_parts:

ItemPart Load (0.5ms)  SELECT "item_parts".* FROM "item_parts" WHERE "item_parts"."part_id" IN (24, 12, 3, 26)
person harlock975    schedule 24.06.2015

Я ожидаю, что это потому, что вы используете метод подсчета, который, согласно:

http://dev.mensfeld.pl/2014/09/activerecord-count-vs-length-vs-size-and-what-will-happen-if-you-use-it-the-way-вы-недолжны/

не сохраняется внутри в течение жизненного цикла объекта, а это означает, что каждый раз, когда мы вызываем этот метод, SQL-запрос выполняется снова

Вместо этого попробуйте использовать методы .length или .size.

Примечание: (цитирую статью)

длина – коллекция.длина

  • Возвращает длину коллекции без выполнения дополнительных запросов… до тех пор, пока коллекция загружена
  • Когда у нас есть ленивая загрузка коллекции, length загрузит всю коллекцию в память, а затем вернет ее длину.
  • Может использовать всю вашу память при неправильном использовании
  • Очень быстро, когда у вас жадно загруженная коллекция

размер – collection.size

  • Сочетает в себе возможности обоих предыдущих методов;
  • Если коллекция загружена, будет подсчитаны ее элементы (без дополнительного запроса)
  • Если коллекция не загружена, выполнит дополнительный запрос
person andyleesuk    schedule 23.06.2015

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

@items = Item.includes(:parts)

@items.each do |item|
   parts_count_per_item = item.parts.size
end

or

@parts = Part.includes(:items)

@parts.each do |part|
   items_count_per_part = part.items.size
end
person andyleesuk    schedule 23.06.2015
comment
Привет, спасибо за вашу помощь. Я использую атрибут unit_count в таблице соединений в качестве счетчика, чтобы отслеживать, сколько частей определенной части вставлено в определенный элемент; например, сколько белых полок связано с конкретной серверной стойкой. Однако я нашел решение, и я собираюсь ответить на свой вопрос. - person harlock975; 24.06.2015