Предварительная загрузка ассоциаций при сбое условий lambda

#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 .