Skip to content

Commit

Permalink
Merged #181.
Browse files Browse the repository at this point in the history
  • Loading branch information
dblock committed Jul 17, 2012
2 parents 1ef1a1f + 18419c4 commit f5be163
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 25 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.markdown
@@ -1,6 +1,8 @@
0.2.2
=================
* [#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).
Next Release
============

* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
* [#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).

0.2.1 (7/11/2012)
=================
Expand Down
23 changes: 15 additions & 8 deletions lib/grape/entity.rb
Expand Up @@ -66,8 +66,8 @@ class Entity
# will be called with the represented object as well as the
# runtime options that were passed in. You can also just supply a
# block to the expose call to achieve the same effect.
# @option options :documentation Define documenation for an exposed
# field, typically the value is a hash with two fields, type and desc.
# @option options :documentation Define documenation for an exposed
# field, typically the value is a hash with two fields, type and desc.
def self.expose(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}

Expand Down Expand Up @@ -98,13 +98,13 @@ def self.exposures
@exposures
end

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

# This allows you to declare a Proc in which exposures can be formatted with.
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
#
#
# @param name [Symbol] the name of the formatter
# @param block [Proc] the block that will interpret the exposed attribute
#
Expand Down Expand Up @@ -260,9 +260,16 @@ def serializable_hash(runtime_options = {})
opts = options.merge(runtime_options || {})
exposures.inject({}) do |output, (attribute, exposure_options)|
if object.respond_to?(attribute) && conditions_met?(exposure_options, opts)
output[key_for(attribute)] = value_for(attribute, opts)
partial_output = value_for(attribute, opts)
output[key_for(attribute)] =
if partial_output.respond_to? :serializable_hash
partial_output.serializable_hash(runtime_options)
elsif partial_output.kind_of?(Array) && !partial_output.map {|o| o.respond_to? :serializable_hash}.include?(false)
partial_output.map {|o| o.serializable_hash}
else
partial_output
end
end

output
end
end
Expand Down
26 changes: 19 additions & 7 deletions lib/grape/middleware/base.rb
Expand Up @@ -110,20 +110,32 @@ def decode_json(object)
MultiJson.load(object)
end

def encode_json(object)
return object if object.is_a?(String)
def serializable?(object)
object.respond_to?(:serializable_hash) ||
object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false) ||
object.kind_of?(Hash)
end

def serialize(object)
if object.respond_to? :serializable_hash
MultiJson.dump(object.serializable_hash)
object.serializable_hash
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
MultiJson.dump(object.map {|o| o.serializable_hash })
elsif object.respond_to? :to_json
object.to_json
object.map {|o| o.serializable_hash }
elsif object.kind_of?(Hash)
object.inject({}) { |h,(k,v)| h[k] = serialize(v); h }
else
MultiJson.dump(object)
object
end
end

def encode_json(object)
return object if object.is_a?(String)
return MultiJson.dump(serialize(object)) if serializable?(object)
return object.to_json if object.respond_to?(:to_json)

MultiJson.dump(object)
end

def encode_txt(object)
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
end
Expand Down
23 changes: 17 additions & 6 deletions spec/grape/entity_spec.rb
Expand Up @@ -235,17 +235,20 @@
end

context 'instance methods' do

let(:model){ mock(attributes) }

let(:attributes){ {
:name => 'Bob Bobson',
:name => 'Bob Bobson',
:email => 'bob@example.com',
:birthday => Time.gm(2012, 2, 27),
:fantasies => ['Unicorns', 'Double Rainbows', 'Nessy'],
:friends => [
mock(:name => "Friend 1", :email => 'friend1@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []),
mock(:name => "Friend 1", :email => 'friend1@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => []),
mock(:name => "Friend 2", :email => 'friend2@example.com', :fantasies => [], :birthday => Time.gm(2012, 2, 27), :friends => [])
]
} }

subject{ fresh_class.new(model) }

describe '#serializable_hash' do
Expand Down Expand Up @@ -282,6 +285,18 @@
res.should_not have_key :non_existant_attribute
res.should_not have_key :non_existant_attribute2
end

it 'should serialize embedded objects which respond to #serializable_hash' do
fresh_class.expose :name, :embedded
presenter = fresh_class.new(EmbeddedExampleWithOne.new)
presenter.serializable_hash.should == {:name => "abc", :embedded => {:abc => "def"}}
end

it 'should serialize embedded arrays of objects which respond to #serializable_hash' do
fresh_class.expose :name, :embedded
presenter = fresh_class.new(EmbeddedExampleWithMany.new)
presenter.serializable_hash.should == {:name => "abc", :embedded => [{:abc => "def"}, {:abc => "def"}]}
end
end

describe '#value_for' do
Expand Down Expand Up @@ -315,10 +330,6 @@ def timestamp(date)
end

it 'should disable root key name for child representations' do
class FriendEntity < Grape::Entity
root 'friends', 'friend'
expose :name, :email
end
fresh_class.class_eval do
expose :friends, :using => FriendEntity
end
Expand Down
12 changes: 12 additions & 0 deletions spec/grape/middleware/formatter_spec.rb
Expand Up @@ -48,6 +48,18 @@ def serializable_hash
subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '{"abc":"def"}'}
end

it 'should serialize objects that respond to #serializable_hash if there is a root element' do
class SimpleExample
def serializable_hash
{:abc => 'def'}
end
end

@body = {"root" => SimpleExample.new}

subject.call({'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json'}).last.each{|b| b.should == '{"root":{"abc":"def"}}'}
end

it 'should call #to_xml if the content type is xml' do
@body = "string"
@body.instance_eval do
Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Expand Up @@ -17,7 +17,7 @@

require 'hashie/hash'

Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each do |file|
require file
end

Expand Down
5 changes: 5 additions & 0 deletions spec/support/examples/embedded_example.rb
@@ -0,0 +1,5 @@
class EmbeddedExample
def serializable_hash(opts = {})
{ :abc => 'def' }
end
end
9 changes: 9 additions & 0 deletions spec/support/examples/embedded_example_with_many.rb
@@ -0,0 +1,9 @@
class EmbeddedExampleWithMany
def name
"abc"
end

def embedded
[ EmbeddedExample.new, EmbeddedExample.new ]
end
end
9 changes: 9 additions & 0 deletions spec/support/examples/embedded_example_with_one.rb
@@ -0,0 +1,9 @@
class EmbeddedExampleWithOne
def name
"abc"
end

def embedded
EmbeddedExample.new
end
end
4 changes: 4 additions & 0 deletions spec/support/examples/friend_entity.rb
@@ -0,0 +1,4 @@
class FriendEntity < Grape::Entity
root 'friends', 'friend'
expose :name, :email
end

0 comments on commit f5be163

Please sign in to comment.