はじめに
Mongoid は Ruby もしくは Rails から MongoDB を使う場合の標準的な OR マッパー(公式には Object-Document-Mapper)です。
Mongoid は Mongoid::Criteria
による Active Record によく似たクエリインターフェースがあります。しかし、Active Record にはあって Mongoid にないメソッドや、同じメソッド名でも引数や例外の有無が違うという事があり、混乱することがあります。
そこで、Rails ガイドの Active Record クエリインターフェイス の内容について Mongoid から見た Active Record との違いを確認してみました。
もともと Mongoid と Active Record は継承関係や include しているモジュールが大きく異なるため、ここで取り上げていない違いはたくさんありますがご了承ください。
Mongoid から見た Active Record との違い
take
がない
Active Record の take
はレコードを1つ取り出しますが、Mongoid にはありません。
> ActiveRecordModel.take
=> #<ActiveRecordModel:0x007f96520a1ed8
id: 1, ...
> MongoidModel.take
NoMethodError: undefined method `take' for MongoidModel:Class
first
, last
に引数を指定できない
first
は主キー順の最初のデータ、last
は最後のデータを取り出します。
Active Record ではこれらのメソッドに引数を指定するとその最大値の配列を返します。
> ActiveRecordModel.first(3)
=> [#<ActiveRecordModel:0x007f96520a1ed8
id: 1, ...],
#<ActiveRecordModel:0x007f96528a1fe8
id: 2, ...],
#<ActiveRecordModel:0x007f96521dccd0
id: 3, ...]]
> ActiveRecordModel.last(3)
=> [#<ActiveRecordModel:0x007f9652cf27a0
id: 6, ...],
#<ActiveRecordModel:0x007f9652cf28e0
id: 7, ...],
#<ActiveRecordModel:0x007f9652cf2a20
id: 8, ...]]
Mongoid では引数を指定することが出来ません。
> MongoidModel.first(3)
ArgumentError: wrong number of arguments (given 1, expected 0)
> MongoidModel.last(3)
ArgumentError: wrong number of arguments (given 1, expected 0)
find_by
でヒットしなかった場合に例外が発生する
find_by
は与えられた条件にマッチするデータのうち、最初のデータを返します。
Active Record ではヒットしなかった場合 nil
を返します。
> ActiveRecordModel.find_by(name: "xxx")
=> nil
Mongoid ではヒットせず、さらに Mongoid.raise_not_found_error
が true
だとMongoid::Errors::DocumentNotFound
例外が発生します。
> MongoidModel.find_by name: "unknown"
[MONGO] { "find" => "clients", "filter" => { "name" => "unknown" }, "sort" => { "_id" => 1 } } (23.6 ms)
Mongoid::Errors::DocumentNotFound:
message:
Document not found for class MongoidModel with attributes {:name=>"unknown"}.
summary:
summary:
When calling StationMaster.find_by with a hash of attributes, all attributes provided must match a document in the database or this error will be raised.
resolution:
(省略)
where
の検索文字列埋め込み
Active Record では、where
の引数に ?
や変数を埋め込んだ文字列を渡して検索ができます。
# ?を使った場合
> Client.where("orders_count = ?", params[:orders])
# プレースホルダを使った場合
> Client.where("orders_count = #{params[:orders]}")
Mongoid で同じように実行するとエラーになります。
# ?を使った場合
> ActiveRecordModel.where("orders_count = ?", params[:orders])
ArgumentError: wrong number of arguments (given 2, expected 1)
# プレースホルダを使った場合
> ActiveRecordModel.where("orders_count = :orders_count", {orders_count: params[:orders]})
ArgumentError: wrong number of arguments (given 2, expected 1)
代わりに for_js
を使用できます。
> MongoidModel.for_js("this.name = param", param: "Tool").first
=> #<Mongoid::Criteria
selector: {"$where"=>#<BSON::CodeWithScope:0x007f9652b39738 @javascript="this.name = param", @scope={:param=>"Tool"}>}
options: {:sort=>{"_id"=>1}}
class: MongoidModel
embedded: false>
where
だと次の様な指定もできます。
> MongoidModel.where("orders_count == #{params[:orders}")
=> #<Mongoid::Criteria
selector: {"$where"=>"orders_count == xxxx"}
options: {:sort=>{"_id"=>1}}
class: MongoidModel
embedded: false>
ちなみに Rails ガイドにも書かれているとおり、この埋込み例はパラメータをそのまま渡しているので注意が必要です。通常は避けるべきです。詳しくはRuby on Railsセキュリティガイドを参照してください。
join
,joins
,left_outer_joins
はない
そもそも MongoDB ではコレクション(RDB のテーブルに該当するもの)結合の機能がないため、JOIN に関するメソッドもありません。
> MongoidModel.respond_to?(:join)
false
> MongoidModel.respond_to?(:joins)
false
> MongoidModel.respond_to?(:left_outer_joins)
false
MongoDB 3.2 で JOIN に似た $lookup が追加されましたが、今のところ Mongoid では応していません。
サブセット条件は any_of
(または or
)
Active Record の場合、where
の条件ハッシュに配列を指定して、SQL の IN 式を作ることができます。
> ActiveRecordModel.where(orders_count: [1,3,5]).size
=> 3
これに対し、Mongoid では次の様に1つずつ条件ハッシュの配列を any_of
もしくは or
に渡します。
> MongoidModel.any_of([{orders_count: 1} ,{orders_count: 3}, {orders_count: 5}])
=> 3
# or は any_of のエイリアス
> MongoidModel.or([{orders_count: 1} ,{orders_count: 3}, {orders_count: 5}])
=> 3
where.not
がない
Active Record の否定条件は where.not
で表現できますが、Mongoid では
> MongoidModel.not_in(id: [1])
もしくは
> MongoidModel.where(:id.nin => [1])
になります。
特定のフィールドを取り出す only
Active Record では特定のレコードのフィールドを取り出すのに select
を使います。
ActiveRecordModel.select(:id).first
#<ActiveRecordModel:0x00000015dae6d8> {
:id => 1
}
Mongoid では only
を使います。
> MongoidModel.only(:id).first
#<MongoidModel:0x00000016a906a8> {
:_id => 1
}
find_by_フィールド名
がない
Active Record では、テーブルに定義されたフィールドに対して自動的に検索メソッドが追加されます。
例えば Client
モデルに name
というフィールドがあった場合、自動的に find_by_name
が追加されます。引数に指定した値をそのフィールドで検索してくれる便利な機能ですが、Mongoid ではこの仕組みが存在しません。
SQL による検索はできない
MongoDB には SQL がないため、Active Record に存在する次のメソッドもありません。
ids
がない
Active Record では、テーブルの主キーを使用するリレーションのIDをすべて取り出す ids
がありますが、Mongoid にはありません。
> MongoidModel.respond_to?(:ids)
=> false
必要であれば代わりに pluck(:_id)
を使う事になると思います。
> MongoidModel.pluck(:_id)
exists?
に引数を指定できない
Active Record では、テーブルにデータが存在するかを確認する exists?
があり、引数に検索条件を指定する事ができます。メソッド名の ?
から推測できるように、戻り値は Boolean
です。
> ActiveRecordModel.exists(3)
=> true
Mongoid では exists?
はありますが、引数を指定することが出来ません。
> MongoidModel.exists?
=> true
> MongoidModel.exists?(3)
ArgumentError: wrong number of arguments (given 1, expected 0)
any?
, many?
がない
Active Record では、1件以上レコードが存在するかを判定する any?
と2件以上レコードが存在するかを判定する many?
がありますが、Mongoid にはこれらのメソッドがありません。
> ActiveRecordModel.find(:all).many? { |model| model.price <= 1000000 }
=> false
> ActiveRecordModel.find(:all).any? { |model| model.price <= 1000000 }
=> true
> MongoidModel.find(:all).any? { |model| model.price <= 1000000 }
NoMethodError: undefined method `any?' for MongoidModel:Class
> MongoidModel.find(:all).many? { |model| model.price <= 1000000 }
NoMethodError: undefined method `many?' for MongoidModel:Class
count?
に引数を指定できない
Active Record では、引数に指定したフィールドが値を持つレコードの合計を返す count?
がありますが、Mongoid では引数を指定できません。
> ActiveRecordModel.count(:age)
=> 10
> Mongoid.count(:age)
ArgumentError: wrong number of arguments (given 1, expected 0)
平均、最小値、最大値
Active Record では、平均を返す average
、最小値を返す minimum
、最大値を返す maximum
があります。
> ActiveRecordModel.average("age")
=> 35.8
> ActiveRecordModel.minimum("points")
=> 10
> ActiveRecordModel.maximum("points")
=> 44
Mongoid ではそれぞれ avg
、min
、max
が対応します。
> MongoidModel.avg("age")
=> 21.304196978175714
> MongoidModel.min("age")
=> 1
> MongoidModel.max("age")
=> 32
なぜ同じ名前にしなかったのでしょうか。
おまけ
メソッドの定義場所を調べる
RubyMine のコードジャンプ機能が便利で大体正しい定義へジャンプ・表示できます。同名の定義がヒットした場合には、ファイルパスと一緒に一覧が表示されます。
他には次の方法があります。
Mongoid は YARD によるドキュメントが採用されているので、ここから確認することもできます。