Skip to content

Commit 7166363

Browse files
committed
Adds Entity DSL.
1 parent 3641744 commit 7166363

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed

README.markdown

+21
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,27 @@ module API
604604
end
605605
```
606606

607+
#### Using the Exposure DSL
608+
609+
Grape ships with a DSL to easily define entities within the context
610+
of an existing class:
611+
612+
```ruby
613+
class User
614+
include Grape::Entity::DSL
615+
616+
entity :name, :email do
617+
expose :advanced, if: :conditional
618+
end
619+
end
620+
```
621+
622+
The above will automatically create a `User::Entity` class and
623+
define properties on it according to the same rules as above. If
624+
you only want to define simple exposures you don't have to supply
625+
a block and can instead simply supply a list of comma-separated
626+
symbols.
627+
607628
### Using Entities
608629

609630
Once an entity is defined, it can be used within endpoints, by calling #present. The #present

lib/grape/entity.rb

+54
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,60 @@ module Grape
4343
class Entity
4444
attr_reader :object, :options
4545

46+
# The Entity DSL allows you to mix entity functionality into
47+
# your existing classes.
48+
module DSL
49+
def self.included(base)
50+
base.extend ClassMethods
51+
ancestor_entity_class = base.ancestors.detect{|a| a.entity_class if a.respond_to?(:entity_class)}
52+
const_set(:Entity, Class.new(ancestor_entity_class || Grape::Entity)) unless const_defined?(:Entity)
53+
end
54+
55+
module ClassMethods
56+
# Returns the automatically-created entity class for this
57+
# Class.
58+
def entity_class(search_ancestors=true)
59+
klass = const_get(:Entity) if const_defined?(:Entity)
60+
klass ||= ancestors.detect{|a| a.entity_class(false) if a.respond_to?(:entity_class) } if search_ancestors
61+
klass
62+
end
63+
64+
# Call this to make exposures to the entity for this Class.
65+
# Can be called with symbols for the attributes to expose,
66+
# a block that yields the full Entity DSL (See Grape::Entity),
67+
# or both.
68+
#
69+
# @example Symbols only.
70+
#
71+
# class User
72+
# include Grape::Entity::DSL
73+
#
74+
# entity :name, :email
75+
# end
76+
#
77+
# @example Mixed.
78+
#
79+
# class User
80+
# include Grape::Entity::DSL
81+
#
82+
# entity :name, :email do
83+
# expose :latest_status, using: Status::Entity, if: :include_status
84+
# expose :new_attribute, :if => {:version => 'v2'}
85+
# end
86+
# end
87+
def entity(*exposures, &block)
88+
entity_class.expose *exposures if exposures.any?
89+
entity_class.class_eval(&block) if block_given?
90+
entity_class
91+
end
92+
end
93+
94+
# Instantiates an entity version of this object.
95+
def entity
96+
self.class.entity_class.new(self)
97+
end
98+
end
99+
46100
# This method is the primary means by which you will declare what attributes
47101
# should be exposed by the entity.
48102
#

spec/grape/entity_spec.rb

+48
Original file line numberDiff line numberDiff line change
@@ -478,5 +478,53 @@ class FriendEntity < Grape::Entity
478478
subject.send(:conditions_met?, exposure_options, :true => true).should be_false
479479
end
480480
end
481+
482+
describe "::DSL" do
483+
subject{ Class.new }
484+
485+
it 'should create an Entity class when called' do
486+
subject.should_not be_const_defined(:Entity)
487+
subject.send(:include, Grape::Entity::DSL)
488+
subject.should be_const_defined(:Entity)
489+
end
490+
491+
context 'pre-mixed' do
492+
before{ subject.send(:include, Grape::Entity::DSL) }
493+
494+
it 'should be able to define entity traits through DSL' do
495+
subject.entity do
496+
expose :name
497+
end
498+
499+
subject.entity_class.exposures.should_not be_empty
500+
end
501+
502+
it 'should be able to expose straight from the class' do
503+
subject.entity :name, :email
504+
subject.entity_class.exposures.size.should == 2
505+
end
506+
507+
it 'should be able to mix field and advanced exposures' do
508+
subject.entity :name, :email do
509+
expose :third
510+
end
511+
subject.entity_class.exposures.size.should == 3
512+
end
513+
514+
context 'instance' do
515+
let(:instance){ subject.new }
516+
517+
describe '#entity' do
518+
it 'should be an instance of the entity class' do
519+
instance.entity.should be_kind_of(subject.entity_class)
520+
end
521+
522+
it 'should have an object of itself' do
523+
instance.entity.object.should == instance
524+
end
525+
end
526+
end
527+
end
528+
end
481529
end
482530
end

0 commit comments

Comments
 (0)