1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activemodel/lib/active_model/attribute_set.rb
Sean Griffin 95b86e57a6 Change how AttributeSet::Builder receives its defaults
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.
2017-11-27 14:06:51 -07:00

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