ActiveModelはActiveRecordっぽいオブジェクトを作るために使えるという程度の認識だったので、少し理解を深めたくて調査しました。この記事では、ActiveModel::Dirtyに焦点を当てます。
代表的な使い方
class Person
include ActiveModel::Dirty
define_attribute_methods :name
def initialize
@name = nil
end
def name
@name
end
def name=(val)
name_will_change! unless val == @name
@name = val
end
def save
# do persistence work
changes_applied
end
def reload!
# get the values from the persistence layer
clear_changes_information
end
def rollback!
restore_attributes
end
end
重要なのは、save
・reload!
・rollback!
の三兄弟です。これらのメソッドでは、それぞれchanges_applied
、clear_changes_information
、restore_attributes
が使われています。これらのメソッドが、すなわちsave、reload、rollbackに対応している訳です。
実際に何が行われているのか確認するために、change_applied
メソッドを読んでみましょう。
changes_appliedメソッド
# Clears dirty data and moves +changes+ to +previous_changes+ and
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
def changes_applied
unless defined?(@attributes)
mutations_from_database.finalize_changes
end
@mutations_before_last_save = mutations_from_database
forget_attribute_assignments
@mutations_from_database = nil
end
解説するまでもなく、コメントに処理内容が書いてありますね(笑)。
ActiveModelのインスタンスには、database(上の変更)をエミュレートするためのオブジェクトが二つ用意されています。一つ前の変更内容(mutation_before_last_save
)と現在の変更内容(mutations_from_database
)ですね。
changes_applied
では、現在の変更内容を一つ前の変更内容へとシフトさせています。このように、データベースの変更をエミュレートすることで、あたかも背後にデータベースがあるかのような振る舞いが可能になり、ActiveRecordの代わりとして使えるようになっています。
使ってみよう
- 値の変更チェック
p = Person.new
p.name = "Tanaka"
p.save
pp p.name_changed? # => false
p.name = "Suzuki"
pp p.name_changed? # => true
- 値の変更履歴
p = Person.new
p.name = "Tanaka"
p.save
p.name = "Suzuki"
p.save
p.name = "Kondo"
pp p.name # => Kondo
pp p.name_was # => Suzuki
pp p.name_previously_was # => Tanaka