Skip to content
Open
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
60 changes: 58 additions & 2 deletions lib/active_resource/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
# # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?><person><id type=\"integer\">1</id><full-name>First Last</full-name></person>"
#
# 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
Comment on lines +108 to +121
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR makes this possible, but I still think this use case (snake_case to camelCase to snake_case) is common enough that it deserves a conventional solution.

I'm happy to explore this further in the future, but I just wanted to share my thoughts here for later.

#
# 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
Expand Down Expand Up @@ -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 <tt>Base#to_json</tt> and
# ActiveResource::Formats::XmlFormat delegates to <tt>Base#to_xml</tt>.
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.
Expand Down
4 changes: 2 additions & 2 deletions lib/active_resource/formats/json_format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions lib/active_resource/formats/xml_format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions test/cases/format_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down