总结:使用Thread.current。(相当于ThreadLocal,任何“非传参、非实例变量“的共享变量)

active_record-updated_at一经require,便会 :

simply patches ActiveRecord::Relation#update_all to automatically specify updated_at as Time.current when:

1.An updated_at column exists on the table
2.An updated_at value is not explicitly specified in the query

但也可通过disable来取消为某次update恢复“无updated_at”

ActiveRecord::UpdatedAt.disable do
  User.update_all(role: “member”)
  User.find(123).update_column(name: "Sean")
end


该block并没有接受任何AR作为参数,block中的User如何知道自己是否被disable了?

答案:用Thread.current来交互。源码很短,如下:

disable会设置Thread.current["UpdatedAt::DISABLED"]为true,然后才执行block,最后确保恢复原state

require "active_record"
require_relative "active_record/updated_at/relation"

module ActiveRecord
  module UpdatedAt
    ActiveRecord::Relation.send(:include, Relation)

    STATE = "#{name}::DISABLED".freeze

    class << self
      def disable(state = true)
        disabled_was = Thread.current[STATE]
        Thread.current[STATE] = state
        yield
      ensure
        Thread.current[STATE] = disabled_was
      end

      def enable(&block)
        disable(false, &block)
      end

      def enabled?
        !Thread.current[STATE]
      end
    end
  end
end


UpdatedAt.enabled?是默认为true的。

包装原update_all,若确为true,且有updated_at栏位,且没填值,则拼接上updated_at = Time.current,再去update_all

module ActiveRecord
  module UpdatedAt
    module Relation
      def self.included(base)
        base.class_eval do
          alias_method :update_all_without_updated_at, :update_all
          alias_method :update_all, :update_all_with_updated_at
        end
      end

      def update_all_with_updated_at(query, *args, &block)
        attribute_exists = column_names.include?("updated_at")
        already_specified = Array(query).flatten.grep(/\bupdated_at\b/).any?
        enabled = UpdatedAt.enabled?
        updated_at = Time.current

        if attribute_exists && !already_specified && enabled
          case query
          when Array
            query.first << ", updated_at = ?"
            query << updated_at
          when Hash
            query[:updated_at] = updated_at
          when String
            query = ["#{query}, updated_at = ?", updated_at]
          end
        end

        update_all_without_updated_at(query, *args, &block)
      end
    end
  end
end