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.rb140
1 files changed, 68 insertions, 72 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 3c03cce838..20f0936e52 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,40 +1,16 @@
+require 'active_support/core_ext/module/method_transplanting'
+
module ActiveRecord
module AttributeMethods
module Read
- extend ActiveSupport::Concern
-
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
-
- included do
- class_attribute :attribute_types_cached_by_default, instance_writer: false
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- end
-
- module ClassMethods
- # +cache_attributes+ allows you to declare which converted attribute
- # values should be cached. Usually caching only pays off for attributes
- # with expensive conversion methods, like time related columns (e.g.
- # +created_at+, +updated_at+).
- def cache_attributes(*attribute_names)
- cached_attributes.merge attribute_names.map { |attr| attr.to_s }
- end
-
- # Returns the attributes which are cached. By default time related columns
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
- def cached_attributes
- @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
- end
-
- # Returns +true+ if the provided attribute is being cached.
- def cache_attribute?(attr_name)
- cached_attributes.include?(attr_name)
- end
-
- protected
-
+ ReaderMethodCache = Class.new(AttributeMethodCache) {
+ private
# 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).
+ # 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
@@ -46,61 +22,81 @@ module ActiveRecord
# 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
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
- def __temp__#{safe_name}
- read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
- end
- alias_method #{name.inspect}, :__temp__#{safe_name}
- undef_method :__temp__#{safe_name}
- STR
+ # key the @attributes in read_attribute.
+ def method_body(method_name, const_name)
+ <<-EOMETHOD
+ def #{method_name}
+ name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
+ _read_attribute(name) { |n| missing_attribute(n, caller) }
+ end
+ EOMETHOD
end
+ }.new
- private
+ extend ActiveSupport::Concern
- def cacheable_column?(column)
- if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- ! serialized_attributes.include? column.name
- else
- attribute_types_cached_by_default.include?(column.type)
+ module ClassMethods
+ [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name|
+ define_method method_name do |*|
+ cached_attributes_deprecation_warning(method_name)
+ true
+ end
+ end
+
+ protected
+
+ def cached_attributes_deprecation_warning(method_name)
+ ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached."
+ end
+
+ if Module.methods_transplantable?
+ def define_method_attribute(name)
+ method = ReaderMethodCache[name]
+ generated_attribute_methods.module_eval { define_method name, method }
+ end
+ else
+ 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
end
end
end
+ ID = 'id'.freeze
+
# 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
+ # it has been typecast (for example, "2004-12-12" in a date column is cast
# to a date object, like Date.new(2004, 12, 12)).
- def read_attribute(attr_name)
- # If it's cached, just return it
- # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/3552829.
+ def read_attribute(attr_name, &block)
name = attr_name.to_s
- @attributes_cache[name] || @attributes_cache.fetch(name) {
- column = @columns_hash.fetch(name) {
- return @attributes.fetch(name) {
- if name == 'id' && self.class.primary_key != name
- read_attribute(self.class.primary_key)
- end
- }
- }
-
- value = @attributes.fetch(name) {
- return block_given? ? yield(name) : nil
- }
+ name = self.class.primary_key if name == ID
+ _read_attribute(name, &block)
+ end
- if self.class.cache_attribute?(name)
- @attributes_cache[name] = column.type_cast(value)
- else
- column.type_cast value
- end
- }
+ # This method exists to avoid the expensive primary_key check internally, without
+ # breaking compatibility with the read_attribute API
+ def _read_attribute(attr_name) # :nodoc:
+ @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? }
end
private
def attribute(attribute_name)
- read_attribute(attribute_name)
+ _read_attribute(attribute_name)
end
end
end