mongoidバッドノウハウ集

この記事は[MongoDB Advent Calendar 2013](http://qiita.com/advent-calendar/2013/mongodb)の6日目です。

mongoidについては[naru0ga](http://qiita.com/naru0ga)さんの[大変わかりやすい説明](http://qiita.com/naru0ga/items/eaa5be47c10800c98656)がありましたので、今回はmongoidのまともじゃない使い方をご紹介させていただきたいと思います。

#mongoidについて
ruby用のODMマッパーです。くわしくは[こちら](http://d.hatena.ne.jp/naruoga/20131202/1385983516)を御覧ください。

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

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

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

```
rails new hogehoge -d mysql
```

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

```rb:Gemfile
gem "mongoid", "~> 3.0.0"
```
この手順だと`mongoid.yml`が作られないので、自前で用意する必要があります。
これでデフォルトがActivRecordとなるプロジェクトができました。

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

```
rails g mongoid:model fugafuga
```

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

```rb:application.rb
    # Generatorのところをmongoidに変更
    config.generators do |g|
      g.orm :mongoid
    end
```

#モデル名と異なるコレクションにクエリを発行したい
たとえば月ごとのデータや時間ごとのログデータ走査に時間がかかるので、log_month201301〜201312、log_hour01〜24のように、月・時間ごとに分けてコレクションにデータを投入するケースを考えます。
とはいえ月・時ごとにモデルを作るのは避けたいところ。ひな形を用意して１モデルで済ませたい場合などに最適な手段といえます。

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

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

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


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

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

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

#モデル名と異なるコレクションにキーを貼りたい
さて、モデルと関連していないコレクションにドキュメントの登録と、検索ができるようになりました。
こうなると次にやりたいのは、そのコレクションに対してキーを貼りたいということではないでしょうか。

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

```
rake db:mongoid:create_indexes
```

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

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

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

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

```rb: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で確認します。

```js
 > 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ハッシュなところが要注意です。

```rb
FugaFuga.collection.find({"$or" => 
    {"name" => "Taro" , "birth_date"=>{"$gte"=>20000101, "$lte"=>20001231}},
    {"name" => "Hanako", "gender" => "woman" }
})
```
突然引き継いだプロジェクトで、こんな記述が出てきたらたまったものではないです。自分なら前任者を探しだして小一時間説教してやるところですが、このプロジェクトは自分しか使ってないので問題ありません。

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