Skip to content

Commit

Permalink
Merge pull request #156 from bobbytables/entity-format-with
Browse files Browse the repository at this point in the history
Entity format with
  • Loading branch information
Michael Bleigh committed Apr 1, 2012
2 parents 31436a5 + e9ca055 commit ac00d2f
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 3 deletions.
60 changes: 60 additions & 0 deletions lib/grape/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def self.expose(*args, &block)
raise ArgumentError, "You may not use block-setting on multi-attribute exposures." if block_given?
end

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

options[:proc] = block if block_given?

args.each do |attribute|
Expand All @@ -91,6 +93,50 @@ def self.exposures
@exposures
end

# 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
#
#
#
# @example Formatter declaration
#
# module API
# module Entities
# class User < Grape::Entity
# format_with :timestamp do |date|
# date.strftime('%m/%d/%Y')
# end
#
# expose :birthday, :last_signed_in, :format_with => :timestamp
# end
# end
# end
#
# @example Formatters are available to all decendants
#
# Grape::Entity.format_with :timestamp do |date|
# date.strftime('%m/%d/%Y')
# end
#
def self.format_with(name, &block)
raise ArgumentError, "You must has a block for formatters" unless block_given?
formatters[name.to_sym] = block
end

# Returns a hash of all formatters that are registered for this and it's ancestors.
def self.formatters
@formatters ||= {}

if superclass.respond_to? :formatters
@formatters = superclass.formatters.merge(@formatters)
end

@formatters
end

# This allows you to set a root element name for your representation.
#
# @param plural [String] the root key to use when representing
Expand Down Expand Up @@ -171,6 +217,10 @@ def exposures
self.class.exposures
end

def formatters
self.class.formatters
end

# The serializable hash is the Entity's primary output. It is the transformed
# hash for the given data model and is used as the basis for serialization to
# JSON and other formats.
Expand Down Expand Up @@ -202,6 +252,16 @@ def value_for(attribute, options = {})
exposure_options[:proc].call(object, options)
elsif exposure_options[:using]
exposure_options[:using].represent(object.send(attribute), :root => nil)
elsif exposure_options[:format_with]
format_with = exposure_options[:format_with]

if format_with.is_a?(Symbol) && formatters[format_with]
formatters[format_with].call(object.send(attribute))
elsif format_with.is_a?(Symbol)
self.send(format_with, object.send(attribute))
elsif format_with.respond_to? :call
format_with.call(object.send(attribute))
end
else
object.send(attribute)
end
Expand Down
60 changes: 57 additions & 3 deletions spec/grape/entity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
expect{ subject.expose :name, :email, :as => :foo }.to raise_error(ArgumentError)
expect{ subject.expose :name, :as => :foo }.not_to raise_error
end

it 'should make sure that :format_with as a proc can not be used with a block' do
expect { subject.expose :name, :format_with => Proc.new {} do |object,options| end }.to raise_error(ArgumentError)
end
end

context 'with a block' do
Expand Down Expand Up @@ -67,6 +71,38 @@
child_class.exposures[:name].should have_key :proc
end
end

context 'register formatters' do
let(:date_formatter) { lambda {|date| date.strftime('%m/%d/%Y') }}

it 'should register a formatter' do
subject.format_with :timestamp, &date_formatter

subject.formatters[:timestamp].should_not be_nil
end

it 'should inherit formatters from ancestors' do
subject.format_with :timestamp, &date_formatter
child_class = Class.new(subject)

child_class.formatters.should == subject.formatters
end

it 'should not allow registering a formatter without a block' do
expect{ subject.format_with :foo }.to raise_error(ArgumentError)
end

it 'should format an exposure with a registered formatter' do
subject.format_with :timestamp do |date|
date.strftime('%m/%d/%Y')
end

subject.expose :birthday, :format_with => :timestamp

model = { :birthday => Time.new(2012, 2, 27) }
subject.new(mock(model)).as_json[:birthday].should == '02/27/2012'
end
end
end

describe '.represent' do
Expand Down Expand Up @@ -201,11 +237,13 @@
context 'instance methods' do
let(:model){ mock(attributes) }
let(:attributes){ {
:name => 'Bob Bobson',
:name => 'Bob Bobson',
:email => '[email protected]',
:birthday => Time.new(2012, 2, 27),
:fantasies => ['Unicorns', 'Double Rainbows', 'Nessy'],
:friends => [
mock(:name => "Friend 1", :email => '[email protected]', :friends => []),
mock(:name => "Friend 2", :email => '[email protected]', :friends => [])
mock(:name => "Friend 1", :email => '[email protected]', :fantasies => [], :birthday => Time.new(2012, 2, 27), :friends => []),
mock(:name => "Friend 2", :email => '[email protected]', :fantasies => [], :birthday => Time.new(2012, 2, 27), :friends => [])
]
} }
subject{ fresh_class.new(model) }
Expand All @@ -229,6 +267,14 @@
expose :computed do |object, options|
options[:awesome]
end

expose :birthday, :format_with => :timestamp

def timestamp(date)
date.strftime('%m/%d/%Y')
end

expose :fantasies, :format_with => lambda {|f| f.reverse }
end
end

Expand Down Expand Up @@ -261,6 +307,14 @@ class FriendEntity < Grape::Entity
it 'should call through to the proc if there is one' do
subject.send(:value_for, :computed, :awesome => 123).should == 123
end

it 'should return a formatted value if format_with is passed' do
subject.send(:value_for, :birthday).should == '02/27/2012'
end

it 'should return a formatted value if format_with is passed a lambda' do
subject.send(:value_for, :fantasies).should == ['Nessy', 'Double Rainbows', 'Unicorns']
end
end

describe '#key_for' do
Expand Down

0 comments on commit ac00d2f

Please sign in to comment.