diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index a97d7989be..faecd6b96f 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -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" diff --git a/activerecord/lib/active_record/attribute.rb b/activemodel/lib/active_model/attribute.rb similarity index 97% rename from activerecord/lib/active_record/attribute.rb rename to activemodel/lib/active_model/attribute.rb index fc474edc15..43130c37c5 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activemodel/lib/active_model/attribute.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activemodel/lib/active_model/attribute/user_provided_default.rb similarity index 78% rename from activerecord/lib/active_record/attribute/user_provided_default.rb rename to activemodel/lib/active_model/attribute/user_provided_default.rb index f746960fac..f274b687d4 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activemodel/lib/active_model/attribute/user_provided_default.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activemodel/lib/active_model/attribute_mutation_tracker.rb similarity index 97% rename from activerecord/lib/active_record/attribute_mutation_tracker.rb rename to activemodel/lib/active_model/attribute_mutation_tracker.rb index 94bf641a5d..9072460124 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activemodel/lib/active_model/attribute_mutation_tracker.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute_set.rb b/activemodel/lib/active_model/attribute_set.rb similarity index 95% rename from activerecord/lib/active_record/attribute_set.rb rename to activemodel/lib/active_model/attribute_set.rb index 9785666e77..a892accbc6 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activemodel/lib/active_model/attribute_set.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activemodel/lib/active_model/attribute_set/builder.rb similarity index 94% rename from activerecord/lib/active_record/attribute_set/builder.rb rename to activemodel/lib/active_model/attribute_set/builder.rb index 349cc7e403..f94f47370f 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activemodel/lib/active_model/attribute_set/builder.rb @@ -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 diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb similarity index 80% rename from activerecord/lib/active_record/attribute_set/yaml_encoder.rb rename to activemodel/lib/active_model/attribute_set/yaml_encoder.rb index 9254ce16ab..4ea945b956 100644 --- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb +++ b/activemodel/lib/active_model/attribute_set/yaml_encoder.rb @@ -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 diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb index 3e34d3b83a..13cad87875 100644 --- a/activemodel/lib/active_model/attributes.rb +++ b/activemodel/lib/active_model/attributes.rb @@ -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. diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 943db0ab52..ddd93e34a6 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -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 attr => [original value, new value]. - # - # 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 attr => original value. - # - # person.name # => "bob" - # person.name = 'robert' - # person.changed_attributes # => {"name" => "bob"} - def changed_attributes - @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new - end - # Handles *_changed? 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 attr => original value. + # + # 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 attr => [original value, new value]. + # + # 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 *_change for +method_missing+. def attribute_change(attr) [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr) @@ -238,15 +307,16 @@ module ActiveModel # Handles *_will_change! 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 restore_*! 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 diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb index b0ed67f28e..39324999c9 100644 --- a/activemodel/lib/active_model/type.rb +++ b/activemodel/lib/active_model/type.rb @@ -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) diff --git a/activerecord/test/cases/attribute_set_test.rb b/activemodel/test/cases/attribute_set_test.rb similarity index 99% rename from activerecord/test/cases/attribute_set_test.rb rename to activemodel/test/cases/attribute_set_test.rb index 8be77ed88f..d50e6cfa7a 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activemodel/test/cases/attribute_set_test.rb @@ -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") diff --git a/activerecord/test/cases/attribute_test.rb b/activemodel/test/cases/attribute_test.rb similarity index 99% rename from activerecord/test/cases/attribute_test.rb rename to activemodel/test/cases/attribute_test.rb index 1731e7926e..14d86cef97 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activemodel/test/cases/attribute_test.rb @@ -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 diff --git a/activemodel/test/cases/attributes_dirty_test.rb b/activemodel/test/cases/attributes_dirty_test.rb index 26b0e85db3..83a86371e0 100644 --- a/activemodel/test/cases/attributes_dirty_test.rb +++ b/activemodel/test/cases/attributes_dirty_test.rb @@ -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 diff --git a/activemodel/test/cases/attributes_test.rb b/activemodel/test/cases/attributes_test.rb index 064cba40e3..914aee1ac0 100644 --- a/activemodel/test/cases/attributes_test.rb +++ b/activemodel/test/cases/attributes_test.rb @@ -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") diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 2cd9e185e6..dfe041ff50 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -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 diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index bf6dfd46e1..0e036f05f5 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -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" diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 79110d04f4..3de6fe566d 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -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 # reload 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 saved_change_to_attribute?("name"). # 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 diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 9224d58928..0b7c9398a8 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -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 diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb index 23644aab8f..ffa095dd94 100644 --- a/activerecord/lib/active_record/legacy_yaml_adapter.rb +++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb @@ -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) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index bed9400f51..12ee4a4137 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -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. diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index fad08e2613..3532f28858 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -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 diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 897ff5c8af..9c5be4ad9b 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -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,