2016-07-01 3 views
1

Итак, в rails-api, над которым я работаю, мы в настоящее время пытаемся оптимизировать некоторые более длительные вызовы, и у меня возникла проблема с функциональностью .includes , У меня это работает в большинстве ситуаций, но есть одна конкретная ситуация, когда он не работает так, как я этого хочу.Рельсы включают кеш, который не используется в методе модели

Вот пример:

класс пользователя

class User < ActiveRecord::Base 
    has_many :images 
    has_one :active_image, -> { where(images: { active_image: true })}, class_name: 'Image' 
    has_many :facebook_auth 

    def get_profile_image 
    if active_image 
     active_image.image.url(:profile) 
    else 
     facebook = facebook_auth.last 
     if facebook 
     "https://graph.facebook.com/#{facebook.provider_user_id}/picture?width=150&height=150" 
     end 
    end 
    nil 
    end 
end 

Контроллер:

class UserController < BaseAPIController 
    def get_user_image 
    user_id = params[:user_id] 
    user = User.includes(:active_image, :facebook_auth).find(user_id) 
    render json: user.get_profile_image 
    end 
end 

С этим, я бы предположить, что .includes(:active_image, :facebook_auth) бы кэшировать данные так, что, когда я называю их в метод get_profile_image, он не делает больше вызовов db, но это не так. Что я здесь делаю неправильно?

Спасибо, Charlie

+0

Вы должны включить active_image слишком –

+0

А, это только опечатка. В моем фактическом коде я это делаю. Я исправлю это. –

+0

у вас также есть опечатка в 'render json: user.get_profile_image' is' render json: user.get_profile_images' – neydroid

ответ

1

Вы, где почти там! Попробуйте этот подход:

class User < ApplicationRecord 
    has_many :images, dependent: :destroy 
    has_one :active_image, 
    -> { where(active: true) }, 
    class_name: 'Image' 

    has_many :facebook_auths, dependent: :destroy 
    has_one :active_facebook_auth, 
    -> { order("created_at desc") }, 
    class_name: 'FacebookAuth' 

    scope :eager_load_image_data, 
    -> { includes(:active_image).includes(:active_facebook_auth) } 

    def profile_image_url 
    if active_image 
     active_image.url 
    elsif active_facebook_auth 
     "https://graph.facebook.com/#{active_facebook_auth.provider_user_id}/picture?width=150&height=150" 
    else 
     nil 
    end 
    end 
end 

Затем в контроллере или всякий раз, когда вы хотите жаждущих загрузки изображений:

# for one user, with id 2: 
User.eager_load_image_data.find(2).profile_image_url 

# for a collection (using 'all' here): 
User.eager_load_image_data.all.map{ |user| 
    [user.name, user.profile_image_url] 
} 

Этот способ eagerloaded данные изображения, как с Image класса и FacebookAuth класс.

Там, где также некоторые другие вопросы, в методе Пользователь # get_profile_image что я исправлено:

  • Он всегда возвращает ноль. Я уверен, что в вашем реальном коде у вас есть ранние возвращения.
  • Для коллекций он выполняет запрос N + 1, если ищет facebook_auth_tokens.
+0

Это выглядит великолепно, я пойду вперед и попробую это и посмотрю, как это работает. Благодаря! –

+0

Ну, я не на 100% уверен, почему, но это работает! Я думаю, что проблема заключалась в том, что я делал has_many, а затем пытался сделать что-то вроде .last на тех, и добавив их, он сделал все, чтобы сделать новый запрос. Добавление порядка desc и has_one вместо has_many - это то, что исправлено. Благодаря! –

0

Ну, я хотел бы прокомментировать, но не может поставить код в комментариях, так что я даю не-ответ ...

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

def self.user_profile_image(user_id) 
    active_image = Images.where(user_id: user_id).where(active_image: true).first 

    if active_image 
     active_image.image.url(:profile) 
    else 
    facebook = FaceBookAuth.where(user_id: user_id).last 
    if facebook 
     "https://graph.facebook.com/#{facebook.provider_user_id}/picture?width=150&height=150" 
    end 
    end 

    nil 
end 

И просто позвонить/кэшировать изображения в контроллере, если это не слишком упрощенное ...

def get_user_image 
    render json: User.user_profile_image(params[:user_id]) 
end 

Это составляет не более 2 относительно эффективных запросов. Он не требует загрузки пользователя и т. Д.

+0

Я просто использую приведенный выше код в качестве примера. В моем фактическом коде это намного сложнее, и я использую пользовательские данные, и я нахожусь в цикле, поэтому важно, чтобы включенные функции работали. –

+0

Ах. Я думал, что так может быть.Другим обходным решением будет предварительная выборка изображений в одном запросе и сохранение их в хеш-таблице. Затем, когда вы зацикливаете пользователей или что-то еще, вы можете быстро найти O (1), чтобы захватить их изображения, а не запускать кучу запросов. В принципе, вы должны вручную делать то, что должна делать prefetch. –

+0

На самом деле это обходной путь, который мы используем. Однако, чем нам приходится использовать посторонние переменные экземпляра и дополнительные циклы, которые я хочу попытаться избежать. Я уверен, что должен быть способ заставить это работать, поэтому я хочу посмотреть, где я ошибаюсь. –

Смежные вопросы