Ruby
Rails
MongoDB
Heroku
mongoid

RailsでActiveRecordとMongoidの共存のさせ方

More than 5 years have passed since last update.

背景

Herokuで適当にスキーマレスなデータをOutput出来る場所がほしい!でも、Herokuはディスクが自由に使えないので、text書き出しができない!!
こんな要求の解決法としては、textタイプのフィールドを作ってserializestoreを使うのが一つの手。ただ、割と書き込みが多い事が想定され、無駄に大切なPostgreSQLサーバーに負荷をかけてしまう。
そんなこんなでスキーマレスな出力場所としてMongoDBを使うというアイデアが持ち上がった。HerokuではMongoHQなら16M、MongoLabなら0.5Gが無料で使える。
なお、基本的に、MongoDBをサブとして使うのでrails g modelなどをした時にmongoidがinvokeされるのは避けたい。

実践

gem 'mongoid'

して、bundle install

$ rails generate mongoid:config

すると、config/mongoid.ymlにdevelopmentとtestの設定が生成されるので、ここにconfigurationを書く。基本的には変更する必要がないが、database, username, passwordあたりの書き換え、書き足しだけチェックしてみればいいのではないだろうか?

productionの設定はHerokuのAdd-onのドキュメント通りにすれば良いと思う。MongoHQのドキュメントの方が詳しく書かれていたのでこっちを参照した。使ったのはMongoLabだけど(笑)

ちゃんと設定したければ、
http://mongoid.org/en/mongoid/docs/installation.html
にそれぞれオプションが詳しく載っている。

mongoidのモデルはrails generate modelでできるようになっている。

$ rails generate model user_mongo name:string
    invoke  mongoid
    create    app/models/user_mongo.rb
app/models/user_mongo.rb
class UserMongo
  include Mongoid::Document
  field :name, type: String
end

ということは、ActiveRecordのmodelを作るとき、active_record:とprefixをつける必要がある。

$ rails generate acrive_record:model

これはmigrationコマンドやgemのdeviseなどでも同様。

これが困る場合、application.rb

config/application.rb
config.generators do |g|
  g.orm :active_record
end

と追加すれば良い。このときmongoidのモデルを作るときは

$ rails generate mongoid:model

とする。

最後に、Mongoidの使い方は、適当に自分で調べて使ってみれば意外と簡単に使えると思うが、http://mongoid.org/en/mongoid/docs/relations.html は読んでおくと良いと思う。多分ActiveRecordとはちょっと違っていて難しいであろう、Relation/Associationのあたりの話がちゃんと書いてある。

参考

追記: ロードするclassの設定

mongoidのgemを見ると設定なしではapps/model以下の全てのRubyファイルをロードしてしまう設定になっていた。

これを防ぐためには、optionspreload_modelsにMongoid::DocumentをIncludeしているクラスを全て指定すれば良い。
例えば、data_mongo.rbuser_monbo.rbを作った場合は、

mongoid.yml
production:
  # ...
  options:
    preload_models:
      - data_mongo
      - user_mongo

というように設定する。

一応、今後解決する可能性もあるので、mongoidの問題となっている部分のを載せておく

mongoid3.0.22/lib/rails/mongoid.rb
    def load_models(app)
      app.config.paths["app/models"].each do |path|
        preload = ::Mongoid.preload_models
        if preload.resizable?
          files = preload.map { |model| "#{path}/#{model}.rb" }
        else
            files = Dir.glob("#{path}/**/*.rb")
        end

        files.sort.each do |file|
          load_model(file.gsub("#{path}/" , "").gsub(".rb", ""))
        end
      end
    end