Rails: предварительная загрузка has_many через отношение в контроллере

Я надеюсь, что смогу объяснить это ясно.

  • Пользователь имеет_много уведомлений и имеет_много фильмов через уведомления. Фильм имеет_много трейлеров.

  • Уведомления имеют 3 столбца: user_id, movie_id и веб-сайт.

  • На trailers#index я отображаю @trailers, и каждый @trailer отображает 3 x ajax уведомления_формы (каждая форма представляет собой всего одну кнопку, по одной для каждой опции «веб-сайта». Когда пользователь нажимает эту кнопку, отправляется запрос ajax для создания новая запись уведомления)

  • Я отслеживаю, какие кнопки были нажаты (просматривая, существует ли уведомление для этого пользователя/фильма)

Меня беспокоит то, что, поскольку я только предварительно загружаю @trailers, я в настоящее время делаю 3 новых вызова БД на каждой итерации трейлера.

_notification_form.html.erb (вызывается 3 раза для каждого трейлера)

<% if trailer.movie.tracked?(current_user, "netflix") %>
   ## do some css stuff on the button to show notification already set

фильм.рб:

def tracked?(user, website)
    user.notifications.where(movie_id: self.id, website: website).first
  end

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


person Jackson Cunningham    schedule 09.12.2015    source источник


Ответы (1)


Поскольку вы проверяете только состояние current_user, а каждый Trailer знает свой movie_id, вы можете просто загрузить Notification из User и проверить их на наличие movie_id из Trailer:

tracked = current_user.notifications
tracked.detect { |n| n.movie_id == trailer.movie_id && n.website == 'netflix' }

Если у User может быть тонна Notifications, вы можете ограничить набор только Notifications для Trailers на отображаемой странице:

tracked = current_user.notifications.where(movie_id: trailers.map(&:movie_id))

Загрузите это, а затем просто проверьте это, когда будете решать, как отображать свои кнопки. Вы можете сделать его еще более эффективным, создав Set, содержащий [movie_id, website] пар, которые будут выполнять поиск за постоянное время.

tracked = Set.new(
  current_user.nofications
    .where(movie_id: trailers.map(&:movie_id))
    .pluck(:movie_id, :website)
)
tracked.include?([trailer.movie_id, 'netflix'])

Эта стратегия будет быстрой для вас, потому что она точно адаптирована к данным, которые вы проверяете. Если вам нужно что-то более гибкое, что по-прежнему сводит к минимуму запросы к базе данных, обратите внимание на быстро загружаемые ассоциации с includes. , preload и eager_load. 3 способа быстрой загрузки (предварительной загрузки) в Rails 3 и 4 имеет отличное описание различий между этими методами. Все они работают во время начальной загрузки отношения (то есть, если вы еще ничего не загрузили), но когда вы уже загрузили часть данных (например, у вас есть Trailers, но еще нет Moviess или Notifications), вы можете использовать ActiveRecord::Associations::Preloader для эффективной загрузки остальных.

person Kristján    schedule 09.12.2015