aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_methods/read.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods/read.rb')
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb155
1 files changed, 73 insertions, 82 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 8566ecad14..964c4123ef 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -6,11 +6,8 @@ module ActiveRecord
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
included do
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
+ config_attribute :attribute_types_cached_by_default, :global => true
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
- # Undefine id so it can be used as an attribute name
- undef_method(:id) if method_defined?(:id)
end
module ClassMethods
@@ -32,107 +29,101 @@ module ActiveRecord
cached_attributes.include?(attr_name)
end
+ def undefine_attribute_methods
+ generated_external_attribute_methods.module_eval do
+ instance_methods.each { |m| undef_method(m) }
+ end
+
+ super
+ end
+
+ def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
+ return unless attr_name
+ attr_name = attr_name.to_s
+
+ if generated_external_attribute_methods.method_defined?(attr_name)
+ if attributes.has_key?(attr_name) || attr_name == 'id'
+ generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
+ end
+ elsif !attribute_methods_generated?
+ # If we haven't generated the caster methods yet, do that and
+ # then try again
+ define_attribute_methods
+ type_cast_attribute(attr_name, attributes, cache)
+ else
+ # If we get here, the attribute has no associated DB column, so
+ # just return it verbatim.
+ attributes[attr_name]
+ end
+ end
+
protected
+ # We want to generate the methods via module_eval rather than define_method,
+ # because define_method is slower on dispatch and uses more memory (because it
+ # creates a closure).
+ #
+ # 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.
def define_method_attribute(attr_name)
- if serialized_attributes.include?(attr_name)
- define_read_method_for_serialized_attribute(attr_name)
- else
- define_read_method(attr_name, attr_name, columns_hash[attr_name])
- end
+ cast_code = attribute_cast_code(attr_name)
- if primary_key? && attr_name == primary_key && attr_name != "id"
- define_read_method('id', attr_name, columns_hash[attr_name])
- end
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__
+ #{internal_attribute_access_code(attr_name, cast_code)}
+ end
+ alias_method '#{attr_name}', :__temp__
+ undef_method :__temp__
+ STR
+
+ generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__(v, attributes, attributes_cache, attr_name)
+ #{external_attribute_access_code(attr_name, cast_code)}
+ end
+ alias_method '#{attr_name}', :__temp__
+ undef_method :__temp__
+ STR
end
private
def cacheable_column?(column)
- serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type)
- end
-
- # Define read method for serialized attribute.
- def define_read_method_for_serialized_attribute(attr_name)
- access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']"
- generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
+ 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 internal_attribute_access_code(attr_name, cast_code)
+ access_code = "(v=@attributes[attr_name]) && #{cast_code}"
- unless primary_key? && attr_name.to_s == primary_key.to_s
- access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
+ unless attr_name == primary_key
+ access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
end
if cache_attribute?(attr_name)
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
+ 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::COMPILABLE_REGEXP
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
- def _#{method_name}
- #{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
+ "attr_name = '#{attr_name}'; #{access_code}"
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)
- else
- _read_attribute attr_name
- end
- end
+ def external_attribute_access_code(attr_name, cast_code)
+ access_code = "v && #{cast_code}"
- def _read_attribute(attr_name)
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key? && self.class.primary_key if attr_name == 'id'
- value = @attributes[attr_name]
- unless value.nil?
- if column = column_for_attribute(attr_name)
- if unserializable_attribute?(attr_name, column)
- unserialize_attribute(attr_name)
- else
- column.type_cast(value)
+ if cache_attribute?(attr_name)
+ access_code = "attributes_cache[attr_name] ||= (#{access_code})"
end
- else
- value
+
+ access_code
end
- end
- end
- # Returns true if the attribute is of a text column and marked for serialization.
- def unserializable_attribute?(attr_name, column)
- column.text? && self.class.serialized_attributes.include?(attr_name)
+ def attribute_cast_code(attr_name)
+ columns_hash[attr_name].type_cast_code('v')
+ end
end
- # Returns the unserialized object of the attribute.
- def unserialize_attribute(attr_name)
- coder = self.class.serialized_attributes[attr_name]
- unserialized_object = coder.load(@attributes[attr_name])
-
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
+ # 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)
+ self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
end
private