Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entity format with #156

Merged
merged 7 commits into from
Apr 1, 2012
Merged
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: 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