Ibraheem Ahmed

I recently had to migrate a Rails app from Postgres to MongoDB. The process wasn't that straightforward, so I thought I'd share some tips I learned along the way.

Note that this post does not cover data migration. See pg2mongo and the official MongoDB migration guide for more information.

Removing Active Record

The officially supported MongoDB driver for Rails is Mongoid, which aims to achieve parity with Active Record. Mongoid accepts all the Active Record associations, validations, and callbacks you're used to. Convincing Rails to switch from Active Record can be tricky, but if you follow all the steps below, you should be able to get it to work.

If you go to your config/application.rb file, you will most likely see the following:

...
require "rails/all"
...

This line is telling Rails to include all the default frameworks. If you want to use Mongoid however, you need to remove this line and instead explicitly choose all the default frameworks, excluding active_record and active_storage:

...
require "rails"

require "active_model/railtie"
require "active_job/railtie"
# require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_mailbox/engine"
require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
# uncomment if your app uses the asset pipeline
# require "sprockets/railtie"
# uncomment if your app uses rails test_unit
# require "rails/test_unit/railtie"
...

Because active_record is no longer being imported, Mongoid can take it's place. Note that active_storage depends directly on active_record and cannot be used with Mongoid.

Now that you aren't using active_record, any references to it in files in the config directory need to be removed:

# config.active_record.dump_schema_after_migration = false
# config.active_storage.service = :local

You also need to remove the active_storage npm package:

$ yarn remove @rails/activestorage

And remove this line from your application.js:

require("@rails/activestorage").start();

You can also delete the /storage directory and your config/storage.yml file.

At this point make sure you do not delete the /db directory and the config/database.yml file. Deleting these files now can cause errors because Rails will still expect those files to exist until we setup Mongoid.

Setting Up Mongoid

First, add the Mongoid gem to your application:

gem 'mongoid', '~> 7.0.6' # or the latest current version

And run:

$ bundle:install

Now you have to stop spring:

$ spring stop && spring start

And generate the default Mongoid configuration file:

$ rails g mongoid:config

Now that Mongoid is setup, you can delete the /db folder, the config/database.yml file, and remove any database gems from your Gemfile (sqlite3, pg, etc. )

Updating Rails Models

Now that you're on Mongoid, you have to update your Rails models. A Mongoid model looks something like this:

class User
  include Mongoid::Document
  include Mongoid::Timestamps

  field Email, type: String
  field Name, type: String

  validates :name, presence: true

  has_many :posts
end

Importantly, the model is no longer inheriting from ApplicationRecord.

Testing

That's it! Mongoid is all setup. To test the setup, make sure MongoDB is running:

$ brew services start mongodb-community@4.2

And try to create a model in the rails console:

$ rails console
~ u = User.create(email: "john@example.org", name: "john")

Debugging

Sometimes Spring tries to load Active Record even when the application has no references to it. If running spring stop && spring start doesn't work, try adding an Active Record adapter such as sqlite3 to your Gemfile so that Active Record can be loaded. You could also try to remove Spring from your application:

$ pkill -f spring
$ spring stop

Edit your Gemfile, removing all references to Spring, and reinstall all your gems:

$ bundle install --redownload
$ bin/spring binstub –remove –all

Alternatively, you can disable Spring locally by adding the following environment variable to your bin/spring file:

#!/usr/bin/env ruby

ENV['DISABLE_SPRING'] = '1'
...

For more information about this bug, see the github issue.