aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
authorJon Leighton <j@jonathanleighton.com>2011-12-01 15:16:41 +0000
committerJon Leighton <j@jonathanleighton.com>2011-12-01 23:41:51 +0000
commit3dcb127109428c1948a9f3bcad7101bd8a7f4d8a (patch)
tree8dc09dc6e13f497dd8b4b01728a3af42f26bffa8 /activerecord/lib/active_record
parent365e10b8dcbefe36cdbe98e077d6432ad76c6a08 (diff)
downloadrails-3dcb127109428c1948a9f3bcad7101bd8a7f4d8a.tar.gz
rails-3dcb127109428c1948a9f3bcad7101bd8a7f4d8a.tar.bz2
rails-3dcb127109428c1948a9f3bcad7101bd8a7f4d8a.zip
Don't rely on underscore-prefixed attribute methods.
Define singleton methods on the attributes module instead. This reduces method pollution on the actual model classes. It also seems to make something faster, I am unsure why! O_o
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb91
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb11
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb26
3 files changed, 80 insertions, 48 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 4fdfe77bab..59e57135f9 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -29,9 +29,54 @@ module ActiveRecord
cached_attributes.include?(attr_name)
end
+ def undefine_attribute_methods
+ if base_class == self
+ generated_attribute_methods.module_eval do
+ public_methods(false).each do |m|
+ singleton_class.send(:undef_method, m) if m.to_s =~ /^cast_/
+ end
+ end
+ end
+
+ super
+ end
+
protected
+ # Where possible, generate the method by evalling a string, as this will result in
+ # faster accesses because it avoids the block eval and then string eval incurred
+ # by the second branch.
+ #
+ # The second, slower, branch is necessary to support instances where the database
+ # returns columns with extra stuff in (like 'my_column(omg)').
def define_method_attribute(attr_name)
- define_read_method(attr_name, attr_name, columns_hash[attr_name])
+ access_code = attribute_access_code(attr_name)
+ cast_code = "v && (#{attribute_cast_code(attr_name)})"
+
+ if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
+ def #{attr_name}
+ #{access_code}
+ end
+
+ def self.cast_#{attr_name}(v)
+ #{cast_code}
+ end
+
+ alias _#{attr_name} #{attr_name}
+ STR
+ else
+ generated_attribute_methods.module_eval do
+ define_method(attr_name) do
+ eval(access_code)
+ end
+
+ singleton_class.send(:define_method, "cast_#{attr_name}") do |v|
+ eval(cast_code)
+ end
+
+ alias_method("_#{attr_name}", attr_name)
+ end
+ end
end
private
@@ -39,14 +84,10 @@ module ActiveRecord
attribute_types_cached_by_default.include?(column.type)
end
- # Define an attribute reader method. Cope with nil column.
- # method_name is the same as attr_name except when a non-standard primary key is used,
- # we still define #id as an accessor for the key
- def define_read_method(method_name, attr_name, column)
- cast_code = column.type_cast_code('v')
- access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
+ def attribute_access_code(attr_name)
+ access_code = "(v=@attributes['#{attr_name}']) && #{attribute_cast_code(attr_name)}"
- unless attr_name.to_s == self.primary_key.to_s
+ unless attr_name == self.primary_key
access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
end
@@ -54,35 +95,25 @@ module ActiveRecord
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
end
- # Where possible, generate the method by evalling a string, as this will result in
- # faster accesses because it avoids the block eval and then string eval incurred
- # by the second branch.
- #
- # The second, slower, branch is necessary to support instances where the database
- # returns columns with extra stuff in (like 'my_column(omg)').
- if method_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
- def _#{method_name}
- #{access_code}
- end
+ access_code
+ end
- alias #{method_name} _#{method_name}
- STR
- else
- generated_attribute_methods.module_eval do
- define_method("_#{method_name}") { eval(access_code) }
- alias_method(method_name, "_#{method_name}")
- end
- end
+ def attribute_cast_code(attr_name)
+ columns_hash[attr_name].type_cast_code('v')
end
end
# 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)).
def read_attribute(attr_name)
- method = "_#{attr_name}"
- if respond_to? method
- send method if @attributes.has_key?(attr_name.to_s)
+ attr_name = attr_name.to_s
+ caster = "cast_#{attr_name}"
+ methods = self.class.generated_attribute_methods
+
+ if methods.respond_to?(caster)
+ if @attributes.has_key?(attr_name)
+ @attributes_cache[attr_name] || methods.send(caster, @attributes[attr_name])
+ end
else
_read_attribute attr_name
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index d1eb3d5bfc..e1eb2d6acf 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -58,14 +58,11 @@ module ActiveRecord
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
end
- def define_method_attribute(attr_name)
+ private
+
+ def attribute_cast_code(attr_name)
if serialized_attributes.include?(attr_name)
- generated_attribute_methods.module_eval(<<-CODE, __FILE__, __LINE__)
- def _#{attr_name}
- @attributes['#{attr_name}'].unserialized_value
- end
- alias #{attr_name} _#{attr_name}
- CODE
+ "v.unserialized_value"
else
super
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 62a3cfa9a5..7cde9dbffd 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -19,18 +19,22 @@ module ActiveRecord
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
# This enhanced read method automatically converts the UTC time stored in the database to the time
# zone stored in Time.zone.
- def define_method_attribute(attr_name)
+ def attribute_access_code(attr_name)
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
- method_body, line = <<-EOV, __LINE__ + 1
- def _#{attr_name}
- cached = @attributes_cache['#{attr_name}']
- return cached if cached
- time = _read_attribute('#{attr_name}')
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
- end
- alias #{attr_name} _#{attr_name}
- EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
+ <<-CODE
+ cached = @attributes_cache['#{attr_name}']
+ return cached if cached
+ time = _read_attribute('#{attr_name}')
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
+ CODE
+ else
+ super
+ end
+ end
+
+ def attribute_cast_code(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ "v.acts_like?(:time) ? v.in_time_zone : v"
else
super
end