MongoMapper Avoiding causing N+1 queries in Ruby on Rails
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...
Advice on migrating from MongoMapper to Mongoid?
Both of them are great MongoDB Libraries for Ruby. But if you want to switch, here are some notes:
Migrating MongoMapper ORM to Mongoid ORM - Notes
Configure the database connection.
Replace configuration yaml file(includes replica configuration).
Configure Mongoid specific options. e.g -
raise_not_found_error: false
. if you don't want an error every time a query returns nothing...Change all models definations -
include MongoMapper::Document
toinclude Mongoid::Document
Change the format for all fields definitions.
In mongoid, you should specipy the timestamp:
include Mongoid::Timestamps
Change validation. e.g:
:in => ARRAY
, will be:validates :name, presence: true, inclusion: { in: ARRAY }
Change indexes.
Change order_by format. e.g: MM:
Model.all(:order => 'name')
. Mongoid:Model.order_by('name ASC')
Error
is a keyword in Mongoid. So if you have a model namedError
, you should change it.Pagination format is different, using another gem.
The primary key index entry in MM is
id
. In Mongoid it's_id
, if you have other code relying on.id
in the object JSON, you can override as_json function in your Model to create the JSON structure you want.In MM,
Model.fields(:id, :name)
,limits the fields returned from the database to those supplied to the method. In Mongoid it'sModel.only(:name,:id)
Some queries changes:
Selecting objects by array: MM:
Model.where(:attr.in => [ ] )
andModel.where(:attr => [ ] )
. Mongoid is only:Model.where(:attr.in => [ ] )
Map option of MM is equivalent to the Mid's pluck.
Model.map(&:name)
--to--Model.pluck(:name)
Mongoid doesn't support find query for nil. e.g:
value = nil. Model.find(value)
will throw an error :"Calling Document .find with nil is invalid"
. So in mongoid we should do:Model.find(value || "")
.In MM:
Model.find_or_initialize_by_name("BOB")
. In MongoidModel.find_or_initialize_by(name: "BOB")
.MM can be used in those two options:
Model.where({:name => 'BOB'}).first
, and alsoModel.first({:name => 'BOB'})
. Mongoid has only first option.In MM, to update multiple objects:
Model.set({conditions},attr_to_update)
. In Mongoid:Model.where(conditions).update_all(attr_to_update)
.
Eager Loading of Associations using MongoMapper
UPDATE: The code below is just as models workflow.. I tried it after some coding and it didnt work!
Lets say you have Post model and User model.
User has_many posts, and you want All the users (authors) with their posts.
Here a tip to handle it. and my example is fetching one post.
post.rb
class Post
include MongoMapper::Document
key :title, String
key :body, String
key :user_id, ObjectId
belongs_to :user
end
and user.rb
class User
include MongoMapper::Document
key :name
many :posts, :embed => :title
end
Now,
u = User.first
p = u.posts.first
puts p.title # read it from embedded doc
puts p.body # lazy loading
The trick here is to embed the mostly common fields like the name of the user, _id, user slug, etc.
I didnt test what above, but you have to give a try!
Best
--Amr
Mongoid or MongoMapper?
I have used MongoMapper for awhile but decided to migrate to MongoId. The reason is hidden issues plus arrogance towards users. I had to jump through hoops to make MongoMapper work with Cucumber (succeeded in the end) and to put a couple of patches even the project was simple, but it's not the point. When I tried to submit a bug fix (due to incompatibility with ActiveRecord), they seemingly got pissed off that I found a problem and I was pushed around. While I was testing, I also encountered a major bug with their query implementation, while their testing was tuned in a way that the tests pass. After my previous experience, didn't dare to submit it.
They have a significantly lower number of pull requests and bug/feature submissions than MongoId, i.e. community participation is much lower. Same experience as mine?
I don't know which one has more features right now, but I don't see much future in MongoMapper. I don't mind fixing issues and adding functionality myself, but I do mind situations when they wouldn't fix bugs.
Related Topics
Using --No-Rdoc and --No-Ri with Bundler
How to Install Ruby Gems on MAC
Rails Get Index of "Each" Loop
Differencebetween 'Raise "Foo"' and 'Raise Exception.New("Foo")'
How to Install Nokogiri on MAC Os Sierra 10.12
Setting Up a Polymorphic Association
How to Require Activerecord in Irb
Heroku-18: Git Push Fails. Showing Different Versions of Ruby on Push
Ruby Gets() Not Returning Correct String
Ruby on Rails - Helper Method - Undefined Method 'Log_In' in Ruby on Rails
Testing for Empty or Nil-Value String
Override Vagrant Configuration Settings Locally (Per-Dev)
Why Would We Put a Module Inside a Class in Ruby
How to Link to a Nested Route Path Inside a Loop
Regex to Extract Boundary and Content Type Out of Mail Headers