grape-entity 0.4.8 → 0.10.2
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 +5 -5
- data/.coveralls.yml +1 -0
- data/.github/dependabot.yml +20 -0
- data/.github/workflows/ci.yml +41 -0
- data/.gitignore +5 -1
- data/.rspec +2 -1
- data/.rubocop.yml +85 -2
- data/.rubocop_todo.yml +41 -33
- data/CHANGELOG.md +243 -37
- data/CONTRIBUTING.md +1 -1
- data/Dangerfile +3 -0
- data/Gemfile +11 -7
- data/Guardfile +4 -2
- data/LICENSE +1 -1
- data/README.md +272 -19
- data/Rakefile +9 -11
- data/UPGRADING.md +35 -0
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +13 -8
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity/condition/base.rb +37 -0
- data/lib/grape_entity/condition/block_condition.rb +23 -0
- data/lib/grape_entity/condition/hash_condition.rb +27 -0
- data/lib/grape_entity/condition/symbol_condition.rb +23 -0
- data/lib/grape_entity/condition.rb +35 -0
- data/lib/grape_entity/delegator/base.rb +7 -0
- data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
- data/lib/grape_entity/delegator/hash_object.rb +4 -2
- data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
- data/lib/grape_entity/delegator/plain_object.rb +2 -0
- data/lib/grape_entity/delegator.rb +14 -9
- data/lib/grape_entity/deprecated.rb +13 -0
- data/lib/grape_entity/entity.rb +192 -258
- data/lib/grape_entity/exposure/base.rb +138 -0
- data/lib/grape_entity/exposure/block_exposure.rb +31 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +13 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +27 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +32 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +83 -0
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +66 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +133 -0
- data/lib/grape_entity/exposure/represent_exposure.rb +50 -0
- data/lib/grape_entity/exposure.rb +105 -0
- data/lib/grape_entity/options.rb +132 -0
- data/lib/grape_entity/version.rb +3 -1
- data/lib/grape_entity.rb +9 -2
- data/spec/grape_entity/entity_spec.rb +903 -184
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +58 -0
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +32 -0
- data/spec/grape_entity/exposure_spec.rb +102 -0
- data/spec/grape_entity/hash_spec.rb +91 -0
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +21 -2
- metadata +91 -18
- data/.travis.yml +0 -19
data/Guardfile
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# A sample Guardfile
|
2
4
|
# More info at https://github.com/guard/guard#readme
|
3
5
|
|
4
6
|
guard 'rspec', version: 2 do
|
5
7
|
watch(%r{^spec/.+_spec\.rb$})
|
6
|
-
watch(%r{^lib/(.+)\.rb$})
|
8
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
9
|
watch(%r{^spec/support/shared_versioning_examples.rb$}) { |_m| 'spec/' }
|
8
|
-
watch('spec/spec_helper.rb')
|
10
|
+
watch('spec/spec_helper.rb') { 'spec/' }
|
9
11
|
end
|
10
12
|
|
11
13
|
guard 'bundler' do
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2010 Michael Bleigh
|
1
|
+
Copyright (c) 2010-2016 Michael Bleigh, Intridea, Inc., ruby-grape and Contributors.
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -1,10 +1,47 @@
|
|
1
|
-
# Grape::Entity
|
2
|
-
|
3
1
|
[](http://badge.fury.io/rb/grape-entity)
|
4
|
-
|
5
|
-
[
|
3
|
+
[](https://coveralls.io/github/ruby-grape/grape-entity?branch=master)
|
6
4
|
[](https://codeclimate.com/github/ruby-grape/grape-entity)
|
7
5
|
|
6
|
+
# Table of Contents
|
7
|
+
|
8
|
+
- [Grape::Entity](#grapeentity)
|
9
|
+
- [Introduction](#introduction)
|
10
|
+
- [Example](#example)
|
11
|
+
- [Reusable Responses with Entities](#reusable-responses-with-entities)
|
12
|
+
- [Defining Entities](#defining-entities)
|
13
|
+
- [Basic Exposure](#basic-exposure)
|
14
|
+
- [Exposing with a Presenter](#exposing-with-a-presenter)
|
15
|
+
- [Conditional Exposure](#conditional-exposure)
|
16
|
+
- [Safe Exposure](#safe-exposure)
|
17
|
+
- [Nested Exposure](#nested-exposure)
|
18
|
+
- [Collection Exposure](#collection-exposure)
|
19
|
+
- [Merge Fields](#merge-fields)
|
20
|
+
- [Runtime Exposure](#runtime-exposure)
|
21
|
+
- [Unexpose](#unexpose)
|
22
|
+
- [Overriding exposures](#overriding-exposures)
|
23
|
+
- [Returning only the fields you want](#returning-only-the-fields-you-want)
|
24
|
+
- [Aliases](#aliases)
|
25
|
+
- [Format Before Exposing](#format-before-exposing)
|
26
|
+
- [Expose Nil](#expose-nil)
|
27
|
+
- [Default Value](#default-value)
|
28
|
+
- [Documentation](#documentation)
|
29
|
+
- [Options Hash](#options-hash)
|
30
|
+
- [Passing Additional Option To Nested Exposure](#passing-additional-option-to-nested-exposure)
|
31
|
+
- [Attribute Path Tracking](#attribute-path-tracking)
|
32
|
+
- [Using the Exposure DSL](#using-the-exposure-dsl)
|
33
|
+
- [Using Entities](#using-entities)
|
34
|
+
- [Entity Organization](#entity-organization)
|
35
|
+
- [Caveats](#caveats)
|
36
|
+
- [Installation](#installation)
|
37
|
+
- [Testing with Entities](#testing-with-entities)
|
38
|
+
- [Project Resources](#project-resources)
|
39
|
+
- [Contributing](#contributing)
|
40
|
+
- [License](#license)
|
41
|
+
- [Copyright](#copyright)
|
42
|
+
|
43
|
+
# Grape::Entity
|
44
|
+
|
8
45
|
## Introduction
|
9
46
|
|
10
47
|
This gem adds Entity support to API frameworks, such as [Grape](https://github.com/ruby-grape/grape). Grape's Entity is an API focused facade that sits on top of an object model.
|
@@ -21,9 +58,10 @@ module API
|
|
21
58
|
expose :text, documentation: { type: "String", desc: "Status update text." }
|
22
59
|
expose :ip, if: { type: :full }
|
23
60
|
expose :user_type, :user_id, if: lambda { |status, options| status.user.public? }
|
61
|
+
expose :location, merge: true
|
24
62
|
expose :contact_info do
|
25
63
|
expose :phone
|
26
|
-
expose :address, using: API::Entities::Address
|
64
|
+
expose :address, merge: true, using: API::Entities::Address
|
27
65
|
end
|
28
66
|
expose :digest do |status, options|
|
29
67
|
Digest::MD5.hexdigest status.txt
|
@@ -73,6 +111,20 @@ The field lookup takes several steps
|
|
73
111
|
* next try `object.fetch(exposure)`
|
74
112
|
* last raise an Exception
|
75
113
|
|
114
|
+
`exposure` is a Symbol by default. If `object` is a Hash with stringified keys, you can set the hash accessor at the entity-class level to properly expose its members:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class Status < GrapeEntity
|
118
|
+
self.hash_access = :to_s
|
119
|
+
|
120
|
+
expose :code
|
121
|
+
expose :message
|
122
|
+
end
|
123
|
+
|
124
|
+
Status.represent({ 'code' => 418, 'message' => "I'm a teapot" }).as_json
|
125
|
+
#=> { code: 418, message: "I'm a teapot" }
|
126
|
+
```
|
127
|
+
|
76
128
|
#### Exposing with a Presenter
|
77
129
|
|
78
130
|
Don't derive your model classes from `Grape::Entity`, expose them using a presenter.
|
@@ -153,6 +205,40 @@ As example:
|
|
153
205
|
|
154
206
|
```
|
155
207
|
|
208
|
+
#### Merge Fields
|
209
|
+
|
210
|
+
Use `:merge` option to merge fields into the hash or into the root:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
expose :contact_info do
|
214
|
+
expose :phone
|
215
|
+
expose :address, merge: true, using: API::Entities::Address
|
216
|
+
end
|
217
|
+
|
218
|
+
expose :status, merge: true
|
219
|
+
```
|
220
|
+
|
221
|
+
This will return something like:
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
{ contact_info: { phone: "88002000700", city: 'City 17', address_line: 'Block C' }, text: 'HL3', likes: 19 }
|
225
|
+
```
|
226
|
+
|
227
|
+
It also works with collections:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
expose :profiles do
|
231
|
+
expose :users, merge: true, using: API::Entities::User
|
232
|
+
expose :admins, merge: true, using: API::Entities::Admin
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
Provide lambda to solve collisions:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
expose :status, merge: ->(key, old_val, new_val) { old_val + new_val if old_val && new_val }
|
240
|
+
```
|
241
|
+
|
156
242
|
#### Runtime Exposure
|
157
243
|
|
158
244
|
Use a block or a `Proc` to evaluate exposure at runtime. The supplied block or
|
@@ -177,7 +263,7 @@ on the object the entity wraps.
|
|
177
263
|
class ExampleEntity < Grape::Entity
|
178
264
|
expose :attr_not_on_wrapped_object
|
179
265
|
# ...
|
180
|
-
private
|
266
|
+
private
|
181
267
|
|
182
268
|
def attr_not_on_wrapped_object
|
183
269
|
42
|
@@ -185,16 +271,17 @@ private
|
|
185
271
|
end
|
186
272
|
```
|
187
273
|
|
188
|
-
You have
|
274
|
+
You always have access to the presented instance (`object`) and the top-level
|
275
|
+
entity options (`options`).
|
189
276
|
|
190
277
|
```ruby
|
191
278
|
class ExampleEntity < Grape::Entity
|
192
279
|
expose :formatted_value
|
193
280
|
# ...
|
194
|
-
private
|
281
|
+
private
|
195
282
|
|
196
283
|
def formatted_value
|
197
|
-
"+ X #{object.value}"
|
284
|
+
"+ X #{object.value} #{options[:y]}"
|
198
285
|
end
|
199
286
|
end
|
200
287
|
```
|
@@ -220,6 +307,22 @@ class MailingAddress < UserData
|
|
220
307
|
end
|
221
308
|
```
|
222
309
|
|
310
|
+
#### Overriding exposures
|
311
|
+
|
312
|
+
If you want to add one more exposure for the field but don't want the first one to be fired (for instance, when using inheritance), you can use the `override` flag. For instance:
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
class User < Grape::Entity
|
316
|
+
expose :name
|
317
|
+
end
|
318
|
+
|
319
|
+
class Employee < User
|
320
|
+
expose :name, as: :employee_name, override: true
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
`User` will return something like this `{ "name" : "John" }` while `Employee` will present the same data as `{ "employee_name" : "John" }` instead of `{ "name" : "John", "employee_name" : "John" }`.
|
325
|
+
|
223
326
|
#### Returning only the fields you want
|
224
327
|
|
225
328
|
After exposing the desired attributes, you can choose which one you need when representing some object or collection by using the only: and except: options. See the example:
|
@@ -276,10 +379,134 @@ expose :replies, using: API::Entities::Status, as: :responses
|
|
276
379
|
Apply a formatter before exposing a value.
|
277
380
|
|
278
381
|
```ruby
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
382
|
+
module Entities
|
383
|
+
class MyModel < Grape::Entity
|
384
|
+
format_with(:iso_timestamp) do |date|
|
385
|
+
date.iso8601
|
386
|
+
end
|
387
|
+
|
388
|
+
with_options(format_with: :iso_timestamp) do
|
389
|
+
expose :created_at
|
390
|
+
expose :updated_at
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
```
|
395
|
+
|
396
|
+
Defining a reusable formatter between multiples entities:
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
module ApiHelpers
|
400
|
+
extend Grape::API::Helpers
|
401
|
+
|
402
|
+
Grape::Entity.format_with :utc do |date|
|
403
|
+
date.utc if date
|
404
|
+
end
|
405
|
+
end
|
406
|
+
```
|
407
|
+
|
408
|
+
```ruby
|
409
|
+
module Entities
|
410
|
+
class MyModel < Grape::Entity
|
411
|
+
expose :updated_at, format_with: :utc
|
412
|
+
end
|
413
|
+
|
414
|
+
class AnotherModel < Grape::Entity
|
415
|
+
expose :created_at, format_with: :utc
|
416
|
+
end
|
417
|
+
end
|
418
|
+
```
|
419
|
+
|
420
|
+
#### Expose Nil
|
421
|
+
|
422
|
+
By default, exposures that contain `nil` values will be represented in the resulting JSON as `null`.
|
423
|
+
|
424
|
+
As an example, a hash with the following values:
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
{
|
428
|
+
name: nil,
|
429
|
+
age: 100
|
430
|
+
}
|
431
|
+
```
|
432
|
+
|
433
|
+
will result in a JSON object that looks like:
|
434
|
+
|
435
|
+
```javascript
|
436
|
+
{
|
437
|
+
"name": null,
|
438
|
+
"age": 100
|
439
|
+
}
|
440
|
+
```
|
441
|
+
|
442
|
+
There are also times when, rather than displaying an attribute with a `null` value, it is more desirable to not display the attribute at all. Using the hash from above the desired JSON would look like:
|
443
|
+
|
444
|
+
```javascript
|
445
|
+
{
|
446
|
+
"age": 100
|
447
|
+
}
|
448
|
+
```
|
449
|
+
|
450
|
+
In order to turn on this behavior for an as-exposure basis, the option `expose_nil` can be used. By default, `expose_nil` is considered to be `true`, meaning that `nil` values will be represented in JSON as `null`. If `false` is provided, then attributes with `nil` values will be omitted from the resulting JSON completely.
|
451
|
+
|
452
|
+
```ruby
|
453
|
+
module Entities
|
454
|
+
class MyModel < Grape::Entity
|
455
|
+
expose :name, expose_nil: false
|
456
|
+
expose :age, expose_nil: false
|
457
|
+
end
|
458
|
+
end
|
459
|
+
```
|
460
|
+
|
461
|
+
`expose_nil` is per exposure, so you can suppress exposures from resulting in `null` or express `null` values on a per exposure basis as you need:
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
module Entities
|
465
|
+
class MyModel < Grape::Entity
|
466
|
+
expose :name, expose_nil: false
|
467
|
+
expose :age # since expose_nil is omitted nil values will be rendered as null
|
468
|
+
end
|
469
|
+
end
|
470
|
+
```
|
471
|
+
|
472
|
+
It is also possible to use `expose_nil` with `with_options` if you want to add the configuration to multiple exposures at once.
|
473
|
+
|
474
|
+
```ruby
|
475
|
+
module Entities
|
476
|
+
class MyModel < Grape::Entity
|
477
|
+
# None of the exposures in the with_options block will render nil values as null
|
478
|
+
with_options(expose_nil: false) do
|
479
|
+
expose :name
|
480
|
+
expose :age
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
```
|
485
|
+
|
486
|
+
When using `with_options`, it is possible to again override which exposures will render `nil` as `null` by adding the option on a specific exposure.
|
487
|
+
|
488
|
+
```ruby
|
489
|
+
module Entities
|
490
|
+
class MyModel < Grape::Entity
|
491
|
+
# None of the exposures in the with_options block will render nil values as null
|
492
|
+
with_options(expose_nil: false) do
|
493
|
+
expose :name
|
494
|
+
expose :age, expose_nil: true # nil values would be rendered as null in the JSON
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
```
|
499
|
+
|
500
|
+
#### Default Value
|
501
|
+
|
502
|
+
This option can be used to provide a default value in case the return value is nil or empty.
|
503
|
+
|
504
|
+
```ruby
|
505
|
+
module Entities
|
506
|
+
class MyModel < Grape::Entity
|
507
|
+
expose :name, default: ''
|
508
|
+
expose :age, default: 60
|
509
|
+
end
|
283
510
|
end
|
284
511
|
```
|
285
512
|
|
@@ -311,7 +538,7 @@ present s, with: Status, user: current_user
|
|
311
538
|
```
|
312
539
|
|
313
540
|
#### Passing Additional Option To Nested Exposure
|
314
|
-
|
541
|
+
Sometimes you want to pass additional options or parameters to nested a exposure. For example, let's say that you need to expose an address for a contact info and it has two different formats: **full** and **simple**. You can pass an additional `full_format` option to specify which format to render.
|
315
542
|
|
316
543
|
```ruby
|
317
544
|
# api/contact.rb
|
@@ -327,12 +554,38 @@ end
|
|
327
554
|
# api/address.rb
|
328
555
|
expose :state, if: lambda {|instance, options| !!options[:full_format]} # the new option could be retrieved in options hash for conditional exposure
|
329
556
|
expose :city, if: lambda {|instance, options| !!options[:full_format]}
|
330
|
-
expose :
|
557
|
+
expose :street do |instance, options|
|
331
558
|
# the new option could be retrieved in options hash for runtime exposure
|
332
559
|
!!options[:full_format] ? instance.full_street_name : instance.simple_street_name
|
333
560
|
end
|
334
561
|
```
|
335
|
-
**Notice**: In the above code, you should pay attention to [**Safe Exposure**](#safe-exposure) yourself
|
562
|
+
**Notice**: In the above code, you should pay attention to [**Safe Exposure**](#safe-exposure) yourself. For example, `instance.address` might be `nil` and it is better to expose it as nil directly.
|
563
|
+
|
564
|
+
#### Attribute Path Tracking
|
565
|
+
|
566
|
+
Sometimes, especially when there are nested attributes, you might want to know which attribute
|
567
|
+
is being exposed. For example, some APIs allow users to provide a parameter to control which fields
|
568
|
+
will be included in (or excluded from) the response.
|
569
|
+
|
570
|
+
GrapeEntity can track the path of each attribute, which you can access during conditions checking
|
571
|
+
or runtime exposure via `options[:attr_path]`.
|
572
|
+
|
573
|
+
The attribute path is an array. The last item of this array is the name (alias) of current attribute.
|
574
|
+
If the attribute is nested, the former items are names (aliases) of its ancestor attributes.
|
575
|
+
|
576
|
+
Example:
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
class Status < Grape::Entity
|
580
|
+
expose :user # path is [:user]
|
581
|
+
expose :foo, as: :bar # path is [:bar]
|
582
|
+
expose :a do
|
583
|
+
expose :b, as: :xx do
|
584
|
+
expose :c # path is [:a, :xx, :c]
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
```
|
336
589
|
|
337
590
|
### Using the Exposure DSL
|
338
591
|
|
@@ -352,7 +605,7 @@ The above will automatically create a `Status::Entity` class and define properti
|
|
352
605
|
|
353
606
|
### Using Entities
|
354
607
|
|
355
|
-
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.
|
608
|
+
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 (unless namespaced entity classes are used, see [next section](#entity-organization)).
|
356
609
|
If the entity includes documentation it can be included in an endpoint's description.
|
357
610
|
|
358
611
|
```ruby
|
@@ -388,7 +641,7 @@ class Status
|
|
388
641
|
end
|
389
642
|
```
|
390
643
|
|
391
|
-
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
|
644
|
+
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 Status.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.
|
392
645
|
|
393
646
|
### Caveats
|
394
647
|
|
@@ -455,4 +708,4 @@ MIT License. See [LICENSE](LICENSE) for details.
|
|
455
708
|
|
456
709
|
## Copyright
|
457
710
|
|
458
|
-
Copyright (c) 2010-
|
711
|
+
Copyright (c) 2010-2016 Michael Bleigh, Intridea, Inc., ruby-grape and Contributors.
|
data/Rakefile
CHANGED
@@ -1,22 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rubygems'
|
2
4
|
require 'bundler'
|
3
|
-
|
5
|
+
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
|
8
|
+
require 'rake'
|
4
9
|
|
5
10
|
Bundler::GemHelper.install_tasks
|
6
11
|
|
12
|
+
require 'rspec/core'
|
7
13
|
require 'rspec/core/rake_task'
|
8
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
9
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
10
|
-
end
|
11
14
|
|
12
|
-
RSpec::Core::RakeTask.new(:
|
13
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
14
|
-
spec.rcov = true
|
15
|
-
end
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
16
16
|
|
17
|
-
task :spec
|
18
|
-
require 'rainbow/ext/string' unless String.respond_to?(:color)
|
19
17
|
require 'rubocop/rake_task'
|
20
18
|
RuboCop::RakeTask.new(:rubocop)
|
21
19
|
|
22
|
-
task default: [
|
20
|
+
task default: %i[spec rubocop]
|
data/UPGRADING.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Upgrading Grape Entity
|
2
|
+
|
3
|
+
|
4
|
+
### Upgrading to >= 0.8.2
|
5
|
+
|
6
|
+
Official support for ruby < 2.5 removed, ruby 2.5 only in testing mode, but no support.
|
7
|
+
|
8
|
+
In Ruby 3.0: the block handling will be changed
|
9
|
+
[language-changes point 3, Proc](https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes).
|
10
|
+
This:
|
11
|
+
```ruby
|
12
|
+
expose :that_method_without_args, &:method_without_args
|
13
|
+
```
|
14
|
+
will be deprecated.
|
15
|
+
|
16
|
+
Prefer to use this pattern for simple setting a value
|
17
|
+
```ruby
|
18
|
+
expose :method_without_args, as: :that_method_without_args
|
19
|
+
```
|
20
|
+
|
21
|
+
### Upgrading to >= 0.6.0
|
22
|
+
|
23
|
+
#### Changes in Grape::Entity#inspect
|
24
|
+
|
25
|
+
The `Grape::Entity#inspect` method will no longer serialize the entity presenter with its options and delegator, but the exposed entity itself, using `#serializable_hash`.
|
26
|
+
|
27
|
+
See [#250](https://github.com/ruby-grape/grape-entity/pull/250) for more information.
|
28
|
+
|
29
|
+
### Upgrading to >= 0.5.1
|
30
|
+
|
31
|
+
#### Changes in NestedExposures.delete_if
|
32
|
+
|
33
|
+
`Grape::Entity::Exposure::NestingExposure::NestedExposures.delete_if` always returns exposures, regardless of delete result (used to be `nil` in negative case).
|
34
|
+
|
35
|
+
See [#203](https://github.com/ruby-grape/grape-entity/pull/203) for more information.
|
data/bench/serializing.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
4
|
require 'grape-entity'
|
3
5
|
require 'benchmark'
|
@@ -5,6 +7,7 @@ require 'benchmark'
|
|
5
7
|
module Models
|
6
8
|
class School
|
7
9
|
attr_reader :classrooms
|
10
|
+
|
8
11
|
def initialize
|
9
12
|
@classrooms = []
|
10
13
|
end
|
@@ -13,6 +16,7 @@ module Models
|
|
13
16
|
class ClassRoom
|
14
17
|
attr_reader :students
|
15
18
|
attr_accessor :teacher
|
19
|
+
|
16
20
|
def initialize(opts = {})
|
17
21
|
@teacher = opts[:teacher]
|
18
22
|
@students = []
|
@@ -21,6 +25,7 @@ module Models
|
|
21
25
|
|
22
26
|
class Person
|
23
27
|
attr_accessor :name
|
28
|
+
|
24
29
|
def initialize(opts = {})
|
25
30
|
@name = opts[:name]
|
26
31
|
end
|
@@ -28,6 +33,7 @@ module Models
|
|
28
33
|
|
29
34
|
class Teacher < Models::Person
|
30
35
|
attr_accessor :tenure
|
36
|
+
|
31
37
|
def initialize(opts = {})
|
32
38
|
super(opts)
|
33
39
|
@tenure = opts[:tenure]
|
@@ -36,6 +42,7 @@ module Models
|
|
36
42
|
|
37
43
|
class Student < Models::Person
|
38
44
|
attr_reader :grade
|
45
|
+
|
39
46
|
def initialize(opts = {})
|
40
47
|
super(opts)
|
41
48
|
@grade = opts[:grade]
|
data/grape-entity.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
4
|
require 'grape_entity/version'
|
3
5
|
|
4
6
|
Gem::Specification.new do |s|
|
@@ -12,19 +14,22 @@ Gem::Specification.new do |s|
|
|
12
14
|
s.description = 'Extracted from Grape, A Ruby framework for rapid API development with great conventions.'
|
13
15
|
s.license = 'MIT'
|
14
16
|
|
15
|
-
s.
|
17
|
+
s.required_ruby_version = '>= 2.5'
|
16
18
|
|
17
|
-
s.add_runtime_dependency 'activesupport'
|
19
|
+
s.add_runtime_dependency 'activesupport', '>= 3.0.0'
|
20
|
+
# FIXME: remove dependecy
|
18
21
|
s.add_runtime_dependency 'multi_json', '>= 1.3.2'
|
19
22
|
|
20
|
-
s.add_development_dependency '
|
23
|
+
s.add_development_dependency 'bundler'
|
21
24
|
s.add_development_dependency 'maruku'
|
25
|
+
s.add_development_dependency 'pry' unless RUBY_PLATFORM.eql?('java') || RUBY_ENGINE.eql?('rbx')
|
26
|
+
s.add_development_dependency 'pry-byebug' unless RUBY_PLATFORM.eql?('java') || RUBY_ENGINE.eql?('rbx')
|
27
|
+
s.add_development_dependency 'rack-test'
|
28
|
+
s.add_development_dependency 'rake'
|
29
|
+
s.add_development_dependency 'rspec', '~> 3.9'
|
22
30
|
s.add_development_dependency 'yard'
|
23
|
-
s.add_development_dependency 'rspec', '~> 2.9'
|
24
|
-
s.add_development_dependency 'bundler'
|
25
31
|
|
26
32
|
s.files = `git ls-files`.split("\n")
|
27
|
-
s.test_files = `git ls-files -- {test,spec
|
28
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
33
|
+
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
29
34
|
s.require_paths = ['lib']
|
30
35
|
end
|
data/lib/grape-entity.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Condition
|
6
|
+
class Base
|
7
|
+
def self.new(inverse, *args, &block)
|
8
|
+
super(inverse).tap { |e| e.setup(*args, &block) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(inverse = false)
|
12
|
+
@inverse = inverse
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
(self.class == other.class) && (inversed? == other.inversed?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def inversed?
|
20
|
+
@inverse
|
21
|
+
end
|
22
|
+
|
23
|
+
def met?(entity, options)
|
24
|
+
@inverse ? unless_value(entity, options) : if_value(entity, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def if_value(_entity, _options)
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def unless_value(entity, options)
|
32
|
+
!if_value(entity, options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Condition
|
6
|
+
class BlockCondition < Base
|
7
|
+
attr_reader :block
|
8
|
+
|
9
|
+
def setup(block)
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
super && @block == other.block
|
15
|
+
end
|
16
|
+
|
17
|
+
def if_value(entity, options)
|
18
|
+
entity.exec_with_object(options, &@block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Condition
|
6
|
+
class HashCondition < Base
|
7
|
+
attr_reader :cond_hash
|
8
|
+
|
9
|
+
def setup(cond_hash)
|
10
|
+
@cond_hash = cond_hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
super && @cond_hash == other.cond_hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def if_value(_entity, options)
|
18
|
+
@cond_hash.all? { |k, v| options[k.to_sym] == v }
|
19
|
+
end
|
20
|
+
|
21
|
+
def unless_value(_entity, options)
|
22
|
+
@cond_hash.any? { |k, v| options[k.to_sym] != v }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grape
|
4
|
+
class Entity
|
5
|
+
module Condition
|
6
|
+
class SymbolCondition < Base
|
7
|
+
attr_reader :symbol
|
8
|
+
|
9
|
+
def setup(symbol)
|
10
|
+
@symbol = symbol
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
super && @symbol == other.symbol
|
15
|
+
end
|
16
|
+
|
17
|
+
def if_value(_entity, options)
|
18
|
+
options[symbol]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|