Skip to content

Commit

Permalink
keep a cache of the reader methods so we can reuse them
Browse files Browse the repository at this point in the history
  • Loading branch information
tenderlove committed Jul 3, 2013
1 parent 501b27e commit a2b847a
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 34 deletions.
48 changes: 48 additions & 0 deletions activerecord/lib/active_record/attribute_methods.rb
Expand Up @@ -28,6 +28,54 @@ def self.set_name_cache(name, value)
end
}

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

module ClassMethods
def inherited(child_class) #:nodoc:
child_class.initialize_generated_modules
Expand Down
36 changes: 2 additions & 34 deletions activerecord/lib/active_record/attribute_methods/read.rb
Expand Up @@ -32,41 +32,9 @@ def cache_attribute?(attr_name)

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 = ActiveRecord::AttributeMethods::ReaderMethodCache[name]
generated_attribute_methods.module_eval { define_method name, method }
end

private
Expand Down

0 comments on commit a2b847a

Please sign in to comment.