update_attribute
で一つの属性値を変更しようとしたら、他の値も更新されてしまった
以下のような操作を行うと、 update_attribute
は明示的に更新しようとしてない物も更新する。
# attr_1 と attr_2 という属性を持つ以下のような SomeModel がある時、
some_model = SomeModel.find(42)
some_model.attr_1 # => nil
some_model.attr_2 # => nil
# attr_1 を dirty にする
some_modal.attr_1 = 'this is ditry column'
# とりあえず attr_2 だけ保存しようかな…
some_model.update_attribute(:attr_2, 'this is updated')
some_model.attr_2_in_database # => 'this is updated'
# !?
some_model.attr_1_in_database # => 'this is ditry column'
Dirty な値が保存されるのは update_attribute
の仕様です
ActiveRecord::Persisitence#update_attributeより引用(強調は引用者による):
Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records. Also note that
- Validation is skipped.
- Callbacks are invoked.
- updated_at/updated_on column is updated if that column is available.
- Updates all the attributes that are dirty in this object.
翻訳(適当):
単一の属性値を更新し、レコードに保存します。このメソッドは特にすでに存在しているレコードのbooleanのフラグ(の更新)にとって便利です。注意点は次の通り
- バリデーションはスキップされます。
- コールバックは実行されます。
- update_at/update_on カラムがあれば更新されます。
- そのオブジェクトでdirtyな属性値は全て更新されます。
実装を見てみる
なんか変な挙動だな…とおもったので実装を見てみる。
activerecord/lib/active_record/persistence.rb より引用
module ActiveRecord
module Persistence
# ...(中略)
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
save(validate: false)
end
# ...(後略)
end
end
値をセットして save
してるだけだった。 dirty な値が全部保存されてしまうのも納得の挙動だ。update_attribute
は「単一の属性値をセットしたあとに save
する」というショートハンドであって、「単一の属性値を保存する」メソッドではないと認識しておこう
付録(update_attributeの歴史)
メソッド名から想像される動作と実際の動作の違いが Rails のコードを全部読んだ人向けっぽいと思った。ライブラリとして小さかった昔からの名残ではないかと思い起源を調べてみた。するとやはり GitHub の rails/rails の最初のコミット(2004-12-24) から存在しており、動作もほぼ今と同じだった。
https://github.com/rails/rails/blob/db045dbbf6/activerecord/lib/active_record/base.rb#L709-L713 より引用(略は引用者による)
module ActiveRecord
class Base
public
# ...(中略)
# Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
def update_attribute(name, value)
self[name] = value
save
end
# ...(後略)
end
end
今のドキュメントと見比べると、メソッドのコメントは今と全く同じで、注意書きが追加されている。同じような勘違いをする人が多いので注意書きが追加されてったのだろうか。