Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
18 changes: 12 additions & 6 deletions lib/active_resource/coder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
#
Expand All @@ -50,6 +50,10 @@ module ActiveResource
class Coder
attr_accessor :resource_class, :encoder

# ==== Arguments
# * <tt>resource_class</tt> Active Resource class that to be coded
# * <tt>encoder_method</tt> 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
Expand All @@ -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
Expand Down
12 changes: 8 additions & 4 deletions lib/active_resource/serialization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
#
Expand Down Expand Up @@ -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"
Expand Down
88 changes: 88 additions & 0 deletions test/cases/base/serialization_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down