Mongodb in Rails4

In the past week i had tried mongodb in rails. I rewrite my previous project money to use mongodb instead of active record. The source code is in branch money-mongodb. Here i record the steps of changing active record to mongodb

Install mongodb

I am using Mac OSX, very easy to install

1
brew install mongodb

Run mongodb in your shell

1
mongod

Update Gemfile

I choose mongoid gem because it has good documents and it’s a object document mapper framework. I am familiar with SQL so i could catch up NonSQL database easily.
Need to add github: 'mongoid/mongoid' because Rails4.

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

Generate mongoid config

1
rails g mongoid:config

Mark active record related codes

In application.rb mark the require rails/all and add below.

application.rb
1
2
3
4
5
# require 'rails/all'
require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"

In development.rb mark the config.active_record.migration_error = :page_load

development.rb
1
# config.active_record.migration_error = :page_load

Re-generate devise

Mark devise_for :users in routes.rb

routes.rb
1
# devise_for :users

Execute devise:install

1
rails g devise:install

Remember to unmark devise_for after new user.rb created. New user.rb looks like below, it includes Mongoid::Document.

user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class User
  include Mongoid::Document
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  ## Database authenticatable
  field :email,              :type => String, :default => ""
  field :encrypted_password, :type => String, :default => ""

  ...
end

Update models

Bascially we just need to include Mongoid::Document and add field

category.rb
1
2
3
4
5
6
7
# class Category < ActiveRecord::Base
# end
class Category
  include Mongoid::Document
  field :name, type: String
  field :cid, type: Integer
end

Update logic of querying data in model

This is the most difficult part. Model has methods to query data from database. All need to rewrite.

No where.not.

1
2
# scope :expense, -> { where.not(category_id: 0) }
scope :expense, -> { where(:category_id.gt => 0) }

Order by

1
2
# where.order('month ASC')
where.asc(:month)

Query condition.

1
2
#r = where('year = ?', params[:y])
r = where(year: params[:y])

No group(). Need to use map/reduce funciton. Good tutorial.
For instance: calculate each month’s total cost.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# r.expense.group(:month).select(:month, 'sum(amount) as total')
map = %Q(
  function(){
    emit(this.month, { month:this.month, amount:this.amount});
  }
)
reduce = %Q(
  function(key, values) {
    var result = { month: 0, total: 0 };
    values.forEach(function(value) {
      result.month = value.month;
      if (value.amount)
        result.total += value.amount;
      else
        result.total += value.total;
      });
    return result;
  }
)
final = %Q(
  function(key, value) {
    if (value.amount)
      value.total = value.amount
    return value;
  }
)
result = []
where(year: params[:y]).map_reduce(map, reduce).out(inline: true).finalize(final).each do |document|
  result.push self.build_money_record_by_month(document)
end

Fecth data from reduced document. If there’s more easy way to fetch data from document, plz let me know.

1
2
3
4
5
6
7
def self.build_money_record_by_month(document)
  value = document.fetch 'value'
  monny = MoneyRecord.new
  monny.total = value.fetch 'total'
  monny.month = value.fetch 'month'
  return monny
end

Update cucumber and rspec testing

In spec/spec_help.rb

spec_help.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# mark below 
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
# config.use_transactional_fixtures = true

# unmark below
config.before(:suite) do
  DatabaseCleaner.strategy = :truncation # must be truncation
end
config.before(:each) do
  DatabaseCleaner.start
end
config.after(:each) do
  DatabaseCleaner.clean
end

In feature/env/rb

env.rb
1
2
3
4
5
begin
  DatabaseCleaner.strategy = :truncation  # must be truncation
rescue NameError
  raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
end

That’s it. But i still have a question not solved. During my trying the mongod process was crash easily and no idea why. Error log like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Wed Oct 23 00:37:28.314 Invalid access at address: 0x10 from thread: conn5

Wed Oct 23 00:37:28.314 Got signal: 11 (Segmentation fault: 11).

Wed Oct 23 00:37:28.380 Backtrace:
0x100a3eaf0 0x1005515bd 0x1005518f8 0x7fff8904a90a 0 0x100b70cc7 0x100c3e2c9 0x100c3e131 0x1009f873b 0x1009f85ff 0x1009f32c2 0x1009f3093 0x1009f2c0f 0x100699c7d 0x1006a484c 0x1006c72f5 0x1006c82b3 0x1006c9096 0x1007dec8d 0x1007e50a8
 0   mongod                              0x0000000100a3eaf0 _ZN5mongo15printStackTraceERSo + 64
 1   mongod                              0x00000001005515bd _ZN5mongo10abruptQuitEi + 397
 2   mongod                              0x00000001005518f8 _ZN5mongo24abruptQuitWithAddrSignalEiP9__siginfoPv + 344
 3   libsystem_c.dylib                   0x00007fff8904a90a _sigtramp + 26
 4   ???                                 0x0000000000000000 0x0 + 0
 5   mongod                              0x0000000100b70cc7 _ZN2v88internal15DeoptimizerDataD1Ev + 55
 6   mongod                              0x0000000100c3e2c9 _ZN2v88internal7Isolate6DeinitEv + 105
 7   mongod                              0x0000000100c3e131 _ZN2v88internal7Isolate8TearDownEv + 81
 8   mongod                              0x00000001009f873b _ZN5mongo7V8ScopeD2Ev + 267
 9   mongod                              0x00000001009f85ff _ZN5mongo7V8ScopeD0Ev + 15
 10  mongod

Comments