Posted at
MongoDBDay 6

mongoidバッドノウハウ集

More than 5 years have passed since last update.

この記事はMongoDB Advent Calendar 2013の6日目です。

mongoidについてはnaru0gaさんの大変わかりやすい説明がありましたので、今回はmongoidのまともじゃない使い方をご紹介させていただきたいと思います。


mongoidについて

ruby用のODMマッパーです。くわしくはこちらを御覧ください。


MySQLと共存させる

リレーションが必要なモデル、飛んだら困るデータの格納はMySQLで、それ以外はMongoDBを使いたい、という場合にリレーショナルデータベースと共存させる方法です。

MongoDBのAdvent Calenderに何書いてんだと罵られそうですが、MongoDBだけで生きていくにはガッツの足りない方(自分含め)向けにMySQLと共存させる方法を紹介します。

まず、プロジェクト作成時、普通にMySQLをドライバとして指定します。

rails new hogehoge -d mysql

Gemfileにmongoidを記述してbundle install します。


Gemfile

gem "mongoid", "~> 3.0.0"


この手順だとmongoid.ymlが作られないので、自前で用意する必要があります。

これでデフォルトがActivRecordとなるプロジェクトができました。

MongoDB用モデルが必要な場合だけ、railsコマンドにmongoidを付けます。

rails g mongoid:model fugafuga

デフォルトのストアをMongoに変更する場合はconfig/application.rbの指定を変更します。


application.rb

    # Generatorのところをmongoidに変更

config.generators do |g|
g.orm :mongoid
end


モデル名と異なるコレクションにクエリを発行したい

たとえば月ごとのデータや時間ごとのログデータ走査に時間がかかるので、log_month201301〜201312、log_hour01〜24のように、月・時間ごとに分けてコレクションにデータを投入するケースを考えます。

とはいえ月・時ごとにモデルを作るのは避けたいところ。ひな形を用意して1モデルで済ませたい場合などに最適な手段といえます。

Mongoモヒカンの方々から全力でSharding使えよカスというツッコミを受けそうですが、バッドノウハウ集なのでmongoidで解決させます。問題ありません。

mongoidではwithでコレクションを指定することでモデルと異なるコレクションを指定できます。

LogMonth.with(collection:"log_month201301").where(name:"unko");

注意点としては、withを使用して存在しないコレクションに対してcreateを行うとコレクションが存在しないエラーになるので、create!を使用します。

LogMonth.with(collection:"log_month201301").create!({name:"unko",insertde_at: Time.now})

設定より規約?何それおいしいの?とでも言えそうなバッドノウハウその一ですね。


モデル名と異なるコレクションにキーを貼りたい

さて、モデルと関連していないコレクションにドキュメントの登録と、検索ができるようになりました。

こうなると次にやりたいのは、そのコレクションに対してキーを貼りたいということではないでしょうか。

mongoidの場合、キー登録はrakeタスクになりますので、通常アプリケーション側からキー登録をすることはありません。

rake db:mongoid:create_indexes

これをアプリケーション側からやるとブラックボックスになる悪寒がバリバリしますね。まさにバッドノウハウ一直線。いい感じです。

やり方は簡単で、collection指定とともにcreate_indexesを実行するだけです。

LogMonth.with(collection:"log_month201301").create_indexes

気をつけないといけないのは、参照するインデックス情報がMongoDBとの接続を持ってるLogMonthモデルのindexに限られることと、2つ目以降のインデックスは無視されます。


log_month.rb

class LogMonth

include Mongoid::Document
field :name, type: String
field :created_at, type: Time

index({created_at: 1}) ←こちらは貼られる
index({name: 1}) ←こちらは無視される


log_month.rbのindex記述を元に、log_month201301コレクションのcreated_atカラムにキーが貼られます。

cuiで確認します。

 > db.log_month201301.getIndexes()

[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "hogehoge_development.log_month201301",
"name" : "_id_"
},
{
"v" : 1,
"key" : {
"created_at" : 1
},
"ns" : "hogehoge_development.log_month201301",
"name" : "created_at_1"
}
]

キーが貼られているのが確認できると思います。


mongoidから複雑なクエリを投げる

mongoidでAND/ORを指定する場合は.and.orを使います。

FugaFuga.where({name: "Taro"}).or({name: "Hanako"})

FugaFuga.where({name: "Taro").and({name: "Hanako"})
FugaFuga.where(:birth_date.gte => 20000101)
FugaFuga.where(:birth_date.lte <= 200012031)

単純な論理演算子はこの記述でできるんですが、「(誕生日が2000年1月1日〜2000年12月31日までで、名前が太郎)または(性別が花子)」というクエリはこの記述方法ではできません。

別々にやれよとかそもそもMongoDBでそんなクエリを投げるななどというまっとうな意見も出てきそうですが、今回のエントリはあくまでバッドノウハウ(略

collection経由でmongoクエリをrubyハッシュで渡すことで、想定するクエリを投げることができます。

jsonハッシュではなく、rubyハッシュなところが要注意です。

FugaFuga.collection.find({"$or" => 

{"name" => "Taro" , "birth_date"=>{"$gte"=>20000101, "$lte"=>20001231}},
{"name" => "Hanako", "gender" => "woman" }
})

突然引き継いだプロジェクトで、こんな記述が出てきたらたまったものではないです。自分なら前任者を探しだして小一時間説教してやるところですが、このプロジェクトは自分しか使ってないので問題ありません。


おわりに

いかがでしたでしょうか。個人的にはこのような記述が存在するようなプロジェクトには関わりたくはないと思いますね。