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 ...