Skip to content

added an example spec for hash delegates and an example implemantation #85

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

Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Next
====

* [#85](https://github.com/intridea/grape-entity/pull/85): Added `present_collection` to indicate that an `Entity` presents an entire Collection. Hashes can now be passed as object to be presented. The `Hash` keys can be referenced by expose. - [@dspaeth-faber](https://github.com/dspaeth-faber).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra period before the -! :)

* Your contribution here.

0.4.3 (2014-06-12)
Expand Down
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ Define a list of fields that will always be exposed.
expose :user_name, :ip
```

The field lookup takes several steps

* first try `entity-instance.exposure`
* next try `object.exposure`
* next try `object.fetch(exposure)`
* last raise an Exception

#### Exposing with a Presenter

Don't derive your model classes from `Grape::Entity`, expose them using a presenter.
Expand Down Expand Up @@ -119,6 +126,20 @@ root 'users', 'user'
expose :id, :name, ...
```

By default every object of a collection is wrapped into an instance of your `Entity` class.
You can override this behavior and wrapp the hole collection into one instance of your `Entity`
class.

As example:

```ruby

present_collection true, :collection_name # `collection_name` is optional and defaults to `items`
expose :collection_name, using: API:Items


```

#### Runtime Exposure

Use a block or a `Proc` to evaluate exposure at runtime. The supplied block or
Expand Down Expand Up @@ -151,6 +172,20 @@ private
end
```

You have always access to the presented instance with `object`

```ruby
class ExampleEntity < Grape::Entity
expose :formatted_value
# ...
private

def formatted_value
"+ X #{object.value}"
end
end
```

#### Aliases

Expose under a different name with `:as`.
Expand Down Expand Up @@ -216,8 +251,7 @@ The above will automatically create a `Status::Entity` class and define properti

### Using Entities

With Grape, once an entity is defined, it can be used within endpoints, by calling `present`. The `present` method accepts two arguments, the object to be presented and the options associated with it. The options hash must always include `:with`, which defines the entity to expose.

With Grape, once an entity is defined, it can be used within endpoints, by calling `present`. The `present` method accepts two arguments, the `object` to be presented and the `options` associated with it. The options hash must always include `:with`, which defines the entity to expose.
If the entity includes documentation it can be included in an endpoint's description.

```ruby
Expand Down
93 changes: 81 additions & 12 deletions lib/grape_entity/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,62 @@ def self.root(plural, singular = nil)
@root = singular
end

# This allows you to present a collection of objects.
#
# @param present_collection [true or false] when true all objects will be available as
# items in your presenter instead of wrapping each object in an instance of your presenter.
# When false (default) every object in a collection to present will be wrapped separately
# into an instance of your presenter.
# @param collection_name [Symbol] the name of the collection accessor in your entity object.
# Default :items
#
# @example Entity Definition
#
# module API
# module Entities
# class User < Grape::Entity
# expose :id
# end
#
# class Users < Grape::Entity
# present_collection true
# expose :items, as: 'users', using: API::Entities::Users
# expose :version, documentation: { type: 'string',
# desc: 'actual api version',
# required: true }
#
# def version
# options[:version]
# end
# end
# end
# end
#
# @example Usage in the API Layer
#
# module API
# class Users < Grape::API
# version 'v2'
#
# # this will render { "users" : [ { "id" : "1" }, { "id" : "2" } ], "version" : "v2" }
# get '/users' do
# @users = User.all
# present @users, with: API::Entities::Users
# end
#
# # this will render { "user" : { "id" : "1" } }
# get '/users/:id' do
# @user = User.find(params[:id])
# present @user, with: API::Entities::User
# end
# end
# end
#
def self.present_collection(present_collection = false, collection_name = :items)
@present_collection = present_collection
@collection_name = collection_name
end

# This convenience method allows you to instantiate one or more entities by
# passing either a singular or collection of objects. Each object will be
# initialized with the same options. If an array of objects is passed in,
Expand All @@ -339,27 +395,34 @@ def self.root(plural, singular = nil)
# @param options [Hash] Options that will be passed through to each entity
# representation.
#
# @option options :root [String] override the default root name set for the entity.
# @option options :root [String or false] override the default root name set for the entity.
# Pass nil or false to represent the object or objects with no root name
# even if one is defined for the entity.
# @option options :serializable [true or false] when true a serializable Hash will be returned
#
def self.represent(objects, options = {})
if objects.respond_to?(:to_ary)
inner = objects.to_ary.map { |object| new(object, { collection: true }.merge(options)) }
inner = inner.map(&:serializable_hash) if options[:serializable]
if objects.respond_to?(:to_ary) && ! @present_collection
root_element = @collection_root
inner = objects.to_ary.map { |object| new(object, { collection: true }.merge(options)).presented }
else
inner = new(objects, options)
inner = inner.serializable_hash if options[:serializable]
objects = { @collection_name => objects } if @present_collection
root_element = @root
inner = new(objects, options).presented
end

root_element = if options.key?(:root)
options[:root]
else
objects.respond_to?(:to_ary) ? @collection_root : @root
end
root_element = options[:root] if options.key?(:root)

root_element ? { root_element => inner } : inner
end

def presented
if options[:serializable]
serializable_hash
else
self
end
end

def initialize(object, options = {})
@object, @options = object, options
end
Expand Down Expand Up @@ -486,7 +549,13 @@ def delegate_attribute(attribute)
if respond_to?(name, true)
send(name)
else
object.send(name)
if object.respond_to?(name, true)
object.send(name)
elsif object.respond_to?(:fetch, true)
object.fetch(name)
else
raise ArgumentError
end
end
end

Expand Down
31 changes: 31 additions & 0 deletions spec/grape_entity/entity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,37 @@ class Parent < Person
representation = subject.represent(2.times.map { Object.new }, serializable: true)
representation.should == [{ awesome: true }, { awesome: true }]
end

it 'returns a serialized hash of a hash' do
subject.expose(:awesome)
representation = subject.represent({ awesome: true }, serializable: true)
representation.should == { awesome: true }
end
end

describe '.present_collection' do
it 'make the objects accessible' do
subject.present_collection true
subject.expose :items

representation = subject.represent(4.times.map { Object.new })
representation.should be_kind_of(subject)
representation.object.should be_kind_of(Hash)
representation.object.should have_key :items
representation.object[:items].should be_kind_of Array
representation.object[:items].size.should be 4
end

it 'serializes items with my root name' do
subject.present_collection true, :my_items
subject.expose :my_items

representation = subject.represent(4.times.map { Object.new }, serializable: true)
representation.should be_kind_of(Hash)
representation.should have_key :my_items
representation[:my_items].should be_kind_of Array
representation[:my_items].size.should be 4
end
end

describe '.root' do
Expand Down