From c4c0b799e464942e26b928817a8cef5f8578c688 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 23 Feb 2023 16:57:22 +0100 Subject: [PATCH] Serialized attribute should be able to be defined in abstract classes Ref: https://github.com/rails/rails/pull/47191 This feature recently regressed when https://github.com/rails/rails/pull/47191 was merged to handle defaults. The `attribute` definition callback is set on the parent class and is triggered when the child class schema is loaded. I don't know if this is fixeable. --- .../attribute_methods/serialization.rb | 2 +- .../lib/active_record/type/serialized.rb | 5 +- .../test/cases/serialized_attribute_test.rb | 51 ++++--------------- 3 files changed, 12 insertions(+), 46 deletions(-) diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index bcba30dea2619..9a702816bfb4e 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -219,7 +219,7 @@ def serialize(attr_name, class_name_or_coder = nil, coder: nil, type: Object, ya end cast_type = cast_type.subtype if Type::Serialized === cast_type - Type::Serialized.new(cast_type, column_serializer, default: columns_hash[attr_name.to_s]&.default) + Type::Serialized.new(cast_type, column_serializer) end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 7608063cd51a7..f2d463e0e5021 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -9,10 +9,9 @@ class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc: attr_reader :subtype, :coder - def initialize(subtype, coder, default: nil) + def initialize(subtype, coder) @subtype = subtype @coder = coder - @default = default super(subtype) end @@ -26,7 +25,7 @@ def deserialize(value) def serialize(value) return if value.nil? - unless default_value?(value) && @default.nil? + unless default_value?(value) super coder.dump(value) end end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index b23e0fe9cd0b0..0372f6eded6f8 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -403,51 +403,18 @@ def test_values_cast_from_nil_are_persisted_as_nil assert_equal [topic, topic2], Topic.where(content: nil).sort_by(&:id) end - # MySQL doesn't support default values for text columns, so we need to skip this test for MySQL - if !current_adapter?(:Mysql2Adapter) - def test_serialized_attribute_with_default_can_update_to_default - @verbose_was = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - ActiveRecord::Schema.define do - create_table :tmp_posts, force: true do |t| - t.text :content, null: false, default: "{}" - end - end - klass = Class.new(ActiveRecord::Base) do - self.table_name = "tmp_posts" - serialize(:content, type: Hash) - end - - t = klass.create!(content: { "other_key" => "new_value" }) - assert_equal({ "other_key" => "new_value" }, t.content) - - t.update!(content: {}) - assert_equal({}, t.content) - ensure - ActiveRecord::Migration.verbose = @verbose_was + def test_serialized_attribute_can_be_defined_in_abstract_classes + klass = Class.new(ActiveRecord::Base) do + self.abstract_class = true + self.table_name = nil + serialize(:content, type: Hash) end - def test_nil_is_always_persisted_as_default - @verbose_was = ActiveRecord::Migration.verbose - ActiveRecord::Migration.verbose = false - - ActiveRecord::Schema.define do - create_table :tmp_posts, force: true do |t| - t.text :content, null: false, default: "{}" - end - end - klass = Class.new(ActiveRecord::Base) do - self.table_name = "tmp_posts" - serialize(:content, type: Hash) - end - - t = klass.create!(content: { foo: "bar" }) - t.update_attribute :content, nil - assert_equal({}, t.content) - ensure - ActiveRecord::Migration.verbose = @verbose_was + subclass = Class.new(klass) do + self.table_name = "posts" end + + subclass.define_attribute_methods end def test_nil_is_always_persisted_as_null