diff options
author | Lisa Ugray <lisa.ugray@shopify.com> | 2017-10-19 12:45:07 -0400 |
---|---|---|
committer | Lisa Ugray <lisa.ugray@shopify.com> | 2017-11-09 14:29:39 -0500 |
commit | c3675f50d2e59b7fc173d7b332860c4b1a24a726 (patch) | |
tree | 736119c8ea9b683ac465c07e6a640d7e14bbc1b0 /activemodel/lib/active_model/attribute_set | |
parent | dac7c8844b4d9944eaa0fca98b45ee478cdb7201 (diff) | |
download | rails-c3675f50d2e59b7fc173d7b332860c4b1a24a726.tar.gz rails-c3675f50d2e59b7fc173d7b332860c4b1a24a726.tar.bz2 rails-c3675f50d2e59b7fc173d7b332860c4b1a24a726.zip |
Move Attribute and AttributeSet to ActiveModel
Use these to back the attributes API. Stop automatically including
ActiveModel::Dirty in ActiveModel::Attributes, and make it optional.
Diffstat (limited to 'activemodel/lib/active_model/attribute_set')
-rw-r--r-- | activemodel/lib/active_model/attribute_set/builder.rb | 124 | ||||
-rw-r--r-- | activemodel/lib/active_model/attribute_set/yaml_encoder.rb | 41 |
2 files changed, 165 insertions, 0 deletions
diff --git a/activemodel/lib/active_model/attribute_set/builder.rb b/activemodel/lib/active_model/attribute_set/builder.rb new file mode 100644 index 0000000000..f94f47370f --- /dev/null +++ b/activemodel/lib/active_model/attribute_set/builder.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class AttributeSet # :nodoc: + class Builder # :nodoc: + attr_reader :types, :always_initialized, :default + + def initialize(types, always_initialized = nil, &default) + @types = types + @always_initialized = always_initialized + @default = default + end + + def build_from_database(values = {}, additional_types = {}) + if always_initialized && !values.key?(always_initialized) + values[always_initialized] = nil + end + + attributes = LazyAttributeHash.new(types, values, additional_types, &default) + AttributeSet.new(attributes) + end + end + end + + class LazyAttributeHash # :nodoc: + delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize + + def initialize(types, values, additional_types, &default) + @types = types + @values = values + @additional_types = additional_types + @materialized = false + @delegate_hash = {} + @default = default || proc {} + end + + def key?(key) + delegate_hash.key?(key) || values.key?(key) || types.key?(key) + end + + def [](key) + delegate_hash[key] || assign_default_value(key) + end + + def []=(key, value) + if frozen? + raise RuntimeError, "Can't modify frozen hash" + end + delegate_hash[key] = value + end + + def deep_dup + dup.tap do |copy| + copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup)) + end + end + + def initialize_dup(_) + @delegate_hash = Hash[delegate_hash] + super + end + + def select + 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 + end + + def ==(other) + if other.is_a?(LazyAttributeHash) + materialize == other.materialize + else + materialize == other + end + end + + def marshal_dump + materialize + end + + def marshal_load(delegate_hash) + @delegate_hash = delegate_hash + @types = {} + @values = {} + @additional_types = {} + @materialized = true + end + + protected + + attr_reader :types, :values, :additional_types, :delegate_hash, :default + + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + unless frozen? + @materialized = true + end + end + delegate_hash + end + + private + + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } + + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) + end + end + end +end diff --git a/activemodel/lib/active_model/attribute_set/yaml_encoder.rb b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb new file mode 100644 index 0000000000..4ea945b956 --- /dev/null +++ b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveModel + class AttributeSet + # Attempts to do more intelligent YAML dumping of an + # ActiveModel::AttributeSet to reduce the size of the resulting string + class YAMLEncoder # :nodoc: + def initialize(default_types) + @default_types = default_types + end + + def encode(attribute_set, coder) + coder["concise_attributes"] = attribute_set.each_value.map do |attr| + if attr.type.equal?(default_types[attr.name]) + attr.with_type(nil) + else + attr + end + end + end + + def decode(coder) + if coder["attributes"] + coder["attributes"] + else + attributes_hash = Hash[coder["concise_attributes"].map do |attr| + if attr.type.nil? + attr = attr.with_type(default_types[attr.name]) + end + [attr.name, attr] + end] + AttributeSet.new(attributes_hash) + end + end + + protected + + attr_reader :default_types + end + end +end |