新規・更新を条件分岐できるpersisted?のソースコードを読んでみた
環境やバージョン
$rails -v Rails 5.2.3 $ruby -v ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin18]
ソースコードリーディング
Returns true if the record is persisted, i.e. it's not a new record and it was not destroyed, otherwise returns false.
persisted?メソッドは,オブジェクトが新しいレコードでない,かつオブジェクトが削除されていない時に,Trueを返すメソッドだ.
この性質から,Viewのnewとeditにて,共通のformパーシャルを切り出した際に,分岐条件として利用される.
persisted?の実装を追うことで理解を深める.
bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/persistence.rbを確認すると,persisted?
メソッドは,以下のような実装になっている.
def persisted? sync_with_transaction_state !(@new_record || @destroyed) end
これは,ド・モルガンの法則より,!(P || Q) == !P && !Qとなる.
つまり,@new_recordでない,かつ,@destroyedでないということだ.これは,冒頭での説明文と一致する.
まずは,sync_with_transaction_stateについて調べてみる.
privateメソッドであるsync_with_transaction_stateは,transactions.rbに定義されている.
ここで,@transaction_stateは,core.rbにてnilで初期化されており,update_attributes_from_transaction_stateメソッドでの処理は,特に行われないことがわかる.
def sync_with_transaction_state update_attributes_from_transaction_state(@transaction_state) end def update_attributes_from_transaction_state(transaction_state) if transaction_state && transaction_state.finalized? restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback? force_clear_transaction_record_state if transaction_state.fully_committed? clear_transaction_record_state if transaction_state.fully_completed? end end
次に,persisted?メソッド内の,@new_recordと@destroyedのインスタンス変数は,どこで定義されているのだろうか?
user = User.new(name: "persist") user.persisted?
上記のように,ユーザーオブジェクトを生成した時,bundle/ruby/2.5.0/gems/activerecord-5.2.3/lib/active_record/core.rbのinitializeメソッドにて,init_internalsが呼び出される.init_internalsは,見てわかるように該当する2つのインスタンス変数の初期化を行なっている.
def initialize(attributes = nil) self.class.define_attribute_methods @attributes = self.class._default_attributes.deep_dup init_internals initialize_internals_callback assign_attributes(attributes) if attributes yield self if block_given? _run_initialize_callbacks end ~~中略~~ def init_internals @readonly = false @destroyed = false // 注目するべきインスタンス変数 @marked_for_destruction = false @destroyed_by_association = nil @new_record = true // 注目するべきインスタンス変数 @_start_transaction_state = {} @transaction_state = nil end
まとめ
- persisted?は,二行のシンプルな構成となっている.
- persisted?は,@new_recordと@destroyedの値を元に,booleanを返すことがわかった.
- editアクションでは,既にデータベースに保存したオブジェクトを参照するため,@new_recordではないためpersisted?は,falseになる.
感想
OSSは,怖くない.
余談だが,初期段階で,rails new
したActiveRecordとRailsドキュメントからのGitHubリンク先の実装が異なっていたので,時間を浪費した.ドキュメントのリンクは,旧versionの実装であったためだ.OSSは,頻繁にコードが変更するため最新のコードを確認することが重要であることを学んだ.
- 作者: すがわらまさのり,前島真一,近藤宇智朗,橋立友宏
- 出版社/メーカー: 技術評論社
- 発売日: 2014/06/06
- メディア: 大型本
- この商品を含むブログ (8件) を見る