Skip to content

Commit f5be163

Browse files
committed
2 parents 1ef1a1f + 18419c4 commit f5be163

File tree

10 files changed

+96
-25
lines changed

10 files changed

+96
-25
lines changed

CHANGELOG.markdown

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
0.2.2
2-
=================
3-
* [#203](https://github.com/intridea/grape/pull/203): Added a check to Entity#serializable_hash that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).
1+
Next Release
2+
============
3+
4+
* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
5+
* [#203](https://github.com/intridea/grape/pull/203): Added a check to `Entity#serializable_hash` that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).
46

57
0.2.1 (7/11/2012)
68
=================

lib/grape/entity.rb

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ class Entity
6666
# will be called with the represented object as well as the
6767
# runtime options that were passed in. You can also just supply a
6868
# block to the expose call to achieve the same effect.
69-
# @option options :documentation Define documenation for an exposed
70-
# field, typically the value is a hash with two fields, type and desc.
69+
# @option options :documentation Define documenation for an exposed
70+
# field, typically the value is a hash with two fields, type and desc.
7171
def self.expose(*args, &block)
7272
options = args.last.is_a?(Hash) ? args.pop : {}
7373

@@ -98,13 +98,13 @@ def self.exposures
9898
@exposures
9999
end
100100

101-
# Returns a hash, the keys are symbolized references to fields in the entity,
102-
# the values are document keys in the entity's documentation key. When calling
101+
# Returns a hash, the keys are symbolized references to fields in the entity,
102+
# the values are document keys in the entity's documentation key. When calling
103103
# #docmentation, any exposure without a documentation key will be ignored.
104104
def self.documentation
105105
@documentation ||= exposures.inject({}) do |memo, value|
106106
unless value[1][:documentation].nil? || value[1][:documentation].empty?
107-
memo[value[0]] = value[1][:documentation]
107+
memo[value[0]] = value[1][:documentation]
108108
end
109109
memo
110110
end
@@ -118,7 +118,7 @@ def self.documentation
118118

119119
# This allows you to declare a Proc in which exposures can be formatted with.
120120
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
121-
#
121+
#
122122
# @param name [Symbol] the name of the formatter
123123
# @param block [Proc] the block that will interpret the exposed attribute
124124
#
@@ -260,9 +260,16 @@ def serializable_hash(runtime_options = {})
260260
opts = options.merge(runtime_options || {})
261261
exposures.inject({}) do |output, (attribute, exposure_options)|
262262
if object.respond_to?(attribute) && conditions_met?(exposure_options, opts)
263-
output[key_for(attribute)] = value_for(attribute, opts)
263+
partial_output = value_for(attribute, opts)
264+
output[key_for(attribute)] =
265+
if partial_output.respond_to? :serializable_hash
266+
partial_output.serializable_hash(runtime_options)
267+
elsif partial_output.kind_of?(Array) && !partial_output.map {|o| o.respond_to? :serializable_hash}.include?(false)
268+
partial_output.map {|o| o.serializable_hash}
269+
else
270+
partial_output
271+
end
264272
end
265-
266273
output
267274
end
268275
end

lib/grape/middleware/base.rb

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,32 @@ def decode_json(object)
110110
MultiJson.load(object)
111111
end
112112

113-
def encode_json(object)
114-
return object if object.is_a?(String)
113+
def serializable?(object)
114+
object.respond_to?(:serializable_hash) ||
115+
object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false) ||
116+
object.kind_of?(Hash)
117+
end
115118

119+
def serialize(object)
116120
if object.respond_to? :serializable_hash
117-
MultiJson.dump(object.serializable_hash)
121+
object.serializable_hash
118122
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
119-
MultiJson.dump(object.map {|o| o.serializable_hash })
120-
elsif object.respond_to? :to_json
121-
object.to_json
123+
object.map {|o| o.serializable_hash }
124+
elsif object.kind_of?(Hash)
125+
object.inject({}) { |h,(k,v)| h[k] = serialize(v); h }
122126
else
123-
MultiJson.dump(object)
127+
object
124128
end
125129
end
126130

131+
def encode_json(object)
132+
return object if object.is_a?(String)
133+
return MultiJson.dump(serialize(object)) if serializable?(object)
134+
return object.to_json if object.respond_to?(:to_json)
135+
136+
MultiJson.dump(object)
137+
end
138+
127139
def encode_txt(object)
128140
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
129141
end

spec/grape/entity_spec.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,17 +235,20 @@
235235
end
236236

237237
context 'instance methods' do
238+
238239
let(:model){ mock(attributes) }
240+
239241
let(:attributes){ {
240-
:name => 'Bob Bobson',
242+
:name => 'Bob Bobson',
241243
:email => '[email protected]',
242244
:birthday => Time.gm(2012, 2, 27),
243245
:fantasies => ['Unicorns', 'Double Rainbows', 'Nessy'],
244246
:friends => [
245-
mock(:name => "Friend 1", :email => '[email protected]', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []),
247+
mock(:name => "Friend 1", :email => '[email protected]', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []),
246248
mock(:name => "Friend 2", :email => '[email protected]', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => [])
247249
]
248250
} }
251+
249252
subject{ fresh_class.new(model) }
250253

251254
describe '#serializable_hash' do
@@ -282,6 +285,18 @@
282285
res.should_not have_key :non_existant_attribute
283286
res.should_not have_key :non_existant_attribute2
284287
end
288+
289+
it 'should serialize embedded objects which respond to #serializable_hash' do
290+
fresh_class.expose :name, :embedded
291+
presenter = fresh_class.new(EmbeddedExampleWithOne.new)
292+
presenter.serializable_hash.should == {:name => "abc", :embedded => {:abc => "def"}}
293+
end
294+
295+
it 'should serialize embedded arrays of objects which respond to #serializable_hash' do
296+
fresh_class.expose :name, :embedded
297+
presenter = fresh_class.new(EmbeddedExampleWithMany.new)
298+
presenter.serializable_hash.should == {:name => "abc", :embedded => [{:abc => "def"}, {:abc => "def"}]}
299+
end
285300
end
286301

287302
describe '#value_for' do
@@ -315,10 +330,6 @@ def timestamp(date)
315330
end
316331

317332
it 'should disable root key name for child representations' do
318-
class FriendEntity < Grape::Entity
319-
root 'friends', 'friend'
320-
expose :name, :email
321-
end
322333
fresh_class.class_eval do
323334
expose :friends, :using => FriendEntity
324335
end

spec/grape/middleware/formatter_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ def serializable_hash
4848
subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '{"abc":"def"}'}
4949
end
5050

51+
it 'should serialize objects that respond to #serializable_hash if there is a root element' do
52+
class SimpleExample
53+
def serializable_hash
54+
{:abc => 'def'}
55+
end
56+
end
57+
58+
@body = {"root" => SimpleExample.new}
59+
60+
subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '{"root":{"abc":"def"}}'}
61+
end
62+
5163
it 'should call #to_xml if the content type is xml' do
5264
@body = "string"
5365
@body.instance_eval do

spec/spec_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
require 'hashie/hash'
1919

20-
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
20+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |file|
2121
require file
2222
end
2323

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class EmbeddedExample
2+
def serializable_hash(opts = {})
3+
{ :abc => 'def' }
4+
end
5+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class EmbeddedExampleWithMany
2+
def name
3+
"abc"
4+
end
5+
6+
def embedded
7+
[ EmbeddedExample.new, EmbeddedExample.new ]
8+
end
9+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class EmbeddedExampleWithOne
2+
def name
3+
"abc"
4+
end
5+
6+
def embedded
7+
EmbeddedExample.new
8+
end
9+
end
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class FriendEntity < Grape::Entity
2+
root 'friends', 'friend'
3+
expose :name, :email
4+
end

0 commit comments

Comments
 (0)