mardi 15 mars 2016

Rails eager loading multiple tables and eager loading from separate tables. How to fix this N+1?

This one is a bit confusing.

I think the line that is problematic is in the controller and it's this line in particular:

recipe_tools = (recipe.recipe_tools + RecipeTool.generic)

My models:

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

This is my controller:

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

This is my serializer:

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

This is my RecipeTool model:

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

In the controller, we call recipe.recipe_tool only once and so I don't think we need to includes recipe_tool. We're not iterating through a collection of recipes and calling recipe_tool on each one so no N+1 problem.

However, we are creating a collection of recipe_tools in the controller by concatenating two collections of recipe_tools together. Recipe.generic is also a SQL query that generates generic recipe_tools.

I think the N+1 problem is happening in generating the JSON response via the serializer. We call recipe_tool.story a lot which would generate a SQL queries each time we call #story and we do that on a collection of recipe_tools.

Aucun commentaire:

Enregistrer un commentaire