From 716636362726fe03f62f5b5da9c57a9c9915c6fd Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 28 Aug 2012 15:00:49 -0700 Subject: [PATCH 1/2] Adds Entity DSL. --- README.markdown | 21 +++++++++++++++ lib/grape/entity.rb | 54 +++++++++++++++++++++++++++++++++++++++ spec/grape/entity_spec.rb | 48 ++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/README.markdown b/README.markdown index 16681775fd..d319c05c0c 100644 --- a/README.markdown +++ b/README.markdown @@ -604,6 +604,27 @@ module API end ``` +#### Using the Exposure DSL + +Grape ships with a DSL to easily define entities within the context +of an existing class: + +```ruby +class User + include Grape::Entity::DSL + + entity :name, :email do + expose :advanced, if: :conditional + end +end +``` + +The above will automatically create a `User::Entity` class and +define properties on it according to the same rules as above. If +you only want to define simple exposures you don't have to supply +a block and can instead simply supply a list of comma-separated +symbols. + ### Using Entities Once an entity is defined, it can be used within endpoints, by calling #present. The #present diff --git a/lib/grape/entity.rb b/lib/grape/entity.rb index 695d72a5fe..b8a72b1790 100644 --- a/lib/grape/entity.rb +++ b/lib/grape/entity.rb @@ -43,6 +43,60 @@ module Grape class Entity attr_reader :object, :options + # The Entity DSL allows you to mix entity functionality into + # your existing classes. + module DSL + def self.included(base) + base.extend ClassMethods + ancestor_entity_class = base.ancestors.detect{|a| a.entity_class if a.respond_to?(:entity_class)} + const_set(:Entity, Class.new(ancestor_entity_class || Grape::Entity)) unless const_defined?(:Entity) + end + + module ClassMethods + # Returns the automatically-created entity class for this + # Class. + def entity_class(search_ancestors=true) + klass = const_get(:Entity) if const_defined?(:Entity) + klass ||= ancestors.detect{|a| a.entity_class(false) if a.respond_to?(:entity_class) } if search_ancestors + klass + end + + # Call this to make exposures to the entity for this Class. + # Can be called with symbols for the attributes to expose, + # a block that yields the full Entity DSL (See Grape::Entity), + # or both. + # + # @example Symbols only. + # + # class User + # include Grape::Entity::DSL + # + # entity :name, :email + # end + # + # @example Mixed. + # + # class User + # include Grape::Entity::DSL + # + # entity :name, :email do + # expose :latest_status, using: Status::Entity, if: :include_status + # expose :new_attribute, :if => {:version => 'v2'} + # end + # end + def entity(*exposures, &block) + entity_class.expose *exposures if exposures.any? + entity_class.class_eval(&block) if block_given? + entity_class + end + end + + # Instantiates an entity version of this object. + def entity + self.class.entity_class.new(self) + end + end + # This method is the primary means by which you will declare what attributes # should be exposed by the entity. # diff --git a/spec/grape/entity_spec.rb b/spec/grape/entity_spec.rb index b0d4eb6430..198e3710e0 100644 --- a/spec/grape/entity_spec.rb +++ b/spec/grape/entity_spec.rb @@ -478,5 +478,53 @@ class FriendEntity < Grape::Entity subject.send(:conditions_met?, exposure_options, :true => true).should be_false end end + + describe "::DSL" do + subject{ Class.new } + + it 'should create an Entity class when called' do + subject.should_not be_const_defined(:Entity) + subject.send(:include, Grape::Entity::DSL) + subject.should be_const_defined(:Entity) + end + + context 'pre-mixed' do + before{ subject.send(:include, Grape::Entity::DSL) } + + it 'should be able to define entity traits through DSL' do + subject.entity do + expose :name + end + + subject.entity_class.exposures.should_not be_empty + end + + it 'should be able to expose straight from the class' do + subject.entity :name, :email + subject.entity_class.exposures.size.should == 2 + end + + it 'should be able to mix field and advanced exposures' do + subject.entity :name, :email do + expose :third + end + subject.entity_class.exposures.size.should == 3 + end + + context 'instance' do + let(:instance){ subject.new } + + describe '#entity' do + it 'should be an instance of the entity class' do + instance.entity.should be_kind_of(subject.entity_class) + end + + it 'should have an object of itself' do + instance.entity.object.should == instance + end + end + end + end + end end end From 1b9217d84819d4c328a36bb6e457211b6aa50af6 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 28 Aug 2012 15:06:28 -0700 Subject: [PATCH 2/2] Trying to fix 1.8 bug. --- lib/grape/entity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/entity.rb b/lib/grape/entity.rb index b8a72b1790..72c0cf9e70 100644 --- a/lib/grape/entity.rb +++ b/lib/grape/entity.rb @@ -49,7 +49,7 @@ module DSL def self.included(base) base.extend ClassMethods ancestor_entity_class = base.ancestors.detect{|a| a.entity_class if a.respond_to?(:entity_class)} - const_set(:Entity, Class.new(ancestor_entity_class || Grape::Entity)) unless const_defined?(:Entity) + base.const_set(:Entity, Class.new(ancestor_entity_class || Grape::Entity)) unless const_defined?(:Entity) end module ClassMethods