mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
95b86e57a6
There are two concerns which are both being combined into one here, but both have the same goal. There are certain attributes which we want to always consider initialized. Previously, they were handled separately. The primary key (which is assumed to be backed by a database column) needs to be initialized, because there is a ton of code in Active Record that assumes `foo.id` will never raise. Additionally, we want attributes which aren't backed by a database column to always be initialized, since we would never receive a database value for them. Ultimately these two concerns can be combined into one. The old implementation hid a lot of inherent complexity, and is hard to optimize from the outside. We can simplify things significantly by just passing in a hash. This has slightly different semantics from the old behavior, in that `Foo.select(:bar).first.id` will return the default value for the primary key, rather than `nil` unconditionally -- however, the default value is always `nil` in practice.
113 lines
2.4 KiB
Ruby
113 lines
2.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "active_model/attribute_set/builder"
|
|
require "active_model/attribute_set/yaml_encoder"
|
|
|
|
module ActiveModel
|
|
class AttributeSet # :nodoc:
|
|
delegate :each_value, :fetch, :except, to: :attributes
|
|
|
|
def initialize(attributes)
|
|
@attributes = attributes
|
|
end
|
|
|
|
def [](name)
|
|
attributes[name] || Attribute.null(name)
|
|
end
|
|
|
|
def []=(name, value)
|
|
attributes[name] = value
|
|
end
|
|
|
|
def values_before_type_cast
|
|
attributes.transform_values(&:value_before_type_cast)
|
|
end
|
|
|
|
def to_hash
|
|
initialized_attributes.transform_values(&:value)
|
|
end
|
|
alias_method :to_h, :to_hash
|
|
|
|
def key?(name)
|
|
attributes.key?(name) && self[name].initialized?
|
|
end
|
|
|
|
def keys
|
|
attributes.each_key.select { |name| self[name].initialized? }
|
|
end
|
|
|
|
if defined?(JRUBY_VERSION)
|
|
# This form is significantly faster on JRuby, and this is one of our biggest hotspots.
|
|
# https://github.com/jruby/jruby/pull/2562
|
|
def fetch_value(name, &block)
|
|
self[name].value(&block)
|
|
end
|
|
else
|
|
def fetch_value(name)
|
|
self[name].value { |n| yield n if block_given? }
|
|
end
|
|
end
|
|
|
|
def write_from_database(name, value)
|
|
attributes[name] = self[name].with_value_from_database(value)
|
|
end
|
|
|
|
def write_from_user(name, value)
|
|
attributes[name] = self[name].with_value_from_user(value)
|
|
end
|
|
|
|
def write_cast_value(name, value)
|
|
attributes[name] = self[name].with_cast_value(value)
|
|
end
|
|
|
|
def freeze
|
|
@attributes.freeze
|
|
super
|
|
end
|
|
|
|
def deep_dup
|
|
self.class.allocate.tap do |copy|
|
|
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
|
end
|
|
end
|
|
|
|
def initialize_dup(_)
|
|
@attributes = attributes.dup
|
|
super
|
|
end
|
|
|
|
def initialize_clone(_)
|
|
@attributes = attributes.clone
|
|
super
|
|
end
|
|
|
|
def reset(key)
|
|
if key?(key)
|
|
write_from_database(key, nil)
|
|
end
|
|
end
|
|
|
|
def accessed
|
|
attributes.select { |_, attr| attr.has_been_read? }.keys
|
|
end
|
|
|
|
def map(&block)
|
|
new_attributes = attributes.transform_values(&block)
|
|
AttributeSet.new(new_attributes)
|
|
end
|
|
|
|
def ==(other)
|
|
attributes == other.attributes
|
|
end
|
|
|
|
protected
|
|
|
|
attr_reader :attributes
|
|
|
|
private
|
|
|
|
def initialized_attributes
|
|
attributes.select { |_, attr| attr.initialized? }
|
|
end
|
|
end
|
|
end
|