I’m currently developing my graduation thesis (yeah, the end is near \o/).

I’ve decided to use this oportunity to explore a new technology, and the chosen one was Padrino.

Padrino is a Ruby framework built upon Sinatra: Sinatra provides a simple DSL to create web applications and Padrino adds some usefull tools to automate the creation of routes, models, anyway, the project’s bootstrap.

One of the steps for creating a Padrino app is to choose an ORM. Padrino supports many ORMs, such ActiveRecord, MongoID, DataMapper, etc.

Well, in my project, I’ve choose ActiveRecord with Postgres. Why? Because I’ve had some familiarity with ActiveRecord from Rails.

Everything was going well, the system taking form but then, I’ve realized that a relational database wouldn’t be the best choice for my problem.

So, I’ve decided to change from an ORM to a ODM (object document mapping), and the chosen one was MongoID + MongoDB.

The challenge now is: how to move from ActiveRecord to MongoID?

After some research, I’ve realized that Padrino doesn’t provide an automated way to disable the old ORM and enable the new one. So, we need to do this manually.


1. Creating a new Padrino project

Before we start our PoC, we need a Padrino project. So, let’s create a new one with the following commands:

# Install Padrino gem
gem install padrino

# Create a new Padrino project, using:
# - RSpec as the test framework
# - ActiveRecord as the ORM;
# - Sqlite as the database
padrino g project padrino_orm_poc -t rspec -d activerecord

Padrino will create the project bootstrap, as expected.

Now, let’s create our Models: User and Post

# Change to Padrino project root
cd padrino_orm_poc

# Install necessary gems
bundle install

# Create User model, with:
# - Name;
# - Age;
padrino g model user name:string age:integer

# Create Post model, with:
# - Title;
# - Content;
padrino g model post title:string content:text

And then, preparing database to receive our models:

# Edit config/database.rb to point to our Postgres database.
# (Here, 'postgres' points to my Postgres database IP. Change it to match your reality)
ActiveRecord::Base.configurations[:development] = {
  :adapter => 'sqlite3',
  :database => Padrino.root('postgres', 'padrino_orm_poc_development.db')
}

ActiveRecord::Base.configurations[:production] = {
  :adapter => 'sqlite3',
  :database => Padrino.root('postgres', 'padrino_orm_poc_production.db')
}

ActiveRecord::Base.configurations[:test] = {
  :adapter => 'sqlite3',
  :database => Padrino.root('postgres', 'padrino_orm_poc_test.db')
}

# Create database schema for the three environments:
# development, test and production
RACK_ENV=development rake db:create db:migrate
RACK_ENV=test rake db:create db:migrate
RACK_ENV=production rake db:create db:migrate

Now, we need to implement our models. So, here they are:

# User Model
class User < ActiveRecord::Base
  has_many :posts
end

# Post Model
class Post < ActiveRecord::Base
  belongs_to :user
end

Now, let’s make sure our models are interacting correctly. Let’s create a simple test in User spec that:

  1. Creates an user;
  2. Creates 3 posts;
  3. Associates posts to users;
  4. Saves the user;

So, here is the test code (spec/models/user.rb):

require 'spec_helper'

RSpec.describe User do
  context 'Create a new user' do
    it 'with posts' do
      user = User.new(name: 'John', age: 25)
      user.save!

      post_1 = Post.new(title: 'Post title 1', content: 'Post 1 content')
      post_2 = Post.new(title: 'Post title 2', content: 'Post 2 content')
      post_3 = Post.new(title: 'Post title 3', content: 'Post 3 content')

      user.posts << post_1
      user.posts << post_2
      user.posts << post_3
      user.save!

      # Load user from database
      user = User.find(user.id)
      expect(user.posts.length).to eq(3)
    end
  end
end

Now, let’s run rspec and see if everything is working as expected:

$ rspec

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Post add some examples to (or delete) /root/padrino/padrino_orm_poc/spec/models/post_spec.rb
     # Not yet implemented
     # ./spec/models/post_spec.rb:4

Failures:

  1) User Create a new user with posts
     Failure/Error: user.posts << post_1
     ActiveModel::MissingAttributeError:
       can't write unknown attribute `user_id`

Of course, we forgot to add an user_id column on the Post’s table, so the association is invalid.

Let’s fix it:

# Create the migration...
padrino g migration AddUserIdToModel

# ...set migration's content...
class AddUserIdToModel < ActiveRecord::Migration
  def change
    add_reference :posts, :user, index: true
  end
end

