diff options
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods/read.rb')
-rw-r--r-- | activerecord/lib/active_record/attribute_methods/read.rb | 155 |
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 |