Search code examples
ruby-on-railsrubymongodbeager-loadingmongomapper

MongoMapper Avoiding causing N+1 queries in Ruby on Rails


So I have two Class that look's like this

class Branch
  include MongoMapper::Document

  many :builds
end

class Build
  include MongoMapper::Document

  belongs_to :branch
end

And If we want to access Branch data from Build Class. I can do it like below

builds = Build.where(___)

builds.each do |build|
  puts "#{build.branch.name} build number #{build.number}"
end

But that triggers an alert that causing N+1 queries because it makes too many independent database queries. Well the solution is by using Eager Load like below

builds = Build.where(____).includes(:branches)

builds.each do |build|
  puts "#{build.branch.name} build number #{build.number}"
end

Well, eager loading or .includes() are not available in MongoMapper as I looking from their's documentation (I Hope I wrong). But it is available in MongoId. But, I'm not planning to change from MongoMapper into MongoId for now. Do you know the turn around for this? that maybe can reduce the queries.


Solution

  • According to the docs Mongoid's #includes "...Will load all the documents into the identity map who's ids match based on the extra query for the ids."

    So there is no magic at all - it sounds like it just performs an additional query to fetch the associated entities and keep them in memory in some kind of data structure with O(1) reads (Hash in Ruby, for example). You can do it on your own (disclaimer: kinda pseudocode is coming, as a reference, not a ready solution):

    builds = #...
    branches = Branch.where(id: builds.map(&:id)).to_h { |br| [br.id, br] }
    
    builds.each do |build|
      puts "#{branches[build.branch_id]&.name} build number #{build.number}"
    end
    

    But please note: if you have to do this kind of memorization often it might be a signal that the data model is not optimal for the document-based database - embedded documents might be a more efficient solution...