Active Record 源碼閱讀

根據(jù)ActiveRecord::Base里的繼承鏈向上閱讀

module ActiveRecord #:nodoc:
  class Base
    extend ActiveModel::Naming

    extend ActiveSupport::Benchmarkable
    extend ActiveSupport::DescendantsTracker

    extend ConnectionHandling
    extend QueryCache::ClassMethods
    extend Querying
    extend Translation
    extend DynamicMatchers
    extend Explain
    extend Enum
    extend Delegation::DelegateCache
    extend Aggregations::ClassMethods

    include Core
    include Persistence
    include ReadonlyAttributes
    include ModelSchema
    include Inheritance
    include Scoping
    include Sanitization
    include AttributeAssignment
    include ActiveModel::Conversion
    include Integration
    include Validations
    include CounterCache
    include Attributes
    include AttributeDecorators
    include Locking::Optimistic
    include Locking::Pessimistic
    include DefineCallbacks
    include AttributeMethods
    include Callbacks
    include Timestamp
    include Associations
    include ActiveModel::SecurePassword
    include AutosaveAssociation
    include NestedAttributes
    include Transactions
    include TouchLater
    include NoTouching
    include Reflection
    include Serialization
    include Store
    include SecureToken
    include Suppressor
  end

  ActiveSupport.run_load_hooks(:active_record, Base)
end
ActiveRecord::Suppressor

先是Suppressor,先看功能部分:

order = Order.last
Order.suppress do
  order.update(phone_number: "1xxxxxxxxxx")
end
order #=> phone_number 并沒有被保存到數(shù)據(jù)庫

被suppress(抑制)包裹的持久化語句將不會執(zhí)行save操作煮寡。

module Suppressor
    extend ActiveSupport::Concern

    module ClassMethods
      def suppress(&block)
        previous_state = SuppressorRegistry.suppressed[name]
        SuppressorRegistry.suppressed[name] = true
        yield
      ensure
        SuppressorRegistry.suppressed[name] = previous_state
      end
    end

    def save(*) # :nodoc:
      SuppressorRegistry.suppressed[self.class.name] ? true : super
    end

    def save!(*) # :nodoc:
      SuppressorRegistry.suppressed[self.class.name] ? true : super
    end
  end

  class SuppressorRegistry # :nodoc:
    extend ActiveSupport::PerThreadRegistry

    attr_reader :suppressed

    def initialize
      @suppressed = {}
    end
  end
end

關(guān)于ActiveSupport::PerThreadRegistry可以看這里.

ActiveRecord::SecureToken

定義了隨機生成token的顽素,并保存到相應(yīng)字段的功能。

# frozen_string_literal: true

module ActiveRecord
  module SecureToken
    extend ActiveSupport::Concern

    module ClassMethods
      # Example using #has_secure_token
      #
      #   # Schema: User(token:string, auth_token:string)
      #   class User < ActiveRecord::Base
      #     has_secure_token
      #     has_secure_token :auth_token
      #   end
      #
      #   user = User.new
      #   user.save
      #   user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
      #   user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7"
      #   user.regenerate_token # => true
      #   user.regenerate_auth_token # => true
      #
      # <tt>SecureRandom::base58</tt> is used to generate the 24-character unique token, so collisions are highly unlikely.
      #
      # Note that it's still possible to generate a race condition in the database in the same way that
      # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
      # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
      def has_secure_token(attribute = :token)
        # Load securerandom only when has_secure_token is used.
        require "active_support/core_ext/securerandom"
        define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
        before_create { send("#{attribute}=", self.class.generate_unique_secure_token) unless send("#{attribute}?") }
      end

      def generate_unique_secure_token
        SecureRandom.base58(24)
      end
    end
  end
end
ActiveRecord::Store

實現(xiàn)對一個字段的序列化舰讹。可參考

require "active_support/core_ext/hash/indifferent_access"

