diff --git a/lib/active_resource/base.rb b/lib/active_resource/base.rb
index 9e556b2014..d0dae3c484 100644
--- a/lib/active_resource/base.rb
+++ b/lib/active_resource/base.rb
@@ -77,6 +77,58 @@ module ActiveResource
# As you can see, these are very similar to Active Record's life cycle methods for database records.
# You can read more about each of these methods in their respective documentation.
#
+ # Active Resource objects provide out-of-the-box support for both JSON and XML
+ # formats. A resource object's format encodes attributes into request payloads and decodes response payloads
+ # into attributes.
+ #
+ # The default format is ActiveResource::Formats::JsonFormat. To change the
+ # format, configure the resource object class' +format+:
+ #
+ # Person.format = :json
+ # person = Person.find(1) # => GET /people/1.json
+ #
+ # person.encode
+ # # => "{\"person\":{\"id\":1,\"full_name\":\"First Last\"}}"
+ #
+ # Person.format.decode(person.encode)
+ # # => {"id"=>1, "full_name"=>"First Last"}
+ #
+ # Person.format = :xml
+ # person = Person.find(1) # => GET /people/1.xml
+ #
+ # person.encode
+ # # => "1First Last"
+ #
+ # Person.format.decode(person.encode)
+ # # => {"full_name"=>"First Last"}
+ #
+ # To customize how attributes are encoded and decoded, declare a format and override
+ # its +encode+ and +decode+ methods:
+ #
+ # module CamelcaseJsonFormat
+ # extend ActiveResource::Formats[:json]
+ #
+ # def self.encode(resource, options = nil)
+ # hash = resource.as_json(options)
+ # hash = hash.deep_transform_keys! { |key| key.camelcase(:lower) }
+ # super(hash)
+ # end
+ #
+ # def decode(json)
+ # hash = super
+ # hash.deep_transform_keys! { |key| key.underscore }
+ # end
+ # end
+ #
+ # Person.format = CamelcaseJsonFormat
+ #
+ # person = Person.new(first_name: "First", last_name: "Last")
+ # person.encode
+ # # => "{\"person\":{\"firstName\":\"First\",\"lastName\":\"Last\"}}"
+ #
+ # Person.format.decode(person.encode)
+ # # => {"first_name"=>"First", "last_name"=>"Last"}
+ #
# === Custom REST methods
#
# Since simple CRUD/life cycle methods can't accomplish every task, Active Resource also supports
@@ -1459,9 +1511,13 @@ def exists?
# Returns the serialized string representation of the resource in the configured
# serialization format specified in ActiveResource::Base.format. The options
- # applicable depend on the configured encoding format.
+ # applicable depend on the configured encoding format, and are forwarded to
+ # the corresponding serializer method.
+ #
+ # ActiveResource::Formats::JsonFormat delegates to Base#to_json and
+ # ActiveResource::Formats::XmlFormat delegates to Base#to_xml.
def encode(options = {})
- send("to_#{self.class.format.extension}", options)
+ self.class.format.encode(self, options)
end
# A method to \reload the attributes of this object from the remote web service.
diff --git a/lib/active_resource/formats/json_format.rb b/lib/active_resource/formats/json_format.rb
index 67795f95e3..3bbe8da34a 100644
--- a/lib/active_resource/formats/json_format.rb
+++ b/lib/active_resource/formats/json_format.rb
@@ -15,8 +15,8 @@ def mime_type
"application/json"
end
- def encode(hash, options = nil)
- ActiveSupport::JSON.encode(hash, options)
+ def encode(resource, options = nil)
+ resource.to_json(options)
end
def decode(json)
diff --git a/lib/active_resource/formats/xml_format.rb b/lib/active_resource/formats/xml_format.rb
index 5fbf967e08..f86ab56115 100644
--- a/lib/active_resource/formats/xml_format.rb
+++ b/lib/active_resource/formats/xml_format.rb
@@ -15,8 +15,8 @@ def mime_type
"application/xml"
end
- def encode(hash, options = {})
- hash.to_xml(options)
+ def encode(resource, options = {})
+ resource.to_xml(options)
end
def decode(xml)
diff --git a/test/cases/format_test.rb b/test/cases/format_test.rb
index 1d0ab4b358..66ece20cbf 100644
--- a/test/cases/format_test.rb
+++ b/test/cases/format_test.rb
@@ -108,6 +108,60 @@ def test_serialization_of_nested_resource
end
end
+ def test_custom_json_format
+ format_class = Class.new do
+ include ActiveResource::Formats[:json]
+
+ def initialize(encoder:, decoder:)
+ @encoder, @decoder = encoder, decoder
+ end
+
+ def encode(resource, options = nil)
+ hash = resource.as_json(options)
+ hash = hash.deep_transform_keys!(&@encoder)
+ super(hash)
+ end
+
+ def decode(json)
+ super.deep_transform_keys!(&@decoder)
+ end
+ end
+
+ format = format_class.new(encoder: ->(key) { key.camelcase(:lower) }, decoder: :underscore)
+
+ using_format(Person, format) do
+ person = Person.new(name: "Joe", likes_hats: true)
+ json = { person: { name: "Joe", likesHats: true } }.to_json
+
+ assert_equal person, Person.new(format.decode(json))
+ assert_equal person.encode, json
+ end
+ end
+
+ def test_custom_xml_format
+ format = Module.new do
+ extend self, ActiveResource::Formats[:xml]
+
+ def encode(value, options = {})
+ xml = value.serializable_hash(options)
+ xml.deep_transform_keys!(&:camelcase)
+ super(xml, root: value.class.element_name)
+ end
+
+ def decode(value)
+ super.deep_transform_keys!(&:underscore)
+ end
+ end
+
+ using_format(Person, format) do
+ person = Person.new(name: "Joe", likes_hats: true)
+ xml = { Name: "Joe", "LikesHats": true }.to_xml(root: "person")
+
+ assert_equal person, Person.new(format.decode(xml))
+ assert_equal person.encode, xml
+ end
+ end
+
def test_removing_root
matz = { name: "Matz" }
matz_with_root = { person: matz }