grape-entity 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +69 -0
- data/.travis.yml +5 -6
- data/CHANGELOG.md +35 -0
- data/Gemfile +7 -0
- data/Guardfile +0 -1
- data/README.md +320 -0
- data/Rakefile +3 -33
- data/lib/grape-entity.rb +1 -1
- data/lib/grape_entity.rb +2 -5
- data/lib/grape_entity/entity.rb +190 -71
- data/lib/grape_entity/version.rb +1 -1
- data/spec/grape_entity/entity_spec.rb +539 -158
- metadata +6 -5
- data/CHANGELOG.markdown +0 -21
- data/README.markdown +0 -197
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35f4a45cc741d725e9cc8d180ae9db0f9123cb7a
|
4
|
+
data.tar.gz: a452232a49b72773f38710733c24104e113e7043
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aeaab5ee1c623a0f4359a4b5378b3536afefd0a23009007a5c0a91fb4086658da6abd53458ea85bcd724dea448b0598213160bf74b58084e4ee68a840e0df511
|
7
|
+
data.tar.gz: 6f187c4bb82bea91a739fb3dc8c48d04985b3b72d09e29e91e6565b42ceecb281275d3c973eb3062b25263a997955dcece3efa7fe297ca1dd1e209e7b18dc3ea
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
AllCops:
|
2
|
+
Excludes:
|
3
|
+
- vendor/**
|
4
|
+
|
5
|
+
LineLength:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
MethodLength:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
ClassLength:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Documentation:
|
15
|
+
# don't require classes to be documented
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
CollectionMethods:
|
19
|
+
# don't prefer map to collect, recuce to inject
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Encoding:
|
23
|
+
# no need to always specify encoding
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
HashMethods:
|
27
|
+
# key? instead of has_key?
|
28
|
+
# value? instead of has_value?
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
StringLiterals:
|
32
|
+
# use single or double-quoted strings, as you please
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Void:
|
36
|
+
# == operator used in void context in specs
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
SignalException:
|
40
|
+
# prefer raise to fail
|
41
|
+
EnforcedStyle: only_raise
|
42
|
+
|
43
|
+
RaiseArgs:
|
44
|
+
# don't care for what kind of raise
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
PerlBackrefs:
|
48
|
+
# TODO: regular expression matching with $1, $2, etc.
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
BlockNesting:
|
52
|
+
# TODO: fix too much nesting
|
53
|
+
Max: 4
|
54
|
+
|
55
|
+
Lambda:
|
56
|
+
# TODO: replace all lambda with -> or Proc
|
57
|
+
Enabled: false
|
58
|
+
|
59
|
+
Blocks:
|
60
|
+
# allow multi-line blocks like expect { }
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
WordArray:
|
64
|
+
# %w vs. [ '', ... ]
|
65
|
+
Enabled: false
|
66
|
+
|
67
|
+
CyclomaticComplexity:
|
68
|
+
Enabled: false
|
69
|
+
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
Next Release
|
2
|
+
============
|
3
|
+
* Ruby 1.8.x is no longer supported - [@dblock](https://github.com/dblock).
|
4
|
+
* [#36](https://github.com/intridea/grape-entity/pull/36): Enforcing Ruby style guidelines via Rubocop - [@dblock](https://github.com/dblock).
|
5
|
+
* [#7](https://github.com/intridea/grape-entity/issues/7): Added `serializable` option to `represent` - [@mbleigh](https://github.com/mbleigh).
|
6
|
+
* [#18](https://github.com/intridea/grape-entity/pull/18): Added `safe` option to `expose`, will not raise error for a missing attribute - [@fixme](https://github.com/fixme).
|
7
|
+
* [#16](https://github.com/intridea/grape-entity/pull/16): Added `using` option to `expose SYMBOL BLOCK` - [@fahchen](https://github.com/fahchen).
|
8
|
+
* [#24](https://github.com/intridea/grape-entity/pull/24): Return documentation with `as` param considered - [@drakula2k](https://github.com/drakula2k).
|
9
|
+
* [#27](https://github.com/intridea/grape-entity/pull/27): Properly serialize hashes - [@clintonb](https://github.com/clintonb).
|
10
|
+
* [#28](https://github.com/intridea/grape-entity/pull/28): Look for method on entity before calling it on the object - [@MichaelXavier](https://github.com/MichaelXavier).
|
11
|
+
* [#33](https://github.com/intridea/grape-entity/pull/33): Support proper merging of nested conditionals - [@wyattisimo](https://github.com/wyattisimo).
|
12
|
+
* [#43](https://github.com/intridea/grape-entity/pull/43): Call procs in context of entity instance - [@joelvh](https://github.com/joelvh).
|
13
|
+
* [#47](https://github.com/intridea/grape-entity/pull/47): Support nested exposures - [@wyattisimo](https://github.com/wyattisimo).
|
14
|
+
* [#46](https://github.com/intridea/grape-entity/issues/46), [#50](https://github.com/intridea/grape-entity/pull/50): Added support for specifying the presenter class in `using` in string format - [@larryzhao](https://github.com/larryzhao).
|
15
|
+
* [#51](https://github.com/intridea/grape-entity/pull/51): Raise `ArgumentError` if an unknown option is used with `expose` - [@aj0strow](https://github.com/aj0strow).
|
16
|
+
* [#51](https://github.com/intridea/grape-entity/pull/51): Alias `:with` to `:using`, consistently with the Grape api endpoints - [@aj0strow](https://github.com/aj0strow).
|
17
|
+
* Your contribution here.
|
18
|
+
|
19
|
+
0.3.0 (2013-03-29)
|
20
|
+
==================
|
21
|
+
|
22
|
+
* [#9](https://github.com/intridea/grape-entity/pull/9): Added `with_options` for block-level exposure setting - [@SegFaultAX](https://github.com/SegFaultAX).
|
23
|
+
* The `instance.entity` method now optionally accepts `options` - [@mbleigh](https://github.com/mbleigh).
|
24
|
+
* You can pass symbols to `:if` and `:unless` to simply check for truthiness/falsiness of the specified options key - [@mbleigh](https://github.com/mbleigh).
|
25
|
+
|
26
|
+
0.2.0 (2013-01-11)
|
27
|
+
==================
|
28
|
+
|
29
|
+
* Moved the namespace back to `Grape::Entity` to preserve compatibility with Grape - [@dblock](https://github.com/dblock).
|
30
|
+
|
31
|
+
0.1.0 (2013-01-11)
|
32
|
+
==================
|
33
|
+
|
34
|
+
* Initial public release - [@agileanimal](https://github.com/agileanimal).
|
35
|
+
|
data/Gemfile
CHANGED
@@ -13,4 +13,11 @@ group :development, :test do
|
|
13
13
|
gem 'rspec'
|
14
14
|
gem 'rack-test', "~> 0.6.2", :require => "rack/test"
|
15
15
|
gem 'github-markup'
|
16
|
+
gem 'rubocop', '~> 0.16.0'
|
17
|
+
end
|
18
|
+
|
19
|
+
platforms :rbx do
|
20
|
+
gem 'rubysl', '~> 2.0'
|
21
|
+
gem 'parser', '~> 2.1'
|
22
|
+
gem 'racc', '~> 1.4'
|
16
23
|
end
|
data/Guardfile
CHANGED
data/README.md
ADDED
@@ -0,0 +1,320 @@
|
|
1
|
+
# Grape::Entity
|
2
|
+
|
3
|
+
[](https://travis-ci.org/agileanimal/grape-entity)
|
4
|
+
|
5
|
+
## Introduction
|
6
|
+
|
7
|
+
This gem adds Entity support to API frameworks, such as [Grape](https://github.com/intridea/grape). Grape's Entity is an API focused facade that sits on top of an object model.
|
8
|
+
|
9
|
+
### Example
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
module API
|
13
|
+
module Entities
|
14
|
+
class Status < Grape::Entity
|
15
|
+
format_with(:iso_timestamp) { |dt| dt.iso8601 }
|
16
|
+
|
17
|
+
expose :user_name
|
18
|
+
expose :text, documentation: { type: "String", desc: "Status update text." }
|
19
|
+
expose :ip, if: { type: :full }
|
20
|
+
expose :user_type, :user_id, if: lambda { |status, options| status.user.public? }
|
21
|
+
expose :contact_info do
|
22
|
+
expose :phone
|
23
|
+
expose :address, using: API::Address
|
24
|
+
end
|
25
|
+
expose :digest do |status, options|
|
26
|
+
Digest::MD5.hexdigest status.txt
|
27
|
+
end
|
28
|
+
expose :replies, using: API::Status, as: :replies
|
29
|
+
expose :last_reply, using: API::Status do |status, options|
|
30
|
+
status.replies.last
|
31
|
+
end
|
32
|
+
|
33
|
+
with_options(format_with: :iso_timestamp) do
|
34
|
+
expose :created_at
|
35
|
+
expose :updated_at
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module API
|
42
|
+
module Entities
|
43
|
+
class StatusDetailed < API::Entities::Status
|
44
|
+
expose :internal_id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
## Reusable Responses with Entities
|
51
|
+
|
52
|
+
Entities are a reusable means for converting Ruby objects to API responses. Entities can be used to conditionally include fields, nest other entities, and build ever larger responses, using inheritance.
|
53
|
+
|
54
|
+
### Defining Entities
|
55
|
+
|
56
|
+
Entities inherit from Grape::Entity, and define a simple DSL. Exposures can use runtime options to determine which fields should be visible, these options are available to `:if`, `:unless`, and `:proc`.
|
57
|
+
|
58
|
+
#### Basic Exposure
|
59
|
+
|
60
|
+
Define a list of fields that will always be exposed.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
expose :user_name, :ip
|
64
|
+
```
|
65
|
+
|
66
|
+
#### Exposing with a Presenter
|
67
|
+
|
68
|
+
Don't derive your model classes from `Grape::Entity`, expose them using a presenter.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
expose :replies, using: API::Status, as: :replies
|
72
|
+
```
|
73
|
+
|
74
|
+
Presenter classes can also be specified in string format, which helps with circular dependencies.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
expose :replies, using: `API::Status`, as: :replies
|
78
|
+
```
|
79
|
+
|
80
|
+
#### Conditional Exposure
|
81
|
+
|
82
|
+
Use `:if` or `:unless` to expose fields conditionally.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
expose :ip, if: { type: :full }
|
86
|
+
|
87
|
+
expose :ip, if: lambda { |instance, options| options[:type] == :full } # exposed if the function evaluates to true
|
88
|
+
expose :ip, if: :type # exposed if :type is available in the options hash
|
89
|
+
expose :ip, if { type: :full } # exposed if options :type has a value of :full
|
90
|
+
|
91
|
+
expose :ip, unless: ... # the opposite of :if
|
92
|
+
```
|
93
|
+
|
94
|
+
#### Safe Exposure
|
95
|
+
|
96
|
+
Don't raise an exception and expose as nil, even if the :x cannot be evaluated.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
expose :ip, safe: true
|
100
|
+
```
|
101
|
+
|
102
|
+
#### Nested Exposure
|
103
|
+
|
104
|
+
Supply a block to define a hash using nested exposures.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
expose :contact_info do
|
108
|
+
expose :phone
|
109
|
+
expose :address, using: API::Address
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
#### Runtime Exposure
|
114
|
+
|
115
|
+
Use a block or a `Proc` to evaluate exposure at runtime. The supplied block or
|
116
|
+
`Proc` will be called with two parameters: the represented object and runtime options.
|
117
|
+
|
118
|
+
**NOTE:** A block supplied with no parameters will be evaluated as a nested exposure (see above).
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
expose :digest do |status, options|
|
122
|
+
Digest::MD5.hexdigest status.txt
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
expose :digest, proc: ... # equivalent to a block
|
128
|
+
```
|
129
|
+
|
130
|
+
You can also define a method on the entity and it will try that before trying
|
131
|
+
on the object the entity wraps.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
class ExampleEntity < Grape::Entity
|
135
|
+
expose :attr_not_on_wrapped_object
|
136
|
+
# ...
|
137
|
+
private
|
138
|
+
|
139
|
+
def attr_not_on_wrapped_object
|
140
|
+
42
|
141
|
+
end
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
#### Aliases
|
146
|
+
|
147
|
+
Expose under a different name with `:as`.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
expose :replies, using: API::Status, as: :replies
|
151
|
+
```
|
152
|
+
|
153
|
+
#### Format Before Exposing
|
154
|
+
|
155
|
+
Apply a formatter before exposing a value.
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
format_with(:iso_timestamp) { |dt| dt.iso8601 }
|
159
|
+
with_options(format_with: :iso_timestamp) do
|
160
|
+
expose :created_at
|
161
|
+
expose :updated_at
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
#### Documentation
|
166
|
+
|
167
|
+
Expose documentation with the field. Gets bubbled up when used with Grape and various API documentation systems.
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
expose :text, documentation: { type: "String", desc: "Status update text." }
|
171
|
+
```
|
172
|
+
|
173
|
+
### Options Hash
|
174
|
+
|
175
|
+
The option keys `:version` and `:collection` are always defined. The `:version` key is defined as `api.version`. The `:collection` key is boolean, and defined as `true` if the object presented is an array. The options also contain the runtime environment in `:env`, which includes request parameters in `options[:env][:grape.request.params]`.
|
176
|
+
|
177
|
+
Any additional options defined on the entity exposure are included as is. In the following example `user` is set to the value of `current_user`.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
class Status < Grape::Entity
|
181
|
+
expose :user, if: lambda { |instance, options| options[:user] } do |instance, options|
|
182
|
+
# examine available environment keys with `p options[:env].keys`
|
183
|
+
options[:user]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
```
|
189
|
+
present s, with: Status, user: current_user
|
190
|
+
```
|
191
|
+
|
192
|
+
### Using the Exposure DSL
|
193
|
+
|
194
|
+
Grape ships with a DSL to easily define entities within the context of an existing class:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
class Status
|
198
|
+
include Grape::Entity::DSL
|
199
|
+
|
200
|
+
entity :text, :user_id do
|
201
|
+
expose :detailed, if: :conditional
|
202
|
+
end
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
The above will automatically create a `Status::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.
|
207
|
+
|
208
|
+
### Using Entities
|
209
|
+
|
210
|
+
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.
|
211
|
+
|
212
|
+
If the entity includes documentation it can be included in an endpoint's description.
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
module API
|
216
|
+
class Statuses < Grape::API
|
217
|
+
version 'v1'
|
218
|
+
|
219
|
+
desc 'Statuses.', {
|
220
|
+
params: API::Entities::Status.documentation
|
221
|
+
}
|
222
|
+
get '/statuses' do
|
223
|
+
statuses = Status.all
|
224
|
+
type = current_user.admin? ? :full : :default
|
225
|
+
present statuses, with: API::Entities::Status, type: type
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
### Entity Organization
|
232
|
+
|
233
|
+
In addition to separately organizing entities, it may be useful to put them as namespaced classes underneath the model they represent.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class Status
|
237
|
+
def entity
|
238
|
+
Entity.new(self)
|
239
|
+
end
|
240
|
+
|
241
|
+
class Entity < Grape::Entity
|
242
|
+
expose :text, :user_id
|
243
|
+
end
|
244
|
+
end
|
245
|
+
```
|
246
|
+
|
247
|
+
If you organize your entities this way, Grape will automatically detect the `Entity` class and use it to present your models. In this example, if you added `present User.new` to your endpoint, Grape would automatically detect that there is a `Status::Entity` class and use that as the representative entity. This can still be overridden by using the `:with` option or an explicit `represents` call.
|
248
|
+
|
249
|
+
### Caveats
|
250
|
+
|
251
|
+
Entities with duplicate exposure names and conditions will silently overwrite one another. In the following example, when `object.check` equals "foo", only `field_a` will be exposed. However, when `object.check` equals "bar" both `field_b` and `foo` will be exposed.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
module API
|
255
|
+
module Entities
|
256
|
+
class Status < Grape::Entity
|
257
|
+
expose :field_a, :foo, if: lambda { |object, options| object.check == "foo" }
|
258
|
+
expose :field_b, :foo, if: lambda { |object, options| object.check == "bar" }
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
This can be problematic, when you have mixed collections. Using `respond_to?` is safer.
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
module API
|
268
|
+
module Entities
|
269
|
+
class Status < Grape::Entity
|
270
|
+
expose :field_a, if: lambda { |object, options| object.check == "foo" }
|
271
|
+
expose :field_b, if: lambda { |object, options| object.check == "bar" }
|
272
|
+
expose :foo, if: lambda { |object, options| object.respond_to?(:foo) }
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
```
|
277
|
+
|
278
|
+
Also note that an `ArgumentError` is raised when unknown options are passed to either `expose` or `with_options`.
|
279
|
+
|
280
|
+
## Installation
|
281
|
+
|
282
|
+
Add this line to your application's Gemfile:
|
283
|
+
|
284
|
+
gem 'grape-entity'
|
285
|
+
|
286
|
+
And then execute:
|
287
|
+
|
288
|
+
$ bundle
|
289
|
+
|
290
|
+
Or install it yourself as:
|
291
|
+
|
292
|
+
$ gem install grape-entity
|
293
|
+
|
294
|
+
## Testing with Entities
|
295
|
+
|
296
|
+
Test API request/response as usual.
|
297
|
+
|
298
|
+
Also see [Grape Entity Matchers](https://github.com/agileanimal/grape-entity-matchers).
|
299
|
+
|
300
|
+
## Project Resources
|
301
|
+
|
302
|
+
* Need help? [Grape Google Group](http://groups.google.com/group/ruby-grape)
|
303
|
+
|
304
|
+
## Contributing
|
305
|
+
|
306
|
+
1. Fork the project
|
307
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
308
|
+
3. Write tests. Make changes. Run `rubocop`.
|
309
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
310
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
311
|
+
5. Create a new pull request
|
312
|
+
|
313
|
+
## License
|
314
|
+
|
315
|
+
MIT License. See LICENSE for details.
|
316
|
+
|
317
|
+
## Copyright
|
318
|
+
|
319
|
+
Copyright (c) 2010-2013 Michael Bleigh, Intridea, Inc., and contributors.
|
320
|
+
|