意図しないものが更新されるリスクを避けるため、 Mongoid が提供する更新メソッドの挙動を整理しておく。
tl;dr
- 引数で指定した値 以外 に変更が及ぶのは
update_attribute
,update_attributes
,update
- たぶんこれが最も正しく認識しておくべきこと
- このあたりは ActiveRecord と同じ(だと思う)
-
upsert
とtouch
はそもそもの用途が異なる -
inc
,rename
,set
,unset
はアトミックな処理であるため、引数で指定した以外の変更は行われない
準備
データを1件用意しておく。
( _id
は削除して作り直したりする都合で途中から変わっている)
> db.items.find()
{ "_id" : ObjectId("5dee1c456694f6bc7bc3c52c"), "name" : "item1", "memo" : "initialized", "size" : "M" }
検証する各メソッドに対して、以下のオペレーションを行う。
- 更新対象のドキュメントを変数に格納する
- 変数の
size
の値を変更する - 更新メソッドの引数で
memo
に更新をかける-
memo
の更新ができないメソッドでは他のフィールドに更新をかける
-
update_attribute
単一のフィールドを更新するメソッド。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee1c456694f6bc7bc3c52c, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.update_attribute(:memo, 'updated')
=> true
irb(main):004:0> Item.find_by(name: 'item1')
=> #<Item _id: 5dee1c456694f6bc7bc3c52c, name: "item1", memo: "updated", size: "L">
memo
はもちろん、 size
も更新される。
update_attributes
複数のフィールドを更新するメソッド。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee1c456694f6bc7bc3c52c, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.update_attributes(memo: 'updated')
=> true
irb(main):004:0> Item.find_by(name: 'item1')
=> #<Item _id: 5dee1c456694f6bc7bc3c52c, name: "item1", memo: "updated", size: "L">
memo
も size
も更新される。
update
複数のフィールドを更新するメソッド。
これは update_attributes
のエイリアスなので上と同じ挙動をする。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee342b6694f6bc7bc3c535, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.update(memo: 'updated')
=> true
irb(main):004:0> Item.find_by(name: 'item1')
=> #<Item _id: 5dee342b6694f6bc7bc3c535, name: "item1", memo: "updated", size: "L">
当然 memo
も size
も更新される。
upsert
対象のデータに一致するものがあれば更新、なければ作成するメソッド。
irb(main):001:0> item = Item.new({name: 'item1'})
=> #<Item _id: 5dee2d1e21b62029414b31f5, name: "item1", memo: nil, size: nil>
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.upsert(memo: 'updated')
=> true
irb(main):004:0> Item.where(name: 'item1').to_a
=> [#<Item _id: 5dee1c456694f6bc7bc3c52c, name: "item1", memo: "initialized", size: "M">, #<Item _id: 5dee2d1e21b62029414b31f5, name: "item1", memo: nil, size: "L">]
当然ながら {name: 'item1', size: 'L'}
のドキュメントは存在しないので作成される。
そして引数で指定した {memo: 'updated'}
は追加されない。
(これも当然のことで、 upsert の引数はバリデーションオプションを設定する箇所であり、今回の指定はスルーされる)
touch
updated_at
を更新する。
引数を指定すると、その項目にも現在時刻を指定する。
今回の検証とは用途が異なるので割愛。
(ちなみに item.touch(memo: 'updated')
とかやると "{:memo => 'updated'}" : ISODate({現在時刻})
などというフィールドが追加されてしまい破滅する)
inc
指定したフィールドをインクリメントする。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee30896694f6bc7bc3c52f, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.inc(generation: 1)
=> #<Item _id: 5dee30896694f6bc7bc3c52f, name: "item1", memo: "initialized", size: "L">
Mongoid 上は変化がないが、実際のドキュメントは変更される。
> db.items.find()
{ "_id" : ObjectId("5dee30896694f6bc7bc3c52f"), "name" : "item1", "memo" : "initialized", "size" : "M", "generation" : 1 }
size
は変更されず、 generation
が追加されている。
rename
指定したフィールドの名前を変更する。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee32226694f6bc7bc3c530, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.rename(memo: :note)
=> #<Item _id: 5dee32226694f6bc7bc3c530, name: "item1", memo: nil, size: "L">
irb(main):004:0> Item.find_by(name: 'item1')
=> #<Item _id: 5dee32226694f6bc7bc3c530, name: "item1", memo: nil, size: "M">
size
は更新されず memo
の値が null になっている。
実際には memo
フィールドはなく note
フィールドに変更されており、 Mongoid ではモデルに定義されている memo
があるものとして扱っているため {memo: null}
に見える。
> db.items.find()
{ "_id" : ObjectId("5dee32226694f6bc7bc3c530"), "name" : "item1", "size" : "M", "note" : "initialized" }
set
指定したフィールドの値を更新する。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee32fe6694f6bc7bc3c532, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.set(memo: 'updated')
=> #<Item _id: 5dee32fe6694f6bc7bc3c532, name: "item1", memo: "updated", size: "L">
irb(main):004:0> Item.find_by(name: 'item1')
=> #<Item _id: 5dee32fe6694f6bc7bc3c532, name: "item1", memo: "updated", size: "M">
size
は更新されず、 memo
だけが更新される。
> db.items.find()
{ "_id" : ObjectId("5dee32fe6694f6bc7bc3c532"), "name" : "item1", "memo" : "updated", "size" : "M" }
unset
指定したフィールドを削除する。
irb(main):001:0> item = Item.find_by(name: 'item1')
=> #<Item _id: 5dee33956694f6bc7bc3c534, name: "item1", memo: "initialized", size: "M">
irb(main):002:0> item.size = 'L'
=> "L"
irb(main):003:0> item.unset(:memo)
=> #<Item _id: 5dee33956694f6bc7bc3c534, name: "item1", memo: nil, size: "L">
irb(main):004:0> Item.find_by(name: 'item1')
=> #<Item _id: 5dee33956694f6bc7bc3c534, name: "item1", memo: nil, size: "M">
size
の値は変更されず、 memo
の値が null になっている様に見える。
これも実際には {memo: null}
ではなくフィールドごとなくなっている。
> db.items.find()
{ "_id" : ObjectId("5dee33956694f6bc7bc3c534"), "name" : "item1", "size" : "M" }
まとめ
メソッド | 変数の値の変更 | 引数での値の変更 |
---|---|---|
update_attribute | 反映される | 反映される |
update_attributes / update | 反映される | 反映される |
upsert | 別ドキュメントに反映される | 反映されない(オプションとして認識される) |
touch | 反映されない | 新規のフィールドという形で反映される |
inc | 反映されない | 反映される |
rename | 反映されない | 反映される |
set | 反映されない | 反映される |
unset | 反映されない | 反映される |