紳士なブログ

紳士すぎてすみません

ActiveRecord::AttributeMethods

明けましておめでとうございます。

本年もよろしくお願いいたします。


新年早々、ActiveRecordのコードを読んでいて書き方が勉強になったので、

今後の参考にメモしておきます。

ソースコードこちら


ちなみに、もともとはinstance_method_already_implemented?メソッドを見たかっただけで、

これはActiveRecordで動的メソッドを定義する前に、同じメソッドが存在しないかどうかを確認するために利用されています。

動的メソッドアクセサを生成する前にこのメソッドが呼び出されている様子。

rails / activerecord / lib / active_record / attribute_methods.rb
require 'active_support/core_ext/enumerable'
require 'active_support/deprecation'

module ActiveRecord
  # = Active Record Attribute Methods
  module AttributeMethods #:nodoc:
    extend ActiveSupport::Concern
    include ActiveModel::AttributeMethods

    included do
      include Read
      include Write
      include BeforeTypeCast
      include Query
      include PrimaryKey
      include TimeZoneConversion
      include Dirty
      include Serialization
      include DeprecatedUnderscoreRead

      # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
      # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
      # (Alias for the protected read_attribute method).
      def [](attr_name)
        read_attribute(attr_name)
      end

      # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
      # (Alias for the protected write_attribute method).
      def []=(attr_name, value)
        write_attribute(attr_name, value)
      end
    end

    module ClassMethods
      # Generates all the attribute related methods for columns in the database
      # accessors, mutators and query methods.
      def define_attribute_methods
        unless defined?(@attribute_methods_mutex)
          msg = "It looks like something (probably a gem/plugin) is overriding the " \
                "ActiveRecord::Base.inherited method. It is important that this hook executes so " \
                "that your models are set up correctly. A workaround has been added to stop this " \
                "causing an error in 3.2, but future versions will simply not work if the hook is " \
                "overridden. If you are using Kaminari, please upgrade as it is known to have had " \
                "this problem.\n\n"
          msg << "The following may help track down the problem:"

          meth = method(:inherited)
          if meth.respond_to?(:source_location)
            msg << " #{meth.source_location.inspect}"
          else
            msg << " #{meth.inspect}"
          end
          msg << "\n\n"

          ActiveSupport::Deprecation.warn(msg)

          @attribute_methods_mutex = Mutex.new
        end

        # Use a mutex; we don't want two thread simaltaneously trying to define
        # attribute methods.
        @attribute_methods_mutex.synchronize do
          return if attribute_methods_generated?
          superclass.define_attribute_methods unless self == base_class
          super(column_names)
          column_names.each { |name| define_external_attribute_method(name) }
          @attribute_methods_generated = true
        end
      end

      def attribute_methods_generated?
        @attribute_methods_generated ||= false
      end

      # We will define the methods as instance methods, but will call them as singleton
      # methods. This allows us to use method_defined? to check if the method exists,
      # which is fast and won't give any false positives from the ancestors (because
      # there are no ancestors).
      def generated_external_attribute_methods
        @generated_external_attribute_methods ||= Module.new { extend self }
      end

      def undefine_attribute_methods
        super
        @attribute_methods_generated = false
      end

      def instance_method_already_implemented?(method_name)
        if dangerous_attribute_method?(method_name)
          raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
        end

        if superclass == Base
          super
        else
          # If B < A and A defines its own attribute method, then we don't want to overwrite that.
          defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
          defined && !ActiveRecord::Base.method_defined?(method_name) || super
        end
      end

      # A method name is 'dangerous' if it is already defined by Active Record, but
      # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
      def dangerous_attribute_method?(name)
        method_defined_within?(name, Base)
      end

      def method_defined_within?(name, klass, sup = klass.superclass)
        if klass.method_defined?(name) || klass.private_method_defined?(name)
          if sup.method_defined?(name) || sup.private_method_defined?(name)
            klass.instance_method(name).owner != sup.instance_method(name).owner
          else
            true
          end
        else
          false
        end
      end
...