Touching Records
跟踪一下touch
[3] pry(main)> binding.trace_tree(htmp: 'ar/touch'){ ss.touch }
调用栈如下
首先是经过module NoTouching。no_touching会将接收消息AR类入栈Thread.current[:no_touching_classes] ||= [],然后block中的touch会检查被touch的AR类是否在栈中有货有父类,有则不touch。注释如下,已解释得很清楚(不过还少了入栈出栈所产生的作用域)
# ActiveRecord::Base.no_touching do
# Project.first.touch # does nothing
# Message.first.touch # does nothing
# end
#
# Project.no_touching do
# Project.first.touch # does nothing
# Message.first.touch # works, but does not touch the associated project
# end
接着,便到了callback,rails默认只提供after_touch来定义
# activerecord-5.1.2/lib/active_record/callbacks.rb
def touch(*)
_run_touch_callbacks { super }
end
最核心的操作,便是activerecord-5.1.2/lib/active_record/persistence.rb
def touch(*names, time: nil)
unless persisted?
raise ActiveRecordError, <<-MSG.squish
cannot touch on a new or destroyed record object. Consider using
persisted?, new_record?, or destroyed? before touching
MSG
end
time ||= current_time_from_proper_timezone
attributes = timestamp_attributes_for_update_in_model
attributes.concat(names)
unless attributes.empty?
changes = {}
attributes.each do |column|
column = column.to_s
changes[column] = write_attribute(column, time)
end
primary_key = self.class.primary_key
scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
if locking_enabled?
locking_column = self.class.locking_column
scope = scope.where(locking_column => _read_attribute(locking_column))
changes[locking_column] = increment_lock
end
clear_attribute_changes(changes.keys)
result = scope.update_all(changes) == 1
if !result && locking_enabled?
raise ActiveRecord::StaleObjectError.new(self, "touch")
end
@_trigger_update_callback = result
result
else
true
end
end
要点如下:
更新为当前时间或用:time选项来指定
被更新的栏位默认有timestamp_attributes_for_update_in_model,即["updated_at", "updated_on"],也可调用touch时加上其他栏位
更新操作使用的是update_all,所以此处没有调任何callback,只有after_touch和transation的after_commit/rollback
被更新的栏位不记录到dirty中
而belongs_to的:touch选项,猜测是用callback来实现,可以跟踪看看
class Spirit < ApplicationRecord
binding.trace_tree(htmp: 'ar/belongs_to_touch'){ belongs_to :human, touch: true }
end
调用栈如下
源码可追溯至
def self.add_touch_callbacks(model, reflection)
foreign_key = reflection.foreign_key
n = reflection.name
touch = reflection.options[:touch]
callback = lambda { |changes_method| lambda { |record|
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
}}
model.after_save callback.(:saved_changes), if: :saved_changes?
model.after_touch callback.(:changes_to_save)
model.after_destroy callback.(:changes_to_save)
end
即是加了:touch后,子表的save、touch、destroy都会touch父表