diff --git a/Gemfile b/Gemfile
index c0a1fca09c..473d9b60fd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -8,6 +8,8 @@ branch = ENV.fetch("BRANCH", "main")
gem "activesupport", github: "rails/rails", branch: branch
gem "activemodel", github: "rails/rails", branch: branch
gem "activejob", github: "rails/rails", branch: branch
+gem "activerecord", github: "rails/rails", branch: branch
+gem "sqlite3", branch == "7-0-stable" ? "~> 1.4" : nil
gem "rubocop"
gem "rubocop-minitest"
diff --git a/lib/active_resource/coder.rb b/lib/active_resource/coder.rb
index 720ad8c66e..8cc8b2f224 100644
--- a/lib/active_resource/coder.rb
+++ b/lib/active_resource/coder.rb
@@ -9,16 +9,16 @@ module ActiveResource
# database. Decodes values read from the database into Active Resource
# instances.
#
- # class User < ActiveRecord::Base
- # serialize :person, coder: ActiveResource::Coder.new(Person)
- # end
- #
# class Person < ActiveResource::Base
# schema do
# attribute :name, :string
# end
# end
#
+ # class User < ActiveRecord::Base
+ # serialize :person, coder: Person
+ # end
+ #
# user = User.new
# user.person = Person.new name: "Matz"
# user.person.name # => "Matz"
@@ -37,8 +37,8 @@ module ActiveResource
#
# user.person_before_type_cast # => "{\"name\":\"Matz\"}"
#
- # To customize serialization, pass the method name or a block as the second
- # argument:
+ # To customize serialization, pass the method name or a block that accepts the
+ # instance as the second argument:
#
# person = Person.new name: "Matz"
#
@@ -50,6 +50,10 @@ module ActiveResource
class Coder
attr_accessor :resource_class, :encoder
+ # ==== Arguments
+ # * resource_class Active Resource class that to be coded
+ # * encoder_method the method to invoke on the instance to encode
+ # it. Defaults to ActiveResource::Base#encode.
def initialize(resource_class, encoder_method = :encode, &block)
@resource_class = resource_class
@encoder = block || encoder_method
@@ -70,6 +74,8 @@ def load(value)
return if value.nil?
value = resource_class.format.decode(value) if value.is_a?(String)
raise ArgumentError.new("expected value to be Hash, but was #{value.class}") unless value.is_a?(Hash)
+ value = Formats.remove_root(value) if value.keys.first.to_s == resource_class.element_name
+
resource_class.new(value, value[resource_class.primary_key])
end
end
diff --git a/lib/active_resource/serialization.rb b/lib/active_resource/serialization.rb
index 0f2dd46353..e42746c2c0 100644
--- a/lib/active_resource/serialization.rb
+++ b/lib/active_resource/serialization.rb
@@ -11,16 +11,16 @@ module ActiveResource
# database. Decodes strings read from the database into Active Resource
# instances.
#
- # class User < ActiveRecord::Base
- # serialize :person, coder: Person
- # end
- #
# class Person < ActiveResource::Base
# schema do
# attribute :name, :string
# end
# end
#
+ # class User < ActiveRecord::Base
+ # serialize :person, coder: Person
+ # end
+ #
# user = User.new
# user.person = Person.new name: "Matz"
#
@@ -57,6 +57,10 @@ module ActiveResource
# end
# end
#
+ # class User < ActiveRecord::Base
+ # serialize :person, coder: ActiveResource::Coder.new(Person, :serializable_hash)
+ # end
+ #
# user = User.new
# user.person = Person.new name: "Matz"
# user.person.name # => "Matz"
diff --git a/test/cases/base/serialization_test.rb b/test/cases/base/serialization_test.rb
index d60adf1565..4eecf1b3fc 100644
--- a/test/cases/base/serialization_test.rb
+++ b/test/cases/base/serialization_test.rb
@@ -3,7 +3,95 @@
require "abstract_unit"
require "fixtures/person"
+require "active_record"
+
+ENV["DATABASE_URL"] = "sqlite3::memory:"
+
+ActiveRecord::Base.establish_connection
+
+ActiveRecord::Schema.define do
+ create_table :users, force: true do |t|
+ t.text :person_text
+ t.json :person_json
+
+ t.check_constraint <<~SQL, name: "person_json_is_object"
+ JSON_TYPE(person_json) = 'object'
+ SQL
+ end
+
+ create_table :teams, force: true do |t|
+ t.text :people_text
+ t.text :paginated_people_text
+ t.json :people_json
+ t.json :paginated_people_json
+
+ t.check_constraint <<~SQL, name: "people_json_is_object"
+ JSON_TYPE(people_json) = 'array'
+ SQL
+ t.check_constraint <<~SQL, name: "paginated_people_json_is_object"
+ JSON_TYPE(paginated_people_json) = 'object'
+ SQL
+ end
+end
+
+class User < ActiveRecord::Base
+ if ActiveSupport::VERSION::MAJOR < 8 && ActiveSupport::VERSION::MINOR < 1
+ serialize :person_text, Person
+ serialize :person_json, ActiveResource::Coder.new(Person, :serializable_hash)
+ else
+ serialize :person_text, coder: Person
+ serialize :person_json, coder: ActiveResource::Coder.new(Person, :serializable_hash)
+ end
+end
+
class SerializationTest < ActiveSupport::TestCase
+ include ActiveRecord::TestFixtures
+
+ test "dumps to a text column" do
+ resource = Person.new({ id: 1, name: "Matz" }, true)
+
+ User.create!(person_text: resource)
+
+ user = User.sole
+ assert_equal resource.to_json, user.person_text_before_type_cast
+ end
+
+ test "dumps to a json column" do
+ resource = Person.new({ id: 1, name: "Matz" }, true)
+
+ User.create!(person_json: resource)
+
+ user = User.sole
+ assert_equal resource.serializable_hash.to_json, user.person_json_before_type_cast
+ end
+
+ test "loads from a text column" do
+ resource = Person.new(id: 1, name: "Matz")
+
+ User.connection.execute(<<~SQL)
+ INSERT INTO users(person_text)
+ VALUES ('#{resource.encode}')
+ SQL
+
+ user = User.sole
+ assert_predicate user.person_text, :persisted?
+ assert_equal resource, user.person_text
+ assert_equal resource.attributes, user.person_text.attributes
+ end
+
+ test "loads from a json column" do
+ resource = Person.new(id: 1, name: "Matz")
+
+ User.connection.execute(<<~SQL)
+ INSERT INTO users(person_json)
+ VALUES ('#{resource.encode}')
+ SQL
+
+ user = User.sole
+ assert_predicate user.person_json, :persisted?
+ assert_equal resource, user.person_json
+ assert_equal resource.attributes, user.person_json.attributes
+ end
test ".load delegates to the .coder" do
resource = Person.new(id: 1, name: "Matz")