MongoDB の $exists
の挙動
以下のようなデータがあるとする。
> db.items.find()
{ "_id" : ObjectId("5de50feab2e406088d483c44"), "name" : "item01", "color" : "red", "size" : "M" }
{ "_id" : ObjectId("5de51001b2e406088d483c45"), "name" : "item02", "color" : "blue", "size" : "S", "memo" : null }
{ "_id" : ObjectId("5de51012b2e406088d483c46"), "name" : "item03", "color" : "yellow", "size" : "L", "memo" : "" }
{ "_id" : ObjectId("5de51029b2e406088d483c47"), "name" : "item04", "color" : "green", "size" : "M", "memo" : "hoge" }
このとき $exists
を使って memo があるデータだけを取ろうとすると以下のようになる。
> db.items.find({memo: {$exists: true}})
{ "_id" : ObjectId("5de51001b2e406088d483c45"), "name" : "item02", "color" : "blue", "size" : "S", "memo" : null }
{ "_id" : ObjectId("5de51012b2e406088d483c46"), "name" : "item03", "color" : "yellow", "size" : "L", "memo" : "" }
{ "_id" : ObjectId("5de51029b2e406088d483c47"), "name" : "item04", "color" : "green", "size" : "M", "memo" : "hoge" }
$exists
は対象のフィールドが存在するかどうかのみを評価している。
したがって、 memo フィールドの値が null でも空でも、 $exists
では true として扱われる。
ここまでは MongoDB の $exists
の話であり、何も難しいことはない。
Mongoid の exists
の挙動
Mongoid は Ruby 上で MongoDB を扱うための ODM だ。
当然 MongoDB の $exists
に相当するものが存在する。
まず、以下のようなクラスを定義しておく。
class Item
include Mongoid::Document
field :name
field :color
field :size
field :memo
end
以下の1行を実行すれば全件取得できる。
(ここでは rails console を利用している)
irb(main):001:0> Item.each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [1] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [1] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de50feab2e406088d483c44, item01, red, M, "
"5de51001b2e406088d483c45, item02, blue, S, "
"5de51012b2e406088d483c46, item03, yellow, L, "
"5de51029b2e406088d483c47, item04, green, M, hoge"
=> nil
exists
を使うと以下のようになる。
irb(main):002:0> Item.exists(memo: true).each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [2] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"memo"=>{"$exists"=>true}}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [2] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de51001b2e406088d483c45, item02, blue, S, "
"5de51012b2e406088d483c46, item03, yellow, L, "
"5de51029b2e406088d483c47, item04, green, M, hoge"
=> nil
Item.exists(memo: true)
の代わりに Item.where(:memo.exists => true)
と書くこともできる。
ここまでは MongoDB の $exists
と同じ挙動になる。
Mongoid で default 値が設定されている場合の挙動
memo フィールドにモデル側で default を設定することができる。
class Item
include Mongoid::Document
field :name
field :color
field :size
field :memo, default: 'empty'
end
rails console でモデルの変更を読み込むためリロードを行う。
irb(main):003:0> reload!
Reloading...
=> true
全件取得すると以下のようになる。
irb(main):004:0> Item.each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [3] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [3] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de50feab2e406088d483c44, item01, red, M, empty"
"5de51001b2e406088d483c45, item02, blue, S, "
"5de51012b2e406088d483c46, item03, yellow, L, "
"5de51029b2e406088d483c47, item04, green, M, hoge"
=> nil
item01 の memo に default 値が設定されていることがわかる。
この状況で先ほどと同様に exists
で絞り込むと以下のようになる。
irb(main):005:0> Item.exists(memo: true).each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [4] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"memo"=>{"$exists"=>true}}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [4] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de51001b2e406088d483c45, item02, blue, S, "
"5de51012b2e406088d483c46, item03, yellow, L, "
"5de51029b2e406088d483c47, item04, green, M, hoge"
=> nil
item01 が出力されない。
つまり default 値の有無は exists
の結果に影響しない。
default はあくまでそのフィールドが存在しない時にアプリケーション側で扱いたい値をセットしておくためのものである。
これを利用して、アプリケーション側でその値が default 値なのかセットされた値なのかを区別することができる。
items に1件追加する。
> db.items.insert({name: 'item05', color: 'orange', size: 'S', memo: 'empty'})
WriteResult({ "nInserted" : 1 })
rails console で全件取得すると以下のようになる。
irb(main):006:0> Item.each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [5] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [5] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de50feab2e406088d483c44, item01, red, M, empty"
"5de51001b2e406088d483c45, item02, blue, S, "
"5de51012b2e406088d483c46, item03, yellow, L, "
"5de51029b2e406088d483c47, item04, green, M, hoge"
"5de51b39b2e406088d483c4a, item05, orange, S, empty"
=> nil
この結果から item01 の memo と item05 の memo が同一かどうかは区別がつかない。
irb(main):007:0> Item.find_by(name: 'item01').memo == Item.find_by(name: 'item05').memo
MONGODB | [6] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item01"}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [6] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"name"=>"item05"}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> true
exists
を使えば区別できる。
irb(main):008:0> Item.exists(memo: true).each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [8] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"memo"=>{"$exists"=>true}}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [8] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de51001b2e406088d483c45, item02, blue, S, "
"5de51012b2e406088d483c46, item03, yellow, L, "
"5de51029b2e406088d483c47, item04, green, M, hoge"
"5de51b39b2e406088d483c4a, item05, orange, S, empty"
=> nil
逆を取りたい場合は以下。
irb(main):009:0> Item.exists(memo: false).each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"memo"=>{"$exists"=>false}}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.003s
"5de50feab2e406088d483c44, item01, red, M, empty"
=> nil
あるいは、直接 where(memo: 'empty')
してしまうことでも item05 のみを取ることができる。
irb(main):010:0> Item.where(memo: 'empty').each { |i| p "#{i._id}, #{i.name}, #{i.color}, #{i.size}, #{i.memo}" }; nil
MONGODB | [10] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"items", "filter"=>{"memo"=>"empty"}, "lsid"=>{"id"=><BSON::Binary:0x70346640265920 type=uuid data=0x672fb96b60a9434f...>}}
MONGODB | [10] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
"5de51b39b2e406088d483c4a, item05, orange, S, empty"
=> nil