LoginSignup
2
0

More than 3 years have passed since last update.

MongoDB/Mongoid の exists を整理する

Last updated at Posted at 2019-12-02

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 に相当するものが存在する。

まず、以下のようなクラスを定義しておく。

app/models/item.rb
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 を設定することができる。

app/models/Item.rb
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
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0