mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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.
This commit is contained in:
parent
70d1b5a7f8
commit
0f29c21607
6 changed files with 80 additions and 28 deletions
|
@ -120,6 +120,7 @@ module ActiveRecord
|
||||||
def primary_key=(value)
|
def primary_key=(value)
|
||||||
@primary_key = value && value.to_s
|
@primary_key = value && value.to_s
|
||||||
@quoted_primary_key = nil
|
@quoted_primary_key = nil
|
||||||
|
@attributes_builder = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,8 +2,6 @@ require 'active_record/attribute_set/builder'
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
class AttributeSet # :nodoc:
|
class AttributeSet # :nodoc:
|
||||||
delegate :keys, to: :initialized_attributes
|
|
||||||
|
|
||||||
def initialize(attributes)
|
def initialize(attributes)
|
||||||
@attributes = attributes
|
@attributes = attributes
|
||||||
end
|
end
|
||||||
|
@ -25,6 +23,10 @@ module ActiveRecord
|
||||||
attributes.key?(name) && self[name].initialized?
|
attributes.key?(name) && self[name].initialized?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def keys
|
||||||
|
attributes.initialized_keys
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_value(name, &block)
|
def fetch_value(name, &block)
|
||||||
self[name].value(&block)
|
self[name].value(&block)
|
||||||
end
|
end
|
||||||
|
@ -43,7 +45,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize_dup(_)
|
def initialize_dup(_)
|
||||||
@attributes = attributes.transform_values(&:dup)
|
@attributes = attributes.dup
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,12 +60,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_initialized(key)
|
|
||||||
unless self[key].initialized?
|
|
||||||
write_from_database(key, nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_reader :attributes
|
attr_reader :attributes
|
||||||
|
|
|
@ -1,35 +1,83 @@
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
class AttributeSet # :nodoc:
|
class AttributeSet # :nodoc:
|
||||||
class Builder # :nodoc:
|
class Builder # :nodoc:
|
||||||
attr_reader :types
|
attr_reader :types, :always_initialized
|
||||||
|
|
||||||
def initialize(types)
|
def initialize(types, always_initialized = nil)
|
||||||
@types = types
|
@types = types
|
||||||
|
@always_initialized = always_initialized
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_from_database(values = {}, additional_types = {})
|
def build_from_database(values = {}, additional_types = {})
|
||||||
attributes = build_attributes_from_values(values, additional_types)
|
if always_initialized && !values.key?(always_initialized)
|
||||||
add_uninitialized_attributes(attributes)
|
values[always_initialized] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes = LazyAttributeHash.new(types, values, additional_types)
|
||||||
AttributeSet.new(attributes)
|
AttributeSet.new(attributes)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def build_attributes_from_values(values, additional_types)
|
class LazyAttributeHash
|
||||||
values.each_with_object({}) do |(name, value), hash|
|
delegate :select, :transform_values, to: :materialize
|
||||||
type = additional_types.fetch(name, types[name])
|
delegate :[], :[]=, :freeze, to: :delegate_hash
|
||||||
hash[name] = Attribute.from_database(name, value, type)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_uninitialized_attributes(attributes)
|
def initialize(types, values, additional_types)
|
||||||
types.each_key do |name|
|
@types = types
|
||||||
next if attributes.key? name
|
@values = values
|
||||||
type = types[name]
|
@additional_types = additional_types
|
||||||
attributes[name] =
|
@materialized = false
|
||||||
Attribute.uninitialized(name, type)
|
@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 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
|
end
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -536,8 +536,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def init_internals
|
def init_internals
|
||||||
@attributes.ensure_initialized(self.class.primary_key)
|
|
||||||
|
|
||||||
@aggregation_cache = {}
|
@aggregation_cache = {}
|
||||||
@association_cache = {}
|
@association_cache = {}
|
||||||
@readonly = false
|
@readonly = false
|
||||||
|
|
|
@ -231,7 +231,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def attributes_builder # :nodoc:
|
def attributes_builder # :nodoc:
|
||||||
@attributes_builder ||= AttributeSet::Builder.new(column_types)
|
@attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key)
|
||||||
end
|
end
|
||||||
|
|
||||||
def column_types # :nodoc:
|
def column_types # :nodoc:
|
||||||
|
|
|
@ -123,6 +123,15 @@ module ActiveRecord
|
||||||
assert_nil attributes.fetch_value(:bar)
|
assert_nil attributes.fetch_value(:bar)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "the primary_key is always initialized" do
|
||||||
|
builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo)
|
||||||
|
attributes = builder.build_from_database
|
||||||
|
|
||||||
|
assert attributes.key?(:foo)
|
||||||
|
assert_equal [:foo], attributes.keys
|
||||||
|
assert attributes[:foo].initialized?
|
||||||
|
end
|
||||||
|
|
||||||
class MyType
|
class MyType
|
||||||
def type_cast_from_user(value)
|
def type_cast_from_user(value)
|
||||||
return if value.nil?
|
return if value.nil?
|
||||||
|
|
Loading…
Reference in a new issue