#ruby-on-rails #ruby-on-rails-4
#ruby-on-rails #ruby-on-rails-4
Вопрос:
Я получаю NoMethodError при попытке предварительной загрузки ассоциации has_many, которая включает лямбда-выражение conditions.
Вот мои модели
Class Event < ActiveRecord::Base
belongs_to :user
end
Class CalendarDay < ActiveRecord::Base
belongs_to :user
has_many :events,
->(_day) {
where(
user_id: _day.user_id,
deleted_at: nil
)
},
autosave: false,
foreign_key: :date,
primary_key: :date,
end
Загрузите несколько календарных дней и попробуйте предварительно загрузить события.
days = CalendarDay.all.to_a
loader = ActiveRecord::Associations::Preloader.new
loader.preload(days, :events)
#NoMethodError: undefined method `user_id' for nil:NilClass
Похоже, что загрузчик работает только с отношениями без conditions lambda.
У кого-нибудь есть идеи? Является ли это ошибкой в классе ActiveRecord ::Associations::Preloader?
Ответ №1:
Если вы хотите предварительно загрузить ассоциацию, вы можете использовать conditions , однако вам не будет передана родительская запись (_day в данном случае), поскольку она будет разной для каждого из объектов day .
Activerecord необходимо выполнить один запрос для загрузки всех событий, и он не знает, как это сделать, когда условие изменяется для каждого родительского объекта (то же самое верно, когда вы тоже включаете ассоциацию, если мне не изменяет память)
Комментарии:
1. хм, хорошо, я думаю, это своего рода вариант использования составных первичных ключей. Я вручную настроил предварительную загрузку.
2. Я думаю, что есть драгоценный камень для составных первичных ключей
Ответ №2:
Мне понравилась хорошая статья, описывающая, как выполнить предварительную загрузку вручную. http://mrbrdo.wordpress.com/2013/09/25/manually-preloading-associations-in-rails-using-custom-scopessql/
Вот мой код предварительной загрузки.
collection = CalendarDay.all
dates = collection.map(amp;:date)
user_ids = collection.map(amp;:user_id)
events = Event.where(
date: dates,
user_id: user_ids
).to_a
collection.each do |record|
association = record.association(:events)
association.loaded!
association.target.concat(
#events.where(
# user_id: record.user_id,
# date: record.date,
# deleted_at: nil
#)
events.find_all { |e|
e.user_id == record.user_id amp;amp;
e.date == record.date amp;amp;
e.deleted_at == nil
}
)
end
Результаты
#No preload code.
Views: 1129.8ms | ActiveRecord: 409.8ms
#Cache active record relation then query for each day.
Views: 296.4ms | ActiveRecord: 198.6ms
#Run query and use find_by
Views: 290.2ms | ActiveRecord: 28.2ms
Комментарии:
1. Вы выполняете один запрос в календарный день — зачем вообще беспокоиться о загрузке?
2. Это не идеально, создание отношения activerecord для событий и использование его для запроса увеличили время запроса на 20%. Я собираюсь перейти
events
к массиву, а затем использовать find_all .