mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #39612 from kamipo/faster_attributes
PERF: 45% faster attributes for readonly usage
This commit is contained in:
commit
d2cdf0be67
4 changed files with 105 additions and 33 deletions
|
@ -5,16 +5,16 @@ require "active_support/core_ext/object/duplicable"
|
|||
module ActiveModel
|
||||
class Attribute # :nodoc:
|
||||
class << self
|
||||
def from_database(name, value, type)
|
||||
FromDatabase.new(name, value, type)
|
||||
def from_database(name, value_before_type_cast, type, value = nil)
|
||||
FromDatabase.new(name, value_before_type_cast, type, nil, value)
|
||||
end
|
||||
|
||||
def from_user(name, value, type, original_attribute = nil)
|
||||
FromUser.new(name, value, type, original_attribute)
|
||||
def from_user(name, value_before_type_cast, type, original_attribute = nil)
|
||||
FromUser.new(name, value_before_type_cast, type, original_attribute)
|
||||
end
|
||||
|
||||
def with_cast_value(name, value, type)
|
||||
WithCastValue.new(name, value, type)
|
||||
def with_cast_value(name, value_before_type_cast, type)
|
||||
WithCastValue.new(name, value_before_type_cast, type)
|
||||
end
|
||||
|
||||
def null(name)
|
||||
|
@ -30,11 +30,12 @@ module ActiveModel
|
|||
|
||||
# This method should not be called directly.
|
||||
# Use #from_database or #from_user
|
||||
def initialize(name, value_before_type_cast, type, original_attribute = nil)
|
||||
def initialize(name, value_before_type_cast, type, original_attribute = nil, value = nil)
|
||||
@name = name
|
||||
@value_before_type_cast = value_before_type_cast
|
||||
@type = type
|
||||
@original_attribute = original_attribute
|
||||
@value = value unless value.nil?
|
||||
end
|
||||
|
||||
def value
|
||||
|
|
|
@ -13,11 +13,11 @@ module ActiveModel
|
|||
end
|
||||
|
||||
def [](name)
|
||||
attributes[name] || Attribute.null(name)
|
||||
@attributes[name] || default_attribute(name)
|
||||
end
|
||||
|
||||
def []=(name, value)
|
||||
attributes[name] = value
|
||||
@attributes[name] = value
|
||||
end
|
||||
|
||||
def values_before_type_cast
|
||||
|
@ -25,9 +25,9 @@ module ActiveModel
|
|||
end
|
||||
|
||||
def to_hash
|
||||
initialized_attributes.transform_values(&:value)
|
||||
keys.index_with { |name| self[name].value }
|
||||
end
|
||||
alias_method :to_h, :to_hash
|
||||
alias :to_h :to_hash
|
||||
|
||||
def key?(name)
|
||||
attributes.key?(name) && self[name].initialized?
|
||||
|
@ -42,38 +42,36 @@ module ActiveModel
|
|||
end
|
||||
|
||||
def write_from_database(name, value)
|
||||
attributes[name] = self[name].with_value_from_database(value)
|
||||
@attributes[name] = self[name].with_value_from_database(value)
|
||||
end
|
||||
|
||||
def write_from_user(name, value)
|
||||
raise FrozenError, "can't modify frozen attributes" if frozen?
|
||||
attributes[name] = self[name].with_value_from_user(value)
|
||||
@attributes[name] = self[name].with_value_from_user(value)
|
||||
value
|
||||
end
|
||||
|
||||
def write_cast_value(name, value)
|
||||
attributes[name] = self[name].with_cast_value(value)
|
||||
@attributes[name] = self[name].with_cast_value(value)
|
||||
value
|
||||
end
|
||||
|
||||
def freeze
|
||||
@attributes.freeze
|
||||
attributes.freeze
|
||||
super
|
||||
end
|
||||
|
||||
def deep_dup
|
||||
self.class.allocate.tap do |copy|
|
||||
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
||||
end
|
||||
AttributeSet.new(attributes.deep_dup)
|
||||
end
|
||||
|
||||
def initialize_dup(_)
|
||||
@attributes = attributes.dup
|
||||
@attributes = @attributes.dup
|
||||
super
|
||||
end
|
||||
|
||||
def initialize_clone(_)
|
||||
@attributes = attributes.clone
|
||||
@attributes = @attributes.clone
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -84,7 +82,7 @@ module ActiveModel
|
|||
end
|
||||
|
||||
def accessed
|
||||
attributes.select { |_, attr| attr.has_been_read? }.keys
|
||||
attributes.each_key.select { |name| self[name].has_been_read? }
|
||||
end
|
||||
|
||||
def map(&block)
|
||||
|
@ -100,8 +98,8 @@ module ActiveModel
|
|||
attr_reader :attributes
|
||||
|
||||
private
|
||||
def initialized_attributes
|
||||
attributes.select { |_, attr| attr.initialized? }
|
||||
def default_attribute(name)
|
||||
Attribute.null(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,14 +13,86 @@ module ActiveModel
|
|||
end
|
||||
|
||||
def build_from_database(values = {}, additional_types = {})
|
||||
attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
|
||||
AttributeSet.new(attributes)
|
||||
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LazyAttributeSet < AttributeSet # :nodoc:
|
||||
def initialize(values, types, additional_types, default_attributes, attributes = {})
|
||||
super(attributes)
|
||||
@values = values
|
||||
@types = types
|
||||
@additional_types = additional_types
|
||||
@default_attributes = default_attributes
|
||||
@casted_values = {}
|
||||
@materialized = false
|
||||
end
|
||||
|
||||
def key?(name)
|
||||
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
||||
end
|
||||
|
||||
def keys
|
||||
keys = values.keys | types.keys | @attributes.keys
|
||||
keys.keep_if { |name| self[name].initialized? }
|
||||
end
|
||||
|
||||
def fetch_value(name, &block)
|
||||
if attr = @attributes[name]
|
||||
return attr.value(&block)
|
||||
end
|
||||
|
||||
@casted_values.fetch(name) do
|
||||
value_present = true
|
||||
value = values.fetch(name) { value_present = false }
|
||||
|
||||
if value_present
|
||||
type = additional_types.fetch(name, types[name])
|
||||
@casted_values[name] = type.deserialize(value)
|
||||
else
|
||||
attr = default_attribute(name, value_present, value)
|
||||
attr.value(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def attributes
|
||||
unless @materialized
|
||||
values.each_key { |key| self[key] }
|
||||
types.each_key { |key| self[key] }
|
||||
@materialized = true
|
||||
end
|
||||
@attributes
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :values, :types, :additional_types, :default_attributes
|
||||
|
||||
def default_attribute(
|
||||
name,
|
||||
value_present = true,
|
||||
value = values.fetch(name) { value_present = false }
|
||||
)
|
||||
type = additional_types.fetch(name, types[name])
|
||||
|
||||
if value_present
|
||||
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
|
||||
elsif types.key?(name)
|
||||
if attr = default_attributes[name]
|
||||
@attributes[name] = attr.dup
|
||||
else
|
||||
@attributes[name] = Attribute.uninitialized(name, type)
|
||||
end
|
||||
else
|
||||
Attribute.null(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class LazyAttributeHash # :nodoc:
|
||||
delegate :transform_values, :each_key, :each_value, :fetch, :except, to: :materialize
|
||||
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
||||
|
||||
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
||||
@types = types
|
||||
|
@ -54,14 +126,9 @@ module ActiveModel
|
|||
super
|
||||
end
|
||||
|
||||
def select
|
||||
def each_key(&block)
|
||||
keys = types.keys | values.keys | delegate_hash.keys
|
||||
keys.each_with_object({}) do |key, hash|
|
||||
attribute = self[key]
|
||||
if yield(key, attribute)
|
||||
hash[key] = attribute
|
||||
end
|
||||
end
|
||||
keys.each(&block)
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
|
|
|
@ -219,6 +219,12 @@ module ActiveModel
|
|||
|
||||
test "marshalling dump/load legacy materialized attribute hash" do
|
||||
builder = AttributeSet::Builder.new(foo: Type::String.new)
|
||||
|
||||
def builder.build_from_database(values = {}, additional_types = {})
|
||||
attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
|
||||
AttributeSet.new(attributes)
|
||||
end
|
||||
|
||||
attributes = builder.build_from_database(foo: "1")
|
||||
|
||||
attributes.instance_variable_get(:@attributes).instance_eval do
|
||||
|
|
Loading…
Reference in a new issue