From 099af48d31bb2b5fc8e3fff39b825efff7964d55 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Thu, 19 Jun 2014 11:13:19 -0600 Subject: 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. --- activerecord/lib/active_record.rb | 1 + .../lib/active_record/attribute_methods.rb | 10 +---- activerecord/lib/active_record/attribute_set.rb | 51 ++++++++++++++++++++++ activerecord/lib/active_record/attributes.rb | 9 ++-- activerecord/lib/active_record/core.rb | 7 ++- activerecord/lib/active_record/model_schema.rb | 10 ++++- activerecord/lib/active_record/persistence.rb | 6 +-- 7 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 activerecord/lib/active_record/attribute_set.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 53fa132219..ab85414277 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -32,6 +32,7 @@ module ActiveRecord extend ActiveSupport::Autoload autoload :Attribute + autoload :AttributeSet autoload :Base autoload :Callbacks autoload :Core diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index b4d75d6556..268cec6160 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -230,7 +230,7 @@ module ActiveRecord # 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 # 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) end @@ -247,7 +247,7 @@ module ActiveRecord # person.has_attribute?('age') # => true # person.has_attribute?(:nothing) # => false def has_attribute?(attr_name) - @attributes.has_key?(attr_name.to_s) + @attributes.include?(attr_name.to_s) end # Returns an array of names for the attributes available on this object. @@ -367,12 +367,6 @@ module ActiveRecord 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: value = send(reader_method, attribute_name) value.duplicable? ? value.clone : value diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb new file mode 100644 index 0000000000..102ef17e16 --- /dev/null +++ b/activerecord/lib/active_record/attribute_set.rb @@ -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 diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 9c80121d70..d0287140c8 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -109,13 +109,14 @@ module ActiveRecord end def clear_caches_calculated_from_columns - @columns = nil - @columns_hash = nil - @column_types = nil + @attributes_builder = nil @column_defaults = nil - @raw_column_defaults = nil @column_names = nil + @column_types = nil + @columns = nil + @columns_hash = nil @content_columns = nil + @raw_column_defaults = nil end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d39e5fddfe..e8e165a7c8 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -251,11 +251,10 @@ module ActiveRecord def initialize(attributes = nil, options = {}) defaults = {} self.class.raw_column_defaults.each do |k, v| - default = v.duplicable? ? v.dup : v - defaults[k] = Attribute.from_database(default, type_for_attribute(k)) + defaults[k] = v.duplicable? ? v.dup : v end - @attributes = defaults + @attributes = self.class.attributes_builder.build_from_database(defaults) @column_types = self.class.column_types init_internals @@ -325,7 +324,7 @@ module ActiveRecord ## def initialize_dup(other) # :nodoc: pk = self.class.primary_key - @attributes = other.clone_attributes + @attributes = @attributes.dup @attributes[pk] = Attribute.from_database(nil, type_for_attribute(pk)) run_callbacks(:initialize) unless _initialize_callbacks.empty? diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index fad7eae461..ffd30eb87d 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -219,12 +219,18 @@ module ActiveRecord connection.schema_cache.table_exists?(table_name) end + def attributes_builder # :nodoc: + @attributes_builder ||= AttributeSet::Builder.new(column_types) + end + 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 def type_for_attribute(attr_name) # :nodoc: - column_types.fetch(attr_name) { Type::Value.new } + column_types[attr_name] end # Returns a hash where the keys are column names and the values are diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 5c744762d9..6707f12489 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -48,11 +48,7 @@ module ActiveRecord # how this "single-table" inheritance mapping is implemented. def instantiate(attributes, column_types = {}) klass = discriminate_class_for_record(attributes) - - 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 + attributes = klass.attributes_builder.build_from_database(attributes, column_types) klass.allocate.init_with('attributes' => attributes, 'new_record' => false) end -- cgit v1.2.3