Rails急切地从单独的表中加载多个表和急切加载。如何解决这个N + 1?

时间:2016-03-15 20:13:24

标签: ruby-on-rails-3 activerecord eager-loading

这个有点令人困惑。

我认为有问题的一行是在控制器中,特别是这一行:

recipe_tools = (recipe.recipe_tools + RecipeTool.generic)

我的模特:

class Recipe < ActiveRecord::Base
   ...
   has_many :recipe_tools, dependent: :destroy
   ...
end

class RecipeTool < ActiveRecord::Base
  belongs_to :story
end

class Story < ActiveRecord::Base
  ...
  has_many :recipe_tools, dependent: :destroy
  ..
end

这是我的控制者:

module Api
  module Recipes
    class RecipeToolsController < Api::BaseController
      before_filter :set_cache_buster

      def index
        # expires_in 30.minutes, public: true

        recipe = Recipe.find(params[:recipe_id])
        recipe_tools = (recipe.recipe_tools + RecipeTool.generic)
        binding.pry

        render json: recipe_tools, each_serializer: Api::V20150315::RecipeToolSerializer
      end
    end
  end
end

这是我的序列化器:

module Api
  module V20150315
    class RecipeToolSerializer < ActiveModel::Serializer
      cached
      delegate :cache_key, to: :object

      attributes :id,
                 :display_name,
                 :images,
                 :display_price,
                 :description,
                 :main_image,
                 :subtitle

      def display_name
        object.display_name
      end

      def images
        object.story.get_spree_product.master.images
      end

      def display_price
        object.story.get_spree_product.master.display_price
      end

      def description
        object.story.description
      end

      def main_image
        object.story.main_image
      end

      def subtitle
        object.story.get_spree_product.subtitle
      end

      def spree_product
        binding.pry
        spree_product.nil? ? nil : spree_product.to_hash
      end

      private

      def recipe_tool_spree_product
        @spree_product ||= object.story.get_spree_product
      end
    end
  end
end

这是我的RecipeTool模型:

class RecipeTool < ActiveRecord::Base
  ...
  scope :generic, -> { where(generic: true) }
end

在控制器中,我们只调用recipe.recipe_tool一次,所以我认为我们不需要包含recipe_tool。我们没有遍历一组食谱并在每个食谱上调用recipe_tool,因此没有N + 1问题。

但是,我们通过将两个recipe_tools集合连接在一起,在控制器中创建recipe_tools集合。 Recipe.generic也是一个生成通用recipe_tools的SQL查询。

我认为在通过序列化程序生成JSON响应时会发生N + 1问题。我们多次调用recipe_tool.story,每次调用#story时都会生成SQL查询,我们会在recipe_tools的集合上执行此操作。

1 个答案:

答案 0 :(得分:0)

首先,我会使用:inverse_of修复你的关联,这样我就不必担心如果它碰巧回溯到父对象,就会重新加载rails。即

    class Recipe < ActiveRecord::Base
       ...
       has_many :recipe_tools, dependent: :destroy, :inverse_of=>:recipe
       ...
    end

    class RecipeTool < ActiveRecord::Base
      belongs_to :story, :inverse_of => :recipe_tools
      belongs_to :recipe, :inverse_of => :recipe_tools ## this one was missing???
    end

    class Story < ActiveRecord::Base
      ...
      has_many :recipe_tools, dependent: :destroy, :inverse_of=>:story
      ..
    end

接下来,我将eager_load控制器中的相应关联,如:

ActiveRecord::Associations::Preloader.new.preload(recipe_tools, :story =>:recipe_tools, :recipe=>:recipe_tools)

在调用序列化程序之前。