aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_set
diff options
context:
space:
mode:
authorSean Griffin <sean@thoughtbot.com>2014-11-14 11:20:28 -0700
committerSean Griffin <sean@thoughtbot.com>2014-11-14 14:30:40 -0700
commit0f29c216074c5da6644feddb5184c4881c078b0d (patch)
tree5ace61c20fc1f4b54c9187567ffccc8c695c3c7e /activerecord/lib/active_record/attribute_set
parent70d1b5a7f8e25b077168deaf592e0e58c3f2bdd1 (diff)
downloadrails-0f29c216074c5da6644feddb5184c4881c078b0d.tar.gz
rails-0f29c216074c5da6644feddb5184c4881c078b0d.tar.bz2
rails-0f29c216074c5da6644feddb5184c4881c078b0d.zip
Reduce the amount of work performed when instantiating AR models
We don't know which attributes will or won't be used, and we don't want to create massive bottlenecks at instantiation. Rather than doing *any* iteration over types and values, we can lazily instantiate the object. The lazy attribute hash should not fully implement hash, or subclass hash at any point in the future. It is not meant to be a replacement, but instead implement its own interface which happens to overlap.
Diffstat (limited to 'activerecord/lib/active_record/attribute_set')
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb78
1 files changed, 63 insertions, 15 deletions
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index d4a787f2fe..3946e02d10 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -1,35 +1,83 @@
module ActiveRecord
class AttributeSet # :nodoc:
class Builder # :nodoc:
- attr_reader :types
+ attr_reader :types, :always_initialized
- def initialize(types)
+ def initialize(types, always_initialized = nil)
@types = types
+ @always_initialized = always_initialized
end
def build_from_database(values = {}, additional_types = {})
- attributes = build_attributes_from_values(values, additional_types)
- add_uninitialized_attributes(attributes)
+ if always_initialized && !values.key?(always_initialized)
+ values[always_initialized] = nil
+ end
+
+ attributes = LazyAttributeHash.new(types, values, additional_types)
AttributeSet.new(attributes)
end
private
+ end
+ end
+
+ class LazyAttributeHash
+ delegate :select, :transform_values, to: :materialize
+ delegate :[], :[]=, :freeze, to: :delegate_hash
+
+ def initialize(types, values, additional_types)
+ @types = types
+ @values = values
+ @additional_types = additional_types
+ @materialized = false
+ @delegate_hash = {}
+ assign_default_proc
+ end
+
+ def key?(key)
+ delegate_hash.key?(key) || values.key?(key) || types.key?(key)
+ end
+
+ def initialized_keys
+ delegate_hash.keys | values.keys
+ end
- def build_attributes_from_values(values, additional_types)
- values.each_with_object({}) do |(name, value), hash|
- type = additional_types.fetch(name, types[name])
- hash[name] = Attribute.from_database(name, value, type)
+ def initialize_dup(_)
+ @delegate_hash = delegate_hash.transform_values(&:dup)
+ assign_default_proc
+ super
+ end
+
+ def initialize_clone(_)
+ @delegate_hash = delegate_hash.clone
+ super
+ end
+
+ protected
+
+ attr_reader :types, :values, :additional_types, :delegate_hash
+
+ private
+
+ def assign_default_proc
+ delegate_hash.default_proc = proc do |hash, name|
+ type = additional_types.fetch(name, types[name])
+
+ if values.key?(name)
+ hash[name] = Attribute.from_database(name, values[name], type)
+ elsif type
+ hash[name] = Attribute.uninitialized(name, type)
end
end
+ end
- def add_uninitialized_attributes(attributes)
- types.each_key do |name|
- next if attributes.key? name
- type = types[name]
- attributes[name] =
- Attribute.uninitialized(name, type)
- end
+ def materialize
+ unless @materialized
+ values.each_key { |key| delegate_hash[key] }
+ types.each_key { |key| delegate_hash[key] }
+ @materialized = true
end
+ delegate_hash
end
end
end