論理削除指定のモデルをhas_manyで参照し、かつnested_attributes_forにallow_destroyを指定するも物理削除になる。
環境はこちら
activerecord(4.1.6)
rails4 acts as _paranoid (0.1.4)
has manyのリレーション先でacts as paranoid指定しているのですが、
nested attributes for リレーション先, allow destroy : true
で何故か物理削除になっていまったので調べてみました。
結論としては
lib/active record/associations/has many association.rb
の112行目で
records.each(&:destroy!)
とdestroy!で消していて、acts as _paranoidのdestroy!は物理削除になるためでした。
何故destroyに!がついているのか
上記が定義されているdelete recordsメソッドを遡ってみます。
delete recordsメソッドはlib/active record/associations/collection association.rb
のremove _recordsの中で呼ばれています(ソースでは492行目)
1
2
3
4
5
6
7
8
9
| # lib/active_record/associations/collection_association.rb
def remove_records(existing_records, records, method)
records.each { |record| callback(:before_remove, record) }
delete_records(existing_records, method) if existing_records.any?
records.each { |record| target.delete(record) }
records.each { |record| callback(:after_remove, record) }
end
|
んでこれはどこで呼ばれるかというとここで呼ばれています(ソースでは485行目)
1
2
3
4
5
6
7
8
9
10
11
12
| # lib/active_record/associations/collection_association.rb
def delete_or_destroy(records, method)
records = records.flatten
records.each { |record| raise_on_type_mismatch!(record) }
existing_records = records.reject { |r| r.new_record? }
if existing_records.empty?
remove_records(existing_records, records, method)
else
transaction { remove_records(existing_records, records, method) }
end
end
|
transactionで囲まれています。
ということでdestroyに失敗した時にロールバックするために!をつけているようです。
なのでparanoidのdestroy!をalias method chainで物理削除にしてしまうか。
上記のrecords.each(&:destroy!)をなんとか書き換えてあげると治りそうですね。
今回はこうやって直しました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| module ActiveRecord
module Associations
module HasManyAssociationPatch
def delete_records(records, method)
if method == :destroy
records.each do |record|
raise ActiveRecord::ActiveRecordError unless record.destroy
end
update_counter(-records.length) unless inverse_updates_counter_cache?
else
super
end
end
end
end
end
ActiveRecord::Associations::HasManyAssociation.send(:prepend, ActiveRecord::Associations::HasManyAssociationPatch)
|
今回ソース読んでいて知ったんですけど、has manyって削除する時に
before remove
after _remove
ってコールバックが発生するんですね。知らなかった。