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.