Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-3172

Support eager loading without requiring IdentityMap

    • Type: Icon: Task Task
    • Resolution: Done
    • 12_01_17
    • Affects Version/s: None
    • Component/s: None
    • Labels:
      None

      Tried enabling identity map and everything went wrong.

      It should be possible to support eager loading without an identity map. A poor man's implementation of that would look like this (tested, called it "project" instead of "eager_load", just for clarity):

       ruby
      module Mongoid
        class Criteria
      
          def project(* relationship)
            if projected_entries.any?
              symbol = Array(relationship).last # eg. :artist
              klass = symbol.to_s.camelize.constantize # eg. Artist
              accessors = Array(relationship)[0..-2] # eg. [ :artwork ]
      
              parent = self.klass
              accessors.each do |accessor|
                parent = parent.relations[accessor.to_s].klass
              end
      
              # examine the relationship
              relation = parent.relations[symbol.to_s]
      
              # fetch the IDs
              target_ids = projected_entries.map do |entry|
                # apply any deep accessors to find the relationship parent
                accessors.each do |accessor|
                  entry = entry.send(accessor)
                end
                entry[relation.foreign_key] if entry # id stored in the model
              end.compact.uniq
      
              # fetch the instances
              target_instances = Hash[klass.find(target_ids).map do |target_instance|
                [ target_instance.id, target_instance ]
              end] if target_ids.any?
      
              # project instances
              projected_entries.each do |entry|
                # apply any deep accessors
                accessors.each do |accessor|
                  entry = entry.send(accessor)
                end
                # identity of the target instance
                target_instance = target_instances[entry[relation.foreign_key]] if entry
                entry.send("#{symbol}=", target_instance) if entry && target_instance
              end if target_instances
            end
            self
          end
      
          def projected_entries
            @projected_entries ||= entries
          end
        end
      end
      

      You could then "project" any criteria and chain projections. Say you have a widget with many gadgets, and we're returning gadgets with their widgets.

       ruby
      Gadget.limit(5).all.project(:widget).projected_entries
      

      This would only do two queries: all gadgets and an $in for all widgets that belong to the first five gadgets.

      Another way would be to introduce identity map only per query, without turning it on globally, but haven't tried that.

      What do you think?

            Assignee:
            Unassigned Unassigned
            Reporter:
            dblock Daniel Doubrovkine
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Resolved: