Help us understand the problem. What is going on with this article?

Mongoid の更新メソッドの挙動を整理する

意図しないものが更新されるリスクを避けるため、 Mongoid が提供する更新メソッドの挙動を整理しておく。

tl;dr

  • 引数で指定した値 以外 に変更が及ぶのは update_attribute, update_attributes, update
    • たぶんこれが最も正しく認識しておくべきこと
    • このあたりは ActiveRecord と同じ(だと思う)
  • upserttouch はそもそもの用途が異なる
  • 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">

memosize も更新される。

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">

当然 memosize も更新される。

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 反映されない 反映される
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした