aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb84
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb45
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