# ...and run migration in all 3 environments
RACK_ENV=development rake db:migrate
RACK_ENV=test rake db:migrate
RACK_ENV=production db:migrate

Running rspec again:

$ rspec

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Post add some examples to (or delete) /root/padrino/padrino_orm_poc/spec/models/post_spec.rb
     # Not yet implemented
     # ./spec/models/post_spec.rb:4

It’s saying that Post tests are missing. That’s OK, because our test is implemented in User spec. So, just delete spec/models/post_spec.rb and run rspec again:

$ rspec

.

Finished in 0.12314 seconds (files took 0.4811 seconds to load)
1 example, 0 failures

Great! We now have a simple Padrino project, running with two models (User and Post), a simple test with RSpec and using ActiveRecord + Sqlite.

It’s time to move to MongoID…


2. Adding MongoID support

Moving from ActiveRecord to MongoID is a three step work:

  1. Add support to MongoID;
  2. Adjust project for using MongoID instead of ActiveRecord;
  3. Remove ActiveRecord from project;

2.1. Adding support to MongoID

In order to use MongoID on your project, we basically need to:

  1. Install MongoID gem;
  2. Create connection to MongoDB database;

So, let’s do it:

# First of all, add the following line to you Gemfile...
gem 'mongoid'

# ...and then, rerun bundle install
bundle install

The next step is change .components file to use MongoID instead of ActiveRecord. So, modify this file as following:

# Replace this line...
:orm: activerecord

# ...by this one
:orm: mongoid

Now, we need to create config/mongoid.yml file. This file is used to configure MongoDB connections used on our project. It’s like the config/database.rb for ActiveRecord.

So, create the config/mongoid.yml file with the following content:

# Here, I have 'mongo' pointing to my MongoID database IP
# (Change this value to match your reality).
development:
  sessions:
    default:
      database: mongoid
      hosts:
        - mongo:27017

test:
  sessions:
    default:
      database: mongoid
      hosts:
        - mongo:27017

production:
  sessions:
    default:
      database: mongoid
      hosts:
        - mongo:27017

And, finally, create a new connection with MongoDB. So:

# In lib/connection_pool_management.rb, remove this line...
ActiveRecord::Base.connection_pool.with_connection { @app.call(env) }

# ...and, in config/boot.rb, add these lines AFTER THE LAST 'REQUIRE' STATEMENT
require 'mongoid'
Mongoid.load!('config/mongoid.yml', RACK_ENV)

Ok, now we have MongoID enabled in our project.

It’s time to adjust our models to use MongoID instead of ActiveRecord.


2.2. Adjusting Models

We need to remove every reference to ActiveRecord from our models and replace it by the equivalent one for MongoID.

Let’s do it:

# Old User Model:
class User < ActiveRecord::Base
  has_many :posts
end

# New User Model
class User
  include Mongoid::Document
  field :name, type: String
  field :age, type: Integer

  embeds_many :posts
end

# Old Post Model:
class Post < ActiveRecord::Base
  belongs_to :user
end

# New Post Model:
class Post
  include Mongoid::Document
  field :title, type: String
  field :content, type: String

  embedded_in :user
end

And, finally, let’s rerun rspec and see if our modifications didn’t break anything:

$ rspec

Finished in 0.0115 seconds (files took 0.66445 seconds to load)
1 example, 0 failures

SUCCESS!!

We now have our Padrino project running with MongoID + MongoDB;

It’s time to clean up the house, removing ActiveRecord.


2.3. Getting rid of ActiveRecord

To remove ActiveRecord, we need to:

# Delete the following resources...
config/database.rb
db/
postgres/

# ...remove this line from Rakefile...
PadrinoTasks.use(:activerecord)

# ...and these lines from Gemfile:
gem 'activerecord'
gem 'sqlite3'

Now, rerun bundle install and a last rspec to make sure everything is still working:

$ bundle install
$ rspec

.

Finished in 0.00805 seconds (files took 0.52411 seconds to load)
1 example, 0 failures

CONGRATULATIONS!

You have moved your project from ActiveRecord + Sqlite to MongoID + MongoDB \o/\o/\o/


Conclusion

Padrino is a great framework for those who wants to enjoy the power of Sinatra without needing to configure everything manually. However, even with a large number of generators and many usefull tools that save you a lot of development time, there are situations where you will need to roll up your sleeves and dive into Padrino’s internals. In those cases, Padrino and Sinatra’s documentation are your good allies.

And, if nothing else works, well, just leave the gun and take the cannoli.

The project used in this tutorial is available on Github. You can access it here