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.
This commit is contained in:
parent
dac7c8844b
commit
c3675f50d2
|
@ -30,6 +30,8 @@ require "active_model/version"
|
|||
module ActiveModel
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Attribute
|
||||
autoload :Attributes
|
||||
autoload :AttributeAssignment
|
||||
autoload :AttributeMethods
|
||||
autoload :BlockValidator, "active_model/validator"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveModel
|
||||
class Attribute # :nodoc:
|
||||
class << self
|
||||
def from_database(name, value, type)
|
||||
|
@ -130,8 +130,6 @@ module ActiveRecord
|
|||
coder["value"] = value if defined?(@value)
|
||||
end
|
||||
|
||||
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
||||
# Workaround for Ruby 2.2 "private attribute?" warning.
|
||||
protected
|
||||
|
||||
attr_reader :original_attribute
|
||||
|
@ -237,6 +235,7 @@ module ActiveRecord
|
|||
self.class.new(name, type)
|
||||
end
|
||||
end
|
||||
|
||||
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
||||
end
|
||||
end
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/attribute"
|
||||
require "active_model/attribute"
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveModel
|
||||
class Attribute # :nodoc:
|
||||
class UserProvidedDefault < FromUser # :nodoc:
|
||||
def initialize(name, value, type, database_default)
|
||||
|
@ -22,8 +22,6 @@ module ActiveRecord
|
|||
self.class.new(name, user_provided_value, type, original_attribute)
|
||||
end
|
||||
|
||||
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
||||
# Workaround for Ruby 2.2 "private attribute?" warning.
|
||||
protected
|
||||
|
||||
attr_reader :user_provided_value
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveModel
|
||||
class AttributeMutationTracker # :nodoc:
|
||||
OPTION_NOT_GIVEN = Object.new
|
||||
|
||||
|
@ -107,5 +107,8 @@ module ActiveRecord
|
|||
|
||||
def original_value(*)
|
||||
end
|
||||
|
||||
def force_change(*)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/attribute_set/builder"
|
||||
require "active_record/attribute_set/yaml_encoder"
|
||||
require "active_model/attribute_set/builder"
|
||||
require "active_model/attribute_set/yaml_encoder"
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveModel
|
||||
class AttributeSet # :nodoc:
|
||||
delegate :each_value, :fetch, to: :attributes
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/attribute"
|
||||
require "active_model/attribute"
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveModel
|
||||
class AttributeSet # :nodoc:
|
||||
class Builder # :nodoc:
|
||||
attr_reader :types, :always_initialized, :default
|
||||
|
@ -92,8 +92,6 @@ module ActiveRecord
|
|||
@materialized = true
|
||||
end
|
||||
|
||||
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
||||
# Workaround for Ruby 2.2 "private attribute?" warning.
|
||||
protected
|
||||
|
||||
attr_reader :types, :values, :additional_types, :delegate_hash, :default
|
|
@ -1,9 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveModel
|
||||
class AttributeSet
|
||||
# Attempts to do more intelligent YAML dumping of an
|
||||
# ActiveRecord::AttributeSet to reduce the size of the resulting string
|
||||
# ActiveModel::AttributeSet to reduce the size of the resulting string
|
||||
class YAMLEncoder # :nodoc:
|
||||
def initialize(default_types)
|
||||
@default_types = default_types
|
||||
|
@ -33,8 +33,6 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
||||
# Workaround for Ruby 2.2 "private attribute?" warning.
|
||||
protected
|
||||
|
||||
attr_reader :default_types
|
|
@ -1,24 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/object/deep_dup"
|
||||
require "active_model/type"
|
||||
require "active_model/attribute_set"
|
||||
require "active_model/attribute/user_provided_default"
|
||||
|
||||
module ActiveModel
|
||||
module Attributes #:nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::AttributeMethods
|
||||
include ActiveModel::Dirty
|
||||
|
||||
included do
|
||||
attribute_method_suffix "="
|
||||
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
||||
self.attribute_types = {}
|
||||
self._default_attributes = {}
|
||||
self.attribute_types = Hash.new(Type.default_value)
|
||||
self._default_attributes = AttributeSet.new({})
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def attribute(name, cast_type = Type::Value.new, **options)
|
||||
self.attribute_types = attribute_types.merge(name.to_s => cast_type)
|
||||
self._default_attributes = _default_attributes.merge(name.to_s => options[:default])
|
||||
def attribute(name, type = Type::Value.new, **options)
|
||||
name = name.to_s
|
||||
if type.is_a?(Symbol)
|
||||
type = ActiveModel::Type.lookup(type, **options.except(:default))
|
||||
end
|
||||
self.attribute_types = attribute_types.merge(name => type)
|
||||
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
|
||||
define_attribute_methods(name)
|
||||
end
|
||||
|
||||
|
@ -37,11 +43,29 @@ module ActiveModel
|
|||
undef_method :__temp__#{safe_name}=
|
||||
STR
|
||||
end
|
||||
|
||||
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
||||
private_constant :NO_DEFAULT_PROVIDED
|
||||
|
||||
def define_default_attribute(name, value, type)
|
||||
self._default_attributes = _default_attributes.deep_dup
|
||||
if value == NO_DEFAULT_PROVIDED
|
||||
default_attribute = _default_attributes[name].with_type(type)
|
||||
else
|
||||
default_attribute = Attribute::UserProvidedDefault.new(
|
||||
name,
|
||||
value,
|
||||
type,
|
||||
_default_attributes.fetch(name.to_s) { nil },
|
||||
)
|
||||
end
|
||||
_default_attributes[name] = default_attribute
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(*)
|
||||
@attributes = self.class._default_attributes.deep_dup
|
||||
super
|
||||
clear_changes_information
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -53,21 +77,17 @@ module ActiveModel
|
|||
attr_name.to_s
|
||||
end
|
||||
|
||||
cast_type = self.class.attribute_types[name]
|
||||
|
||||
deserialized_value = ActiveModel::Type.lookup(cast_type).cast(value)
|
||||
attribute_will_change!(name) unless deserialized_value == attribute(name)
|
||||
instance_variable_set("@#{name}", deserialized_value)
|
||||
deserialized_value
|
||||
@attributes.write_from_user(attr_name.to_s, value)
|
||||
value
|
||||
end
|
||||
|
||||
def attribute(name)
|
||||
if instance_variable_defined?("@#{name}")
|
||||
instance_variable_get("@#{name}")
|
||||
def attribute(attr_name)
|
||||
name = if self.class.attribute_alias?(attr_name)
|
||||
self.class.attribute_alias(attr_name).to_s
|
||||
else
|
||||
default = self.class._default_attributes[name]
|
||||
default.respond_to?(:call) ? default.call : default
|
||||
attr_name.to_s
|
||||
end
|
||||
@attributes.fetch_value(name)
|
||||
end
|
||||
|
||||
# Handle *= for method_missing.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require "active_support/hash_with_indifferent_access"
|
||||
require "active_support/core_ext/object/duplicable"
|
||||
require "active_model/attribute_mutation_tracker"
|
||||
|
||||
module ActiveModel
|
||||
# == Active \Model \Dirty
|
||||
|
@ -130,6 +131,24 @@ module ActiveModel
|
|||
attribute_method_affix prefix: "restore_", suffix: "!"
|
||||
end
|
||||
|
||||
def initialize_dup(other) # :nodoc:
|
||||
super
|
||||
if self.class.respond_to?(:_default_attributes)
|
||||
@attributes = self.class._default_attributes.map do |attr|
|
||||
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
||||
end
|
||||
end
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
|
||||
def changes_applied # :nodoc:
|
||||
@previously_changed = changes
|
||||
@mutations_before_last_save = mutations_from_database
|
||||
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
||||
forget_attribute_assignments
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
|
||||
# Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
|
||||
#
|
||||
# person.changed? # => false
|
||||
|
@ -148,36 +167,6 @@ module ActiveModel
|
|||
changed_attributes.keys
|
||||
end
|
||||
|
||||
# Returns a hash of changed attributes indicating their original
|
||||
# and new values like <tt>attr => [original value, new value]</tt>.
|
||||
#
|
||||
# person.changes # => {}
|
||||
# person.name = 'bob'
|
||||
# person.changes # => { "name" => ["bill", "bob"] }
|
||||
def changes
|
||||
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
|
||||
end
|
||||
|
||||
# Returns a hash of attributes that were changed before the model was saved.
|
||||
#
|
||||
# person.name # => "bob"
|
||||
# person.name = 'robert'
|
||||
# person.save
|
||||
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
||||
def previous_changes
|
||||
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
|
||||
end
|
||||
|
||||
# Returns a hash of the attributes with unsaved changes indicating their original
|
||||
# values like <tt>attr => original value</tt>.
|
||||
#
|
||||
# person.name # => "bob"
|
||||
# person.name = 'robert'
|
||||
# person.changed_attributes # => {"name" => "bob"}
|
||||
def changed_attributes
|
||||
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
||||
end
|
||||
|
||||
# Handles <tt>*_changed?</tt> for +method_missing+.
|
||||
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
|
||||
!!changes_include?(attr) &&
|
||||
|
@ -200,11 +189,103 @@ module ActiveModel
|
|||
attributes.each { |attr| restore_attribute! attr }
|
||||
end
|
||||
|
||||
# Clears all dirty data: current changes and previous changes.
|
||||
def clear_changes_information
|
||||
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
||||
@mutations_before_last_save = nil
|
||||
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
||||
forget_attribute_assignments
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
|
||||
def clear_attribute_changes(attr_names)
|
||||
attributes_changed_by_setter.except!(*attr_names)
|
||||
attr_names.each do |attr_name|
|
||||
clear_attribute_change(attr_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a hash of the attributes with unsaved changes indicating their original
|
||||
# values like <tt>attr => original value</tt>.
|
||||
#
|
||||
# person.name # => "bob"
|
||||
# person.name = 'robert'
|
||||
# person.changed_attributes # => {"name" => "bob"}
|
||||
def changed_attributes
|
||||
# This should only be set by methods which will call changed_attributes
|
||||
# multiple times when it is known that the computed value cannot change.
|
||||
if defined?(@cached_changed_attributes)
|
||||
@cached_changed_attributes
|
||||
else
|
||||
attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a hash of changed attributes indicating their original
|
||||
# and new values like <tt>attr => [original value, new value]</tt>.
|
||||
#
|
||||
# person.changes # => {}
|
||||
# person.name = 'bob'
|
||||
# person.changes # => { "name" => ["bill", "bob"] }
|
||||
def changes
|
||||
cache_changed_attributes do
|
||||
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a hash of attributes that were changed before the model was saved.
|
||||
#
|
||||
# person.name # => "bob"
|
||||
# person.name = 'robert'
|
||||
# person.save
|
||||
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
||||
def previous_changes
|
||||
@previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
|
||||
@previously_changed.merge(mutations_before_last_save.changes)
|
||||
end
|
||||
|
||||
def attribute_changed_in_place?(attr_name) # :nodoc:
|
||||
mutations_from_database.changed_in_place?(attr_name)
|
||||
end
|
||||
|
||||
private
|
||||
def clear_attribute_change(attr_name)
|
||||
mutations_from_database.forget_change(attr_name)
|
||||
end
|
||||
|
||||
def mutations_from_database
|
||||
unless defined?(@mutations_from_database)
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
@mutations_from_database ||= if @attributes
|
||||
ActiveModel::AttributeMutationTracker.new(@attributes)
|
||||
else
|
||||
NullMutationTracker.instance
|
||||
end
|
||||
end
|
||||
|
||||
def forget_attribute_assignments
|
||||
@attributes = @attributes.map(&:forgetting_assignment) if @attributes
|
||||
end
|
||||
|
||||
def mutations_before_last_save
|
||||
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
|
||||
end
|
||||
|
||||
def cache_changed_attributes
|
||||
@cached_changed_attributes = changed_attributes
|
||||
yield
|
||||
ensure
|
||||
clear_changed_attributes_cache
|
||||
end
|
||||
|
||||
def clear_changed_attributes_cache
|
||||
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
||||
end
|
||||
|
||||
# Returns +true+ if attr_name is changed, +false+ otherwise.
|
||||
def changes_include?(attr_name)
|
||||
attributes_changed_by_setter.include?(attr_name)
|
||||
attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
|
||||
end
|
||||
alias attribute_changed_by_setter? changes_include?
|
||||
|
||||
|
@ -214,18 +295,6 @@ module ActiveModel
|
|||
previous_changes.include?(attr_name)
|
||||
end
|
||||
|
||||
# Removes current changes and makes them accessible through +previous_changes+.
|
||||
def changes_applied # :doc:
|
||||
@previously_changed = changes
|
||||
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
||||
end
|
||||
|
||||
# Clears all dirty data: current changes and previous changes.
|
||||
def clear_changes_information # :doc:
|
||||
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
||||
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
||||
end
|
||||
|
||||
# Handles <tt>*_change</tt> for +method_missing+.
|
||||
def attribute_change(attr)
|
||||
[changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
|
||||
|
@ -238,15 +307,16 @@ module ActiveModel
|
|||
|
||||
# Handles <tt>*_will_change!</tt> for +method_missing+.
|
||||
def attribute_will_change!(attr)
|
||||
return if attribute_changed?(attr)
|
||||
unless attribute_changed?(attr)
|
||||
begin
|
||||
value = _read_attribute(attr)
|
||||
value = value.duplicable? ? value.clone : value
|
||||
rescue TypeError, NoMethodError
|
||||
end
|
||||
|
||||
begin
|
||||
value = _read_attribute(attr)
|
||||
value = value.duplicable? ? value.clone : value
|
||||
rescue TypeError, NoMethodError
|
||||
set_attribute_was(attr, value)
|
||||
end
|
||||
|
||||
set_attribute_was(attr, value)
|
||||
mutations_from_database.force_change(attr)
|
||||
end
|
||||
|
||||
# Handles <tt>restore_*!</tt> for +method_missing+.
|
||||
|
@ -257,18 +327,13 @@ module ActiveModel
|
|||
end
|
||||
end
|
||||
|
||||
# This is necessary because `changed_attributes` might be overridden in
|
||||
# other implementations (e.g. in `ActiveRecord`)
|
||||
alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
|
||||
def attributes_changed_by_setter
|
||||
@attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
|
||||
end
|
||||
|
||||
# Force an attribute to have a particular "before" value
|
||||
def set_attribute_was(attr, old_value)
|
||||
attributes_changed_by_setter[attr] = old_value
|
||||
end
|
||||
|
||||
# Remove changes information for the provided attributes.
|
||||
def clear_attribute_changes(attributes) # :doc:
|
||||
attributes_changed_by_setter.except!(*attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,10 @@ module ActiveModel
|
|||
def lookup(*args, **kwargs) # :nodoc:
|
||||
registry.lookup(*args, **kwargs)
|
||||
end
|
||||
|
||||
def default_value # :nodoc:
|
||||
@default_value ||= Value.new
|
||||
end
|
||||
end
|
||||
|
||||
register(:big_integer, Type::BigInteger)
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require "cases/helper"
|
||||
|
||||
module ActiveRecord
|
||||
class AttributeSetTest < ActiveRecord::TestCase
|
||||
module ActiveModel
|
||||
class AttributeSetTest < ActiveModel::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")
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require "cases/helper"
|
||||
|
||||
module ActiveRecord
|
||||
class AttributeTest < ActiveRecord::TestCase
|
||||
module ActiveModel
|
||||
class AttributeTest < ActiveModel::TestCase
|
||||
setup do
|
||||
@type = Minitest::Mock.new
|
||||
end
|
|
@ -1,12 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "active_model/attributes"
|
||||
|
||||
class AttributesDirtyTest < ActiveModel::TestCase
|
||||
class DirtyModel
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
include ActiveModel::Dirty
|
||||
attribute :name, :string
|
||||
attribute :color, :string
|
||||
attribute :size, :integer
|
||||
|
@ -69,12 +69,10 @@ class AttributesDirtyTest < ActiveModel::TestCase
|
|||
end
|
||||
|
||||
test "attribute mutation" do
|
||||
@model.instance_variable_set("@name", "Yam".dup)
|
||||
@model.name = "Yam"
|
||||
@model.save
|
||||
assert !@model.name_changed?
|
||||
@model.name.replace("Hadad")
|
||||
assert !@model.name_changed?
|
||||
@model.name_will_change!
|
||||
@model.name.replace("Baal")
|
||||
assert @model.name_changed?
|
||||
end
|
||||
|
||||
|
@ -190,4 +188,18 @@ class AttributesDirtyTest < ActiveModel::TestCase
|
|||
assert_equal "Dmitry", @model.name
|
||||
assert_equal "White", @model.color
|
||||
end
|
||||
|
||||
test "changing the attribute reports a change only when the cast value changes" do
|
||||
@model.size = "2.3"
|
||||
@model.save
|
||||
@model.size = "2.1"
|
||||
|
||||
assert_equal false, @model.changed?
|
||||
|
||||
@model.size = "5.1"
|
||||
|
||||
assert_equal true, @model.changed?
|
||||
assert_equal true, @model.size_changed?
|
||||
assert_equal({ "size" => [2, 5] }, @model.changes)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "active_model/attributes"
|
||||
|
||||
module ActiveModel
|
||||
class AttributesTest < ActiveModel::TestCase
|
||||
|
@ -13,7 +12,7 @@ module ActiveModel
|
|||
attribute :string_field, :string
|
||||
attribute :decimal_field, :decimal
|
||||
attribute :string_with_default, :string, default: "default string"
|
||||
attribute :date_field, :string, default: -> { Date.new(2016, 1, 1) }
|
||||
attribute :date_field, :date, default: -> { Date.new(2016, 1, 1) }
|
||||
attribute :boolean_field, :boolean
|
||||
end
|
||||
|
||||
|
@ -48,31 +47,6 @@ module ActiveModel
|
|||
assert_equal true, data.boolean_field
|
||||
end
|
||||
|
||||
test "dirty" do
|
||||
data = ModelForAttributesTest.new(
|
||||
integer_field: "2.3",
|
||||
string_field: "Rails FTW",
|
||||
decimal_field: "12.3",
|
||||
boolean_field: "0"
|
||||
)
|
||||
|
||||
assert_equal false, data.changed?
|
||||
|
||||
data.integer_field = "2.1"
|
||||
|
||||
assert_equal false, data.changed?
|
||||
|
||||
data.string_with_default = "default string"
|
||||
|
||||
assert_equal false, data.changed?
|
||||
|
||||
data.integer_field = "5.1"
|
||||
|
||||
assert_equal true, data.changed?
|
||||
assert_equal true, data.integer_field_changed?
|
||||
assert_equal({ "integer_field" => [2, 5] }, data.changes)
|
||||
end
|
||||
|
||||
test "nonexistent attribute" do
|
||||
assert_raise ActiveModel::UnknownAttributeError do
|
||||
ModelForAttributesTest.new(nonexistent: "nonexistent")
|
||||
|
|
|
@ -219,4 +219,8 @@ class DirtyTest < ActiveModel::TestCase
|
|||
assert_equal "Dmitry", @model.name
|
||||
assert_equal "White", @model.color
|
||||
end
|
||||
|
||||
test "model can be dup-ed without Attributes" do
|
||||
assert @model.dup
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,14 +27,14 @@ require "active_support"
|
|||
require "active_support/rails"
|
||||
require "active_model"
|
||||
require "arel"
|
||||
require "yaml"
|
||||
|
||||
require "active_record/version"
|
||||
require "active_record/attribute_set"
|
||||
require "active_model/attribute_set"
|
||||
|
||||
module ActiveRecord
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Attribute
|
||||
autoload :Base
|
||||
autoload :Callbacks
|
||||
autoload :Core
|
||||
|
@ -181,3 +181,7 @@ end
|
|||
ActiveSupport.on_load(:i18n) do
|
||||
I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__)
|
||||
end
|
||||
|
||||
YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet"
|
||||
YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase"
|
||||
YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/attribute_accessors"
|
||||
require "active_record/attribute_mutation_tracker"
|
||||
|
||||
module ActiveRecord
|
||||
module AttributeMethods
|
||||
|
@ -33,65 +32,13 @@ module ActiveRecord
|
|||
# <tt>reload</tt> the record and clears changed attributes.
|
||||
def reload(*)
|
||||
super.tap do
|
||||
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
||||
@mutations_before_last_save = nil
|
||||
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
||||
@mutations_from_database = nil
|
||||
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_dup(other) # :nodoc:
|
||||
super
|
||||
@attributes = self.class._default_attributes.map do |attr|
|
||||
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
||||
end
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
|
||||
def changes_applied # :nodoc:
|
||||
@mutations_before_last_save = mutations_from_database
|
||||
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
||||
forget_attribute_assignments
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
|
||||
def clear_changes_information # :nodoc:
|
||||
@mutations_before_last_save = nil
|
||||
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
||||
forget_attribute_assignments
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
|
||||
def clear_attribute_changes(attr_names) # :nodoc:
|
||||
super
|
||||
attr_names.each do |attr_name|
|
||||
clear_attribute_change(attr_name)
|
||||
end
|
||||
end
|
||||
|
||||
def changed_attributes # :nodoc:
|
||||
# This should only be set by methods which will call changed_attributes
|
||||
# multiple times when it is known that the computed value cannot change.
|
||||
if defined?(@cached_changed_attributes)
|
||||
@cached_changed_attributes
|
||||
else
|
||||
super.reverse_merge(mutations_from_database.changed_values).freeze
|
||||
end
|
||||
end
|
||||
|
||||
def changes # :nodoc:
|
||||
cache_changed_attributes do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def previous_changes # :nodoc:
|
||||
mutations_before_last_save.changes
|
||||
end
|
||||
|
||||
def attribute_changed_in_place?(attr_name) # :nodoc:
|
||||
mutations_from_database.changed_in_place?(attr_name)
|
||||
end
|
||||
|
||||
# Did this attribute change when we last saved? This method can be invoked
|
||||
# as +saved_change_to_name?+ instead of <tt>saved_change_to_attribute?("name")</tt>.
|
||||
# Behaves similarly to +attribute_changed?+. This method is useful in
|
||||
|
@ -182,26 +129,6 @@ module ActiveRecord
|
|||
result
|
||||
end
|
||||
|
||||
def mutations_from_database
|
||||
unless defined?(@mutations_from_database)
|
||||
@mutations_from_database = nil
|
||||
end
|
||||
@mutations_from_database ||= AttributeMutationTracker.new(@attributes)
|
||||
end
|
||||
|
||||
def changes_include?(attr_name)
|
||||
super || mutations_from_database.changed?(attr_name)
|
||||
end
|
||||
|
||||
def clear_attribute_change(attr_name)
|
||||
mutations_from_database.forget_change(attr_name)
|
||||
end
|
||||
|
||||
def attribute_will_change!(attr_name)
|
||||
super
|
||||
mutations_from_database.force_change(attr_name)
|
||||
end
|
||||
|
||||
def _update_record(*)
|
||||
partial_writes? ? super(keys_for_partial_write) : super
|
||||
end
|
||||
|
@ -213,25 +140,6 @@ module ActiveRecord
|
|||
def keys_for_partial_write
|
||||
changed_attribute_names_to_save & self.class.column_names
|
||||
end
|
||||
|
||||
def forget_attribute_assignments
|
||||
@attributes = @attributes.map(&:forgetting_assignment)
|
||||
end
|
||||
|
||||
def mutations_before_last_save
|
||||
@mutations_before_last_save ||= NullMutationTracker.instance
|
||||
end
|
||||
|
||||
def cache_changed_attributes
|
||||
@cached_changed_attributes = changed_attributes
|
||||
yield
|
||||
ensure
|
||||
clear_changed_attributes_cache
|
||||
end
|
||||
|
||||
def clear_changed_attributes_cache
|
||||
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/attribute/user_provided_default"
|
||||
require "active_model/attribute/user_provided_default"
|
||||
|
||||
module ActiveRecord
|
||||
# See ActiveRecord::Attributes::ClassMethods for documentation
|
||||
|
@ -250,14 +250,14 @@ module ActiveRecord
|
|||
if value == NO_DEFAULT_PROVIDED
|
||||
default_attribute = _default_attributes[name].with_type(type)
|
||||
elsif from_user
|
||||
default_attribute = Attribute::UserProvidedDefault.new(
|
||||
default_attribute = ActiveModel::Attribute::UserProvidedDefault.new(
|
||||
name,
|
||||
value,
|
||||
type,
|
||||
_default_attributes.fetch(name.to_s) { nil },
|
||||
)
|
||||
else
|
||||
default_attribute = Attribute.from_database(name, value, type)
|
||||
default_attribute = ActiveModel::Attribute.from_database(name, value, type)
|
||||
end
|
||||
_default_attributes[name] = default_attribute
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module ActiveRecord
|
|||
case coder["active_record_yaml_version"]
|
||||
when 1, 2 then coder
|
||||
else
|
||||
if coder["attributes"].is_a?(AttributeSet)
|
||||
if coder["attributes"].is_a?(ActiveModel::AttributeSet)
|
||||
Rails420.convert(klass, coder)
|
||||
else
|
||||
Rails41.convert(klass, coder)
|
||||
|
|
|
@ -323,7 +323,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def attributes_builder # :nodoc:
|
||||
@attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name|
|
||||
@attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
|
||||
unless columns_hash.key?(name)
|
||||
_default_attributes[name].dup
|
||||
end
|
||||
|
@ -346,7 +346,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def yaml_encoder # :nodoc:
|
||||
@yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
|
||||
@yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
|
||||
end
|
||||
|
||||
# Returns the type of the attribute with the given name, after applying
|
||||
|
@ -376,7 +376,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def _default_attributes # :nodoc:
|
||||
@default_attributes ||= AttributeSet.new({})
|
||||
@default_attributes ||= ActiveModel::AttributeSet.new({})
|
||||
end
|
||||
|
||||
# Returns an array of column names as strings.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_record/attribute"
|
||||
require "active_model/attribute"
|
||||
|
||||
module ActiveRecord
|
||||
class Relation
|
||||
class QueryAttribute < Attribute # :nodoc:
|
||||
class QueryAttribute < ActiveModel::Attribute # :nodoc:
|
||||
def type_cast(value)
|
||||
value
|
||||
end
|
||||
|
|
|
@ -930,7 +930,7 @@ module ActiveRecord
|
|||
arel.where(where_clause.ast) unless where_clause.empty?
|
||||
arel.having(having_clause.ast) unless having_clause.empty?
|
||||
if limit_value
|
||||
limit_attribute = Attribute.with_cast_value(
|
||||
limit_attribute = ActiveModel::Attribute.with_cast_value(
|
||||
"LIMIT".freeze,
|
||||
connection.sanitize_limit(limit_value),
|
||||
Type.default_value,
|
||||
|
@ -938,7 +938,7 @@ module ActiveRecord
|
|||
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
||||
end
|
||||
if offset_value
|
||||
offset_attribute = Attribute.with_cast_value(
|
||||
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
||||
"OFFSET".freeze,
|
||||
offset_value.to_i,
|
||||
Type.default_value,
|
||||
|
|
Loading…
Reference in New Issue