mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Introduce an object to aid in creation and management of @attributes
Mostly delegation to start, but we can start moving a lot of behavior in bulk to this object.
This commit is contained in:
parent
dccf6da66b
commit
099af48d31
8 changed files with 120 additions and 23 deletions
|
@ -32,6 +32,7 @@ module ActiveRecord
|
||||||
extend ActiveSupport::Autoload
|
extend ActiveSupport::Autoload
|
||||||
|
|
||||||
autoload :Attribute
|
autoload :Attribute
|
||||||
|
autoload :AttributeSet
|
||||||
autoload :Base
|
autoload :Base
|
||||||
autoload :Callbacks
|
autoload :Callbacks
|
||||||
autoload :Core
|
autoload :Core
|
||||||
|
|
|
@ -230,7 +230,7 @@ module ActiveRecord
|
||||||
# For queries selecting a subset of columns, return false for unselected columns.
|
# For queries selecting a subset of columns, return false for unselected columns.
|
||||||
# We check defined?(@attributes) not to issue warnings if called on objects that
|
# We check defined?(@attributes) not to issue warnings if called on objects that
|
||||||
# have been allocated but not yet initialized.
|
# have been allocated but not yet initialized.
|
||||||
if defined?(@attributes) && @attributes.any? && self.class.column_names.include?(name)
|
if defined?(@attributes) && self.class.column_names.include?(name)
|
||||||
return has_attribute?(name)
|
return has_attribute?(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ module ActiveRecord
|
||||||
# person.has_attribute?('age') # => true
|
# person.has_attribute?('age') # => true
|
||||||
# person.has_attribute?(:nothing) # => false
|
# person.has_attribute?(:nothing) # => false
|
||||||
def has_attribute?(attr_name)
|
def has_attribute?(attr_name)
|
||||||
@attributes.has_key?(attr_name.to_s)
|
@attributes.include?(attr_name.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array of names for the attributes available on this object.
|
# Returns an array of names for the attributes available on this object.
|
||||||
|
@ -367,12 +367,6 @@ module ActiveRecord
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def clone_attributes # :nodoc:
|
|
||||||
@attributes.each_with_object({}) do |(name, attr), h|
|
|
||||||
h[name] = attr.dup
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
|
def clone_attribute_value(reader_method, attribute_name) # :nodoc:
|
||||||
value = send(reader_method, attribute_name)
|
value = send(reader_method, attribute_name)
|
||||||
value.duplicable? ? value.clone : value
|
value.duplicable? ? value.clone : value
|
||||||
|
|
51
activerecord/lib/active_record/attribute_set.rb
Normal file
51
activerecord/lib/active_record/attribute_set.rb
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
module ActiveRecord
|
||||||
|
class AttributeSet # :nodoc:
|
||||||
|
delegate :[], :[]=, :fetch, :include?, :keys, :each_with_object, to: :attributes
|
||||||
|
|
||||||
|
def initialize(attributes)
|
||||||
|
@attributes = attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(other)
|
||||||
|
attributes.update(other.attributes)
|
||||||
|
end
|
||||||
|
|
||||||
|
def freeze
|
||||||
|
@attributes.freeze
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_dup(_)
|
||||||
|
@attributes = attributes.dup
|
||||||
|
attributes.each do |key, attr|
|
||||||
|
attributes[key] = attr.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_clone(_)
|
||||||
|
@attributes = attributes.clone
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
class Builder
|
||||||
|
def initialize(types)
|
||||||
|
@types = types
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_from_database(values, additional_types = {})
|
||||||
|
attributes = values.each_with_object({}) do |(name, value), hash|
|
||||||
|
type = additional_types.fetch(name, @types[name])
|
||||||
|
hash[name] = Attribute.from_database(value, type)
|
||||||
|
end
|
||||||
|
AttributeSet.new(attributes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
attr_reader :attributes
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -109,13 +109,14 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_caches_calculated_from_columns
|
def clear_caches_calculated_from_columns
|
||||||
|
@attributes_builder = nil
|
||||||
|
@column_defaults = nil
|
||||||
|
@column_names = nil
|
||||||
|
@column_types = nil
|
||||||
@columns = nil
|
@columns = nil
|
||||||
@columns_hash = nil
|
@columns_hash = nil
|
||||||
@column_types = nil
|
|
||||||
@column_defaults = nil
|
|
||||||
@raw_column_defaults = nil
|
|
||||||
@column_names = nil
|
|
||||||
@content_columns = nil
|
@content_columns = nil
|
||||||
|
@raw_column_defaults = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -251,11 +251,10 @@ module ActiveRecord
|
||||||
def initialize(attributes = nil, options = {})
|
def initialize(attributes = nil, options = {})
|
||||||
defaults = {}
|
defaults = {}
|
||||||
self.class.raw_column_defaults.each do |k, v|
|
self.class.raw_column_defaults.each do |k, v|
|
||||||
default = v.duplicable? ? v.dup : v
|
defaults[k] = v.duplicable? ? v.dup : v
|
||||||
defaults[k] = Attribute.from_database(default, type_for_attribute(k))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@attributes = defaults
|
@attributes = self.class.attributes_builder.build_from_database(defaults)
|
||||||
@column_types = self.class.column_types
|
@column_types = self.class.column_types
|
||||||
|
|
||||||
init_internals
|
init_internals
|
||||||
|
@ -325,7 +324,7 @@ module ActiveRecord
|
||||||
##
|
##
|
||||||
def initialize_dup(other) # :nodoc:
|
def initialize_dup(other) # :nodoc:
|
||||||
pk = self.class.primary_key
|
pk = self.class.primary_key
|
||||||
@attributes = other.clone_attributes
|
@attributes = @attributes.dup
|
||||||
@attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
|
@attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk))
|
||||||
|
|
||||||
run_callbacks(:initialize) unless _initialize_callbacks.empty?
|
run_callbacks(:initialize) unless _initialize_callbacks.empty?
|
||||||
|
|
|
@ -219,12 +219,18 @@ module ActiveRecord
|
||||||
connection.schema_cache.table_exists?(table_name)
|
connection.schema_cache.table_exists?(table_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attributes_builder # :nodoc:
|
||||||
|
@attributes_builder ||= AttributeSet::Builder.new(column_types)
|
||||||
|
end
|
||||||
|
|
||||||
def column_types # :nodoc:
|
def column_types # :nodoc:
|
||||||
@column_types ||= Hash[columns.map { |column| [column.name, column.cast_type] }]
|
@column_types ||= Hash.new(Type::Value.new).tap do |column_types|
|
||||||
|
columns.each { |column| column_types[column.name] = column.cast_type }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def type_for_attribute(attr_name) # :nodoc:
|
def type_for_attribute(attr_name) # :nodoc:
|
||||||
column_types.fetch(attr_name) { Type::Value.new }
|
column_types[attr_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a hash where the keys are column names and the values are
|
# Returns a hash where the keys are column names and the values are
|
||||||
|
|
|
@ -48,11 +48,7 @@ module ActiveRecord
|
||||||
# how this "single-table" inheritance mapping is implemented.
|
# how this "single-table" inheritance mapping is implemented.
|
||||||
def instantiate(attributes, column_types = {})
|
def instantiate(attributes, column_types = {})
|
||||||
klass = discriminate_class_for_record(attributes)
|
klass = discriminate_class_for_record(attributes)
|
||||||
|
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
|
||||||
attributes = attributes.each_with_object({}) do |(name, value), h|
|
|
||||||
type = column_types.fetch(name) { klass.type_for_attribute(name) }
|
|
||||||
h[name] = Attribute.from_database(value, type)
|
|
||||||
end
|
|
||||||
klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
|
klass.allocate.init_with('attributes' => attributes, 'new_record' => false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
49
activerecord/test/cases/attribute_set_test.rb
Normal file
49
activerecord/test/cases/attribute_set_test.rb
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
require 'cases/helper'
|
||||||
|
|
||||||
|
module ActiveRecord
|
||||||
|
class AttributeSetTest < ActiveRecord::TestCase
|
||||||
|
test "building a new set from raw attributes" do
|
||||||
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
|
||||||
|
attributes = builder.build_from_database(foo: '1.1', bar: '2.2')
|
||||||
|
|
||||||
|
assert_equal 1, attributes[:foo].value
|
||||||
|
assert_equal 2.2, attributes[:bar].value
|
||||||
|
end
|
||||||
|
|
||||||
|
test "building with custom types" do
|
||||||
|
builder = AttributeSet::Builder.new(foo: Type::Float.new)
|
||||||
|
attributes = builder.build_from_database({ foo: '3.3', bar: '4.4' }, { bar: Type::Integer.new })
|
||||||
|
|
||||||
|
assert_equal 3.3, attributes[:foo].value
|
||||||
|
assert_equal 4, attributes[:bar].value
|
||||||
|
end
|
||||||
|
|
||||||
|
test "duping creates a new hash and dups each attribute" do
|
||||||
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
|
||||||
|
attributes = builder.build_from_database(foo: 1, bar: 'foo')
|
||||||
|
|
||||||
|
# Ensure the type cast value is cached
|
||||||
|
attributes[:foo].value
|
||||||
|
attributes[:bar].value
|
||||||
|
|
||||||
|
duped = attributes.dup
|
||||||
|
duped[:foo] = Attribute.from_database(2, Type::Integer.new)
|
||||||
|
duped[:bar].value << 'bar'
|
||||||
|
|
||||||
|
assert_equal 1, attributes[:foo].value
|
||||||
|
assert_equal 2, duped[:foo].value
|
||||||
|
assert_equal 'foo', attributes[:bar].value
|
||||||
|
assert_equal 'foobar', duped[:bar].value
|
||||||
|
end
|
||||||
|
|
||||||
|
test "freezing cloned set does not freeze original" do
|
||||||
|
attributes = AttributeSet.new({})
|
||||||
|
clone = attributes.clone
|
||||||
|
|
||||||
|
clone.freeze
|
||||||
|
|
||||||
|
assert clone.frozen?
|
||||||
|
assert_not attributes.frozen?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue