0

I have rather complex association where I use awesone_nested_set gem to manage Ratecard::RuleIndex parents and children:

class Inventory::Inventory < ApplicationRecord
  has_many :ratecards_rules, class_name: 'Ratecard::RatecardsRule', through: :ratecards
  has_many :rule_indices, class_name: 'Ratecard::RuleIndex', through: :ratecards do
    def children
      Ratecard::RuleIndex.each_with_level(Ratecard::RuleIndex.root.children) do |index, level|
        puts index.index_value
      end
    end
  end
end

class Ratecard::Ratecard < ApplicationRecord
  has_many :ratecards_rules, class_name: 'Ratecard::RatecardsRule'
  has_many :rule_indices, class_name: 'Ratecard::RuleIndex', 
            through: :ratecards_rules, source: :rulable, 
            source_type: 'Ratecard::RuleIndex'
end

class Ratecard::RatecardsRule < ApplicationRecord
  belongs_to :rulable, polymorphic: true, optional: true
end

class Ratecard::Rule < ApplicationRecord
  has_many :ratecards_rules, class_name: 'Ratecard::RatecardsRule', as: :rulable
  has_many :ratecards, class_name: 'Ratecard::Ratecard', through: :ratecards_rules
end

class Ratecard::RuleIndex < Ratecard::Rule
  has_many :ratecards_rules, class_name: 'Ratecard::RatecardsRule', as: :rulable
  has_many :ratecards, class_name: 'Ratecard::Ratecard', through: :ratecards_rules
end

In my console I can do:

i=Inventory::Inventory.where(id: [200,201,202]).includes(:ratecards)
                      .where(ratecard_ratecards: {id: 1}).preload(:rule_indices)

which preloads Ratecard::RuleIndex parent records (indices). However I need to somehow preload children records. At the moment when I do

i.collect{|x| {indices: x.rule_indices.children.collect{|i| {id: i.index_value}} }}

I get my desired output, however extra queries are performed and it looks like his:

Ratecard::RuleIndex Load (0.8ms)  SELECT  "ratecard_rules".* FROM "ratecard_rules" WHERE "ratecard_rules"."type" IN ('Ratecard::RuleIndex') AND "ratecard_rules"."parent_id" IS NULL ORDER BY "ratecard_rules"."lft" ASC LIMIT $1  [["LIMIT", 1]]
Ratecard::Rule Load (0.5ms)  SELECT "ratecard_rules".* FROM "ratecard_rules" WHERE "ratecard_rules"."parent_id" = $1 ORDER BY "ratecard_rules"."lft" ASC  [["parent_id", 60]]
0.95
=> [{:indices=>[{:id=>0.95e0}]}]

Is there some way to preload (due to Polymorphic association and need especially "preload") my child records so I don't get later N+1 queries? I'd be happy for any hint. Thank you.

So far I've been trying to implement has_many associaion extension, but it doesn't give me desired result as I can't make my children records to be preloaded. I understand this is due to my query goes to Ratecard::RatecardsRule, where my "parent" indices records are grabbed by rulable_type and rulable_id. It would be great if I can somehow make to preload child records (stored in "ratecard_rules" table togeher with parents and managed with Awesome Nested Set gem).

matiss
  • 747
  • 15
  • 36
  • One of the major cons of polymorphism is the spotty support for eager loading since ActiveRecord actually has to fetch the data to determine which table to join. – max Jul 17 '18 at 00:15
  • What you would need would be something like `SELECT a.*, b.* FROM a LEFT OUTER JOIN a.b_type ON (b.id = a.b_id)` - but there is no polyglot way to dynamically insert the name of the joined table. If this is performance critical I would consider using an alternative solution such as STI. – max Jul 17 '18 at 00:34
  • @max Thank you for clearing out. I think I've over-complicated, because all I needed was to group my `Ratecard::RuleIndex`. It looks like I can do it with [self joins](http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference) rather than using Awesome Nested Set. – matiss Jul 17 '18 at 05:33

0 Answers0