I recently went through an exercise of upgrading one of Artsy's largest web projects to the current HEAD of Mongoid 4.x. This is going to be a major release with numerous changes and I wanted to flush out bugs before the final version of the ODM is released. All Mongoid changes currently live on master.

1
gem 'mongoid', github: 'mongoid/mongoid'

In the process I've worked on making a few gems compatible with Mongoid 4 and learned a couple of things that should help you make this process smooth for your own applications.

Moped::BSON::ObjectId

Moped's BSON implementation has been removed in favor of the MongoDB bson gem 2.0 and higher. All Moped::BSON references must change to BSON. This is rather annoying and forces many libraries to have to fork behavior at runtime.

1
2
3
4
5
6
7
8
9
module Mongoid
  def self.mongoid3?
    ::Mongoid.const_defined? :Observer # deprecated in Mongoid 4.x
  end

  def self.mongoid2?
    ::Mongoid.const_defined? :Contexts # deprecated in Mongoid 3.x
  end
end

The mongoid2? implementation is borrowed from mongoid_orderable and I wrote the mongoid3? version by parsing the CHANGELOG - observers are deprecated in 4.0.

Now, instead of calling Moped::BSON::ObjectId.legal?(id), you have to do something like this:

1
2
3
4
5
if Mongoid.mongoid3?
  Moped::BSON::ObjectId.legal? id
else
  BSON::ObjectId.legal? id
end

Furthermore, you can no longer convert a string into a Moped::BSON::ObjectId(id), you must explicitly call from_string:

1
2
3
4
5
if Mongoid.mongoid3?
  Moped::BSON::ObjectId(id)
else
  BSON::ObjectId.from_string(id)
end

Libraries should then adjust their dependencies on Mongoid and specify >= 3.0, and maybe < 5.0.

Testing Against Multiple Mongoid Versions

The mongoid-orderable gem has a neat system for testing against all versions of Mongoid with Travis CI. First, the .travis.yml file declares a test matrix that sets MONGOID_VERSION. Note that Mongoid 3.x or newer doesn't run with Ruby 1.8.x or 1.9.2.

.travis.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rvm:
  - 1.8.7
  - 1.9.2
  - 1.9.3
  - ruby-head

env:
  - MONGOID_VERSION=2
  - MONGOID_VERSION=3
  - MONGOID_VERSION=4

matrix:
  exclude:
    - rvm: 1.8.7
      env: MONGOID_VERSION=3
    - rvm: 1.8.7
      env: MONGOID_VERSION=4
    - rvm: 1.9.2
      env: MONGOID_VERSION=3
    - rvm: 1.9.2
      env: MONGOID_VERSION=4

services: mongodb

The library's Gemfile locks a different version depending on the environment variable, defaulting to 3.x. You can also test against a very specific version, if you must.

Gemfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
source "http://rubygems.org"

gemspec

case version = ENV['MONGOID_VERSION'] || "~> 3.1"
when /4/
  gem "mongoid", :github => 'mongoid/mongoid'
when /3/
  gem "mongoid", "~> 3.1"
when /2/
  gem "mongoid", "~> 2.8"
else
  gem "mongoid", version
end

Upgraded Gems

I used the above method to make a few gems Mongoid 4.x compatible, via the following pull requests.

Upgrading a Rails Project

If you're using Rails, you're in for upgrading both Mongoid 4.x and Rails to 4.x. This means you will suffer a lot of pain trying to find compatible versions of various interdependent gems. I suggest locking Rails, Mongoid and ActiveSupport to begin with.

Gemfile
1
2
3
gem 'rails', '4.0.1'
gem 'activesupport', '4.0.1'
gem 'mongoid', github: 'mongoid/mongoid'

Bulk search & replace Moped::BSON::ObjectId references.

Calls to inc, set and add_to_set now take hashes, eg. artist.inc(likes_count: 1).

If you're converting Mongoid objects to JSON and seeing data such as { "$oid" => "..." } instead of an ID, monkey-patch BSON::ObjectId.as_json. See this discussion thread.

config/initializers/bson/object_id.rb
1
2
3
4
5
6
7
module BSON
  class ObjectId
    def as_json(options = {})
      to_s
    end
  end
end

If you're using Warden (including via Devise) and/or rely on session cookies that may contain a user ID, add an implementation for the deprecated Moped::BSON::Document. This will prevent all old cookies from causing a serialization error and logging all those users out.

config/initializers/bson/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module Moped
  module BSON
    ObjectId = ::BSON::ObjectId

    class Document < Hash
      class << self
        def deserialize(io, document = new)
          __bson_load__(io, document)
        end

        def serialize(document, io = "")
          document.__bson_dump__(io)
        end
      end
    end
  end
end

Updates

Please post your updates below and questions to the mongoid mailing list. I'll update this post up until Mongoid 4.x ships.

Categories: mongodb, mongoid


Comments