module ActiveRecord
  module Store
    extend ActiveSupport::Concern

    included do
      class << self
        attr_accessor :local_stored_attributes
      end
    end

    module ClassMethods
      def store(store_attribute, options = {})
        serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
        store_accessor(store_attribute, options[:accessors], options.slice(:prefix, :suffix)) if options.has_key? :accessors
      end

      def store_accessor(store_attribute, *keys, prefix: nil, suffix: nil)
        keys = keys.flatten

        accessor_prefix =
          case prefix
          when String, Symbol
            "#{prefix}_"
          when TrueClass
            "#{store_attribute}_"
          else
            ""
          end
        accessor_suffix =
          case suffix
          when String, Symbol
            "_#{suffix}"
          when TrueClass
            "_#{store_attribute}"
          else
            ""
          end

        _store_accessors_module.module_eval do
          keys.each do |key|
            accessor_key = "#{accessor_prefix}#{key}#{accessor_suffix}"

            define_method("#{accessor_key}=") do |value|
              write_store_attribute(store_attribute, key, value)
            end

            define_method(accessor_key) do
              read_store_attribute(store_attribute, key)
            end

            define_method("#{accessor_key}_changed?") do
              return false unless attribute_changed?(store_attribute)
              prev_store, new_store = changes[store_attribute]
              prev_store&.dig(key) != new_store&.dig(key)
            end

            define_method("#{accessor_key}_change") do
              return unless attribute_changed?(store_attribute)
              prev_store, new_store = changes[store_attribute]
              [prev_store&.dig(key), new_store&.dig(key)]
            end

            define_method("#{accessor_key}_was") do
              return unless attribute_changed?(store_attribute)
              prev_store, _new_store = changes[store_attribute]
              prev_store&.dig(key)
            end

            define_method("saved_change_to_#{accessor_key}?") do
              return false unless saved_change_to_attribute?(store_attribute)
              prev_store, new_store = saved_change_to_attribute(store_attribute)
              prev_store&.dig(key) != new_store&.dig(key)
            end

            define_method("saved_change_to_#{accessor_key}") do
              return unless saved_change_to_attribute?(store_attribute)
              prev_store, new_store = saved_change_to_attribute(store_attribute)
              [prev_store&.dig(key), new_store&.dig(key)]
            end

            define_method("#{accessor_key}_before_last_save") do
              return unless saved_change_to_attribute?(store_attribute)
              prev_store, _new_store = saved_change_to_attribute(store_attribute)
              prev_store&.dig(key)
            end
          end
        end

        # assign new store attribute and create new hash to ensure that each class in the hierarchy
        # has its own hash of stored attributes.
        self.local_stored_attributes ||= {}
        self.local_stored_attributes[store_attribute] ||= []
        self.local_stored_attributes[store_attribute] |= keys
      end

      def _store_accessors_module # :nodoc:
        @_store_accessors_module ||= begin
          mod = Module.new
          include mod
          mod
        end
      end

      def stored_attributes
        parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
        if local_stored_attributes
          parent.merge!(local_stored_attributes) { |k, a, b| a | b }
        end
        parent
      end
    end

    private
      def read_store_attribute(store_attribute, key) # :doc:
        accessor = store_accessor_for(store_attribute)
        accessor.read(self, store_attribute, key)
      end

      def write_store_attribute(store_attribute, key, value) # :doc:
        accessor = store_accessor_for(store_attribute)
        accessor.write(self, store_attribute, key, value)
      end

      def store_accessor_for(store_attribute)
        type_for_attribute(store_attribute).accessor
      end

      class HashAccessor # :nodoc:
        def self.read(object, attribute, key)
          prepare(object, attribute)
          object.public_send(attribute)[key]
        end

        def self.write(object, attribute, key, value)
          prepare(object, attribute)
          if value != read(object, attribute, key)
            object.public_send :"#{attribute}_will_change!"
            object.public_send(attribute)[key] = value
          end
        end

        def self.prepare(object, attribute)
          object.public_send :"#{attribute}=", {} unless object.send(attribute)
        end
      end

      class StringKeyedHashAccessor < HashAccessor # :nodoc:
        def self.read(object, attribute, key)
          super object, attribute, key.to_s
        end

        def self.write(object, attribute, key, value)
          super object, attribute, key.to_s, value
        end
      end

      class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
        def self.prepare(object, store_attribute)
          attribute = object.send(store_attribute)
          unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
            attribute = IndifferentCoder.as_indifferent_hash(attribute)
            object.send :"#{store_attribute}=", attribute
          end
          attribute
        end
      end

      class IndifferentCoder # :nodoc:
        def initialize(attr_name, coder_or_class_name)
          @coder =
            if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
              coder_or_class_name
            else
              ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
            end
        end

        def dump(obj)
          @coder.dump self.class.as_indifferent_hash(obj)
        end

        def load(yaml)
          self.class.as_indifferent_hash(@coder.load(yaml || ""))
        end

        def self.as_indifferent_hash(obj)
          case obj
          when ActiveSupport::HashWithIndifferentAccess
            obj
          when Hash
            obj.with_indifferent_access
          else
            ActiveSupport::HashWithIndifferentAccess.new
          end
        end
      end
  end
end

ActiveRecord::Reflection

用于保存Model的associations和aggregations的配置自信息.參考

ActiveRecord::NoTouching

實現(xiàn)在一個block里暫時禁用掉touch功能以躯。參考

ActiveRecord::TouchLater

touch_later實現(xiàn)在一個事物里先執(zhí)行別的語句之后,在commit之前執(zhí)行touch的功能模塊啄踊。

ActiveRecord:Transaction

事務(wù)模塊的實現(xiàn)忧设。參考

ActiveRecord::NestedAttributes

nested_attributes的實現(xiàn)。參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颠通,一起剝皮案震驚了整個濱河市址晕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌顿锰,老刑警劉巖谨垃,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異硼控,居然都是意外死亡乘客,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門淀歇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來易核,“玉大人,你說我怎么就攤上這事浪默∧抵保” “怎么了缀匕?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碰逸。 經(jīng)常有香客問我乡小,道長,這世上最難降的妖魔是什么饵史? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任满钟,我火速辦了婚禮,結(jié)果婚禮上胳喷,老公的妹妹穿的比我還像新娘湃番。我一直安慰自己,他們只是感情好吭露,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布吠撮。 她就那樣靜靜地躺著,像睡著了一般讲竿。 火紅的嫁衣襯著肌膚如雪泥兰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天题禀,我揣著相機與錄音鞋诗,去河邊找鬼。 笑死迈嘹,一個胖子當(dāng)著我的面吹牛削彬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播江锨,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼吃警,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啄育?” 一聲冷哼從身側(cè)響起酌心,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挑豌,沒想到半個月后安券,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡氓英,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年侯勉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铝阐。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡址貌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情练对,我是刑警寧澤遍蟋,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站螟凭,受9級特大地震影響虚青,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜螺男,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一棒厘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧下隧,春花似錦奢人、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篙耗。三九已至迫筑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宗弯,已是汗流浹背脯燃。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒙保,地道東北人辕棚。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像邓厕,于是被迫代替她去往敵國和親逝嚎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容