Skip to content

Commit ac00d2f

Browse files
author
Michael Bleigh
committed
Merge pull request #156 from bobbytables/entity-format-with
Entity format with
2 parents 31436a5 + e9ca055 commit ac00d2f

File tree

2 files changed

+117
-3
lines changed

2 files changed

+117
-3
lines changed

lib/grape/entity.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def self.expose(*args, &block)
7171
raise ArgumentError, "You may not use block-setting on multi-attribute exposures." if block_given?
7272
end
7373

74+
raise ArgumentError, "You may not use block-setting when also using " if block_given? && options[:format_with].respond_to?(:call)
75+
7476
options[:proc] = block if block_given?
7577

7678
args.each do |attribute|
@@ -91,6 +93,50 @@ def self.exposures
9193
@exposures
9294
end
9395

96+
# This allows you to declare a Proc in which exposures can be formatted with.
97+
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
98+
#
99+
# @param name [Symbol] the name of the formatter
100+
# @param block [Proc] the block that will interpret the exposed attribute
101+
#
102+
#
103+
#
104+
# @example Formatter declaration
105+
#
106+
# module API
107+
# module Entities
108+
# class User < Grape::Entity
109+
# format_with :timestamp do |date|
110+
# date.strftime('%m/%d/%Y')
111+
# end
112+
#
113+
# expose :birthday, :last_signed_in, :format_with => :timestamp
114+
# end
115+
# end
116+
# end
117+
#
118+
# @example Formatters are available to all decendants
119+
#
120+
# Grape::Entity.format_with :timestamp do |date|
121+
# date.strftime('%m/%d/%Y')
122+
# end
123+
#
124+
def self.format_with(name, &block)
125+
raise ArgumentError, "You must has a block for formatters" unless block_given?
126+
formatters[name.to_sym] = block
127+
end
128+
129+
# Returns a hash of all formatters that are registered for this and it's ancestors.
130+
def self.formatters
131+
@formatters ||= {}
132+
133+
if superclass.respond_to? :formatters
134+
@formatters = superclass.formatters.merge(@formatters)
135+
end
136+
137+
@formatters
138+
end
139+
94140
# This allows you to set a root element name for your representation.
95141
#
96142
# @param plural [String] the root key to use when representing
@@ -171,6 +217,10 @@ def exposures
171217
self.class.exposures
172218
end
173219

220+
def formatters
221+
self.class.formatters
222+
end
223+
174224
# The serializable hash is the Entity's primary output. It is the transformed
175225
# hash for the given data model and is used as the basis for serialization to
176226
# JSON and other formats.
@@ -202,6 +252,16 @@ def value_for(attribute, options = {})
202252
exposure_options[:proc].call(object, options)
203253
elsif exposure_options[:using]
204254
exposure_options[:using].represent(object.send(attribute), :root => nil)
255+
elsif exposure_options[:format_with]
256+
format_with = exposure_options[:format_with]
257+
258+
if format_with.is_a?(Symbol) && formatters[format_with]
259+
formatters[format_with].call(object.send(attribute))
260+
elsif format_with.is_a?(Symbol)
261+
self.send(format_with, object.send(attribute))
262+
elsif format_with.respond_to? :call
263+
format_with.call(object.send(attribute))
264+
end
205265
else
206266
object.send(attribute)
207267
end

spec/grape/entity_spec.rb

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
expect{ subject.expose :name, :email, :as => :foo }.to raise_error(ArgumentError)
2525
expect{ subject.expose :name, :as => :foo }.not_to raise_error
2626
end
27+
28+
it 'should make sure that :format_with as a proc can not be used with a block' do
29+
expect { subject.expose :name, :format_with => Proc.new {} do |object,options| end }.to raise_error(ArgumentError)
30+
end
2731
end
2832

2933
context 'with a block' do
@@ -67,6 +71,38 @@
6771
child_class.exposures[:name].should have_key :proc
6872
end
6973
end
74+
75+
context 'register formatters' do
76+
let(:date_formatter) { lambda {|date| date.strftime('%m/%d/%Y') }}
77+
78+
it 'should register a formatter' do
79+
subject.format_with :timestamp, &date_formatter
80+
81+
subject.formatters[:timestamp].should_not be_nil
82+
end
83+
84+
it 'should inherit formatters from ancestors' do
85+
subject.format_with :timestamp, &date_formatter
86+
child_class = Class.new(subject)
87+
88+
child_class.formatters.should == subject.formatters
89+
end
90+
91+
it 'should not allow registering a formatter without a block' do
92+
expect{ subject.format_with :foo }.to raise_error(ArgumentError)
93+
end
94+
95+
it 'should format an exposure with a registered formatter' do
96+
subject.format_with :timestamp do |date|
97+
date.strftime('%m/%d/%Y')
98+
end
99+
100+
subject.expose :birthday, :format_with => :timestamp
101+
102+
model = { :birthday => Time.new(2012, 2, 27) }
103+
subject.new(mock(model)).as_json[:birthday].should == '02/27/2012'
104+
end
105+
end
70106
end
71107

72108
describe '.represent' do
@@ -201,11 +237,13 @@
201237
context 'instance methods' do
202238
let(:model){ mock(attributes) }
203239
let(:attributes){ {
204-
:name => 'Bob Bobson',
240+
:name => 'Bob Bobson',
205241
:email => '[email protected]',
242+
:birthday => Time.new(2012, 2, 27),
243+
:fantasies => ['Unicorns', 'Double Rainbows', 'Nessy'],
206244
:friends => [
207-
mock(:name => "Friend 1", :email => '[email protected]', :friends => []),
208-
mock(:name => "Friend 2", :email => '[email protected]', :friends => [])
245+
mock(:name => "Friend 1", :email => '[email protected]', :fantasies => [], :birthday => Time.new(2012, 2, 27), :friends => []),
246+
mock(:name => "Friend 2", :email => '[email protected]', :fantasies => [], :birthday => Time.new(2012, 2, 27), :friends => [])
209247
]
210248
} }
211249
subject{ fresh_class.new(model) }
@@ -229,6 +267,14 @@
229267
expose :computed do |object, options|
230268
options[:awesome]
231269
end
270+
271+
expose :birthday, :format_with => :timestamp
272+
273+
def timestamp(date)
274+
date.strftime('%m/%d/%Y')
275+
end
276+
277+
expose :fantasies, :format_with => lambda {|f| f.reverse }
232278
end
233279
end
234280

@@ -261,6 +307,14 @@ class FriendEntity < Grape::Entity
261307
it 'should call through to the proc if there is one' do
262308
subject.send(:value_for, :computed, :awesome => 123).should == 123
263309
end
310+
311+
it 'should return a formatted value if format_with is passed' do
312+
subject.send(:value_for, :birthday).should == '02/27/2012'
313+
end
314+
315+
it 'should return a formatted value if format_with is passed a lambda' do
316+
subject.send(:value_for, :fantasies).should == ['Nessy', 'Double Rainbows', 'Unicorns']
317+
end
264318
end
265319

266320
describe '#key_for' do

0 commit comments

Comments
 (0)