Migrate from Moped to the ruby mongo driver

22 March 2017 • Tags: mopedmongoidmongodb

It clearly has been some time since Mongoid 5 has been released, yes there’s even Mongoid 6 but as our app still runs on Rails 4.x that’s not an option yet. Plus the big works is in Moped -> Mongo anyways. The main reason why I’m doing this, the failure handling in Moped for when one for my replica nodes fails is shit. Plain as that, the app’s performance slumps immediately when one node fails and that’s never acceptable.

So, here’s some stuff do you have to check for when upgrading, besides the very few things mentioned in the official docs of course. You will also find the API docs most helpful.

As usual: Having a lot of tests will help you, write specs. I can’t repeat it often enough. In our case only 5% failed after updating the gem, I expected more in this 6+ yrs old application but a few commits later I saw the green light as a wiser man.

Your gemfile

I bet you know what to do, but for those new to bundler and the ruby-sphere:

gem 'mongoid', '~> 4.0'
gem 'mongoid', '~> 5.0'

Querying by ids requires BSON::ObjectId.from_string

Given this little model

class Favourite
  include Mongoid::Document
  belongs_to :user
  belongs_to :f, :polymorphic => true, :validate => false # add f_type and f_id fields
end

With the upgrade the error was actually hard to spot, lot of document not found before I realized that I was passing the id as a string and not as an ObjectId (MongoDB’s internal format to store them, back in the days you could use strings if you really wanted).

In my controller I did query for favs based on the information what it is favouring, this line won’t do anymore current_user.favourites.where(f_type: params[:f_type], f_id: params[:f_id]).first

and that needed an explicit BSON::ObjectId.from_string.

current_user.favourites.where(f_type: params[:f_type], f_id: BSON::ObjectId.from_string(params[:f_id])).first

An even better solution, and closing security holes for most cases, would be to pass the favourited object and let mongoid do its magic.

current_user.favourites.where(f: MyThing.find(params[:f_id])).first

no implicit conversion of Mongo::Collection::View::Aggregation into Array

Hit this error when concating (concatting?) an aggregation to an array.

items.concat Thing.collection.aggregate([
        {"$match" => selector},
        {"$sample" => {size: size}},
        {"$project" => {_id: 1, text: 1}}
     ])

and a simple to_a on it did fix it: Thing.collection.aggregate(...).to_a

Over are the days where we could just slap stuff together and let moped figure that it’s all arrays.

Map reduce with inline collections

The error message was along the lines of Invalid collection name things_test (73) and it took me some time to figure that I used map_reduce(map, reduce).out(inline: true) and mongodb now explicitly wants to see inline: 1. Ok… still friends?

includes() can be picky about the order of relations

Something that I used in only one place, but includes will raise a NoMethodError if it disagrees with your order. Something like User.includes(:country, :town) (Town belongs to Country) didn’t work but User.includes(:town, :country) did. Beats me. Just change the order and move on.

gems

Everyone’s Gemfile is different, we have 10 mongoid gems in use, here’s the troublemakers.

mongoid_taggable

Now this is a old gem, left outside by it’s original developer and quite a few people took it home and prepped it for yet another version of mongoid. We were using the version from colibri-software through-out mongoid 4 but no mongoid 5 yet and I didn’t need those few extra functions anyways. We’ll see if the one from Adam will stick.