diff options
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/read.rb | 84 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/write.rb | 45 |
2 files changed, 84 insertions, 45 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 684fe2dc05..d43f4b55e8 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,6 +1,54 @@ module ActiveRecord module AttributeMethods module Read + ReaderMethodCache = Class.new { + include Mutex_m + + def initialize + super + @module = Module.new + @method_cache = {} + end + + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes_cache in read_attribute. + def [](name) + synchronize do + @method_cache.fetch(name) { + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" + + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + @module.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + read_attribute(name) { |n| missing_attribute(n, caller) } + end + STR + + @method_cache[name] = @module.instance_method temp_method + } + end + end + }.new + extend ActiveSupport::Concern ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] @@ -32,41 +80,9 @@ module ActiveRecord protected - # We want to generate the methods via module_eval rather than - # define_method, because define_method is slower on dispatch. - # Evaluating many similar methods may use more memory as the instruction - # sequences are duplicated and cached (in MRI). define_method may - # be slower on dispatch, but if you're careful about the closure - # created, then define_method will consume much less memory. - # - # But sometimes the database might return columns with - # characters that are not allowed in normal method names (like - # 'my_column(omg)'. So to work around this we first define with - # the __temp__ identifier, and then use alias method to rename - # it to what we want. - # - # We are also defining a constant to hold the frozen string of - # the attribute name. Using a constant means that we do not have - # to allocate an object on each call to the attribute method. - # Making it frozen means that it doesn't get duped when used to - # key the @attributes_cache in read_attribute. def define_method_attribute(name) - safe_name = name.unpack('h*').first - temp_method = "__temp__#{safe_name}" - - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def #{temp_method} - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - read_attribute(name) { |n| missing_attribute(n, caller) } - end - STR - - generated_attribute_methods.module_eval do - alias_method name, temp_method - undef_method temp_method - end + method = ReaderMethodCache[name] + generated_attribute_methods.module_eval { define_method name, method } end private diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 9c7f643283..813a85d2f7 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -1,6 +1,36 @@ module ActiveRecord module AttributeMethods module Write + WriterMethodCache = Class.new { + include Mutex_m + + def initialize + super + @module = Module.new + @method_cache = {} + end + + def [](name) + synchronize do + @method_cache.fetch(name) { + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}=" + + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + + @module.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method}(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + STR + + @method_cache[name] = @module.instance_method temp_method + } + end + end + }.new + extend ActiveSupport::Concern included do @@ -13,17 +43,10 @@ module ActiveRecord # See define_method_attribute in read.rb for an explanation of # this code. def define_method_attribute=(name) - safe_name = name.unpack('h*').first - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR + method = WriterMethodCache[name] + generated_attribute_methods.module_eval { + define_method "#{name}=", method + } end end |