grape-entity 0.6.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.coveralls.yml +1 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +82 -2
- data/.rubocop_todo.yml +16 -33
- data/.travis.yml +18 -17
- data/CHANGELOG.md +75 -0
- data/Dangerfile +2 -0
- data/Gemfile +6 -1
- data/Guardfile +4 -2
- data/README.md +101 -4
- data/Rakefile +2 -2
- data/UPGRADING.md +31 -2
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +10 -10
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity.rb +3 -0
- data/lib/grape_entity/condition.rb +20 -11
- data/lib/grape_entity/condition/base.rb +3 -1
- data/lib/grape_entity/condition/block_condition.rb +3 -1
- data/lib/grape_entity/condition/hash_condition.rb +2 -0
- data/lib/grape_entity/condition/symbol_condition.rb +2 -0
- data/lib/grape_entity/delegator.rb +10 -9
- data/lib/grape_entity/delegator/base.rb +2 -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/deprecated.rb +13 -0
- data/lib/grape_entity/entity.rb +115 -36
- data/lib/grape_entity/exposure.rb +64 -41
- data/lib/grape_entity/exposure/base.rb +21 -8
- data/lib/grape_entity/exposure/block_exposure.rb +2 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +36 -30
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +26 -15
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +10 -2
- data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
- data/lib/grape_entity/options.rb +44 -58
- data/lib/grape_entity/version.rb +3 -1
- data/spec/grape_entity/entity_spec.rb +270 -47
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +5 -3
- data/spec/grape_entity/exposure_spec.rb +14 -2
- data/spec/grape_entity/hash_spec.rb +38 -1
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +17 -0
- metadata +32 -43
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](http://img.shields.io/gem/v/grape-entity.svg)](http://badge.fury.io/rb/grape-entity)
|
4
4
|
[![Build Status](http://img.shields.io/travis/ruby-grape/grape-entity.svg)](https://travis-ci.org/ruby-grape/grape-entity)
|
5
|
-
[![
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape-entity/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape-entity?branch=master)
|
6
6
|
[![Code Climate](https://codeclimate.com/github/ruby-grape/grape-entity.svg)](https://codeclimate.com/github/ruby-grape/grape-entity)
|
7
7
|
|
8
8
|
## Introduction
|
@@ -220,7 +220,8 @@ class ExampleEntity < Grape::Entity
|
|
220
220
|
end
|
221
221
|
```
|
222
222
|
|
223
|
-
You have
|
223
|
+
You always have access to the presented instance (`object`) and the top-level
|
224
|
+
entity options (`options`).
|
224
225
|
|
225
226
|
```ruby
|
226
227
|
class ExampleEntity < Grape::Entity
|
@@ -229,7 +230,7 @@ class ExampleEntity < Grape::Entity
|
|
229
230
|
private
|
230
231
|
|
231
232
|
def formatted_value
|
232
|
-
"+ X #{object.value}"
|
233
|
+
"+ X #{object.value} #{options[:y]}"
|
233
234
|
end
|
234
235
|
end
|
235
236
|
```
|
@@ -255,6 +256,22 @@ class MailingAddress < UserData
|
|
255
256
|
end
|
256
257
|
```
|
257
258
|
|
259
|
+
#### Overriding exposures
|
260
|
+
|
261
|
+
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:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
class User < Grape::Entity
|
265
|
+
expose :name
|
266
|
+
end
|
267
|
+
|
268
|
+
class Employee < User
|
269
|
+
expose :name, as: :employee_name, override: true
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
`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" }`.
|
274
|
+
|
258
275
|
#### Returning only the fields you want
|
259
276
|
|
260
277
|
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:
|
@@ -320,7 +337,7 @@ module Entities
|
|
320
337
|
with_options(format_with: :iso_timestamp) do
|
321
338
|
expose :created_at
|
322
339
|
expose :updated_at
|
323
|
-
end
|
340
|
+
end
|
324
341
|
end
|
325
342
|
end
|
326
343
|
```
|
@@ -349,6 +366,86 @@ module Entities
|
|
349
366
|
end
|
350
367
|
```
|
351
368
|
|
369
|
+
#### Expose Nil
|
370
|
+
|
371
|
+
By default, exposures that contain `nil` values will be represented in the resulting JSON as `null`.
|
372
|
+
|
373
|
+
As an example, a hash with the following values:
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
{
|
377
|
+
name: nil,
|
378
|
+
age: 100
|
379
|
+
}
|
380
|
+
```
|
381
|
+
|
382
|
+
will result in a JSON object that looks like:
|
383
|
+
|
384
|
+
```javascript
|
385
|
+
{
|
386
|
+
"name": null,
|
387
|
+
"age": 100
|
388
|
+
}
|
389
|
+
```
|
390
|
+
|
391
|
+
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:
|
392
|
+
|
393
|
+
```javascript
|
394
|
+
{
|
395
|
+
"age": 100
|
396
|
+
}
|
397
|
+
```
|
398
|
+
|
399
|
+
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.
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
module Entities
|
403
|
+
class MyModel < Grape::Entity
|
404
|
+
expose :name, expose_nil: false
|
405
|
+
expose :age, expose_nil: false
|
406
|
+
end
|
407
|
+
end
|
408
|
+
```
|
409
|
+
|
410
|
+
`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:
|
411
|
+
|
412
|
+
```ruby
|
413
|
+
module Entities
|
414
|
+
class MyModel < Grape::Entity
|
415
|
+
expose :name, expose_nil: false
|
416
|
+
expose :age # since expose_nil is omitted nil values will be rendered as null
|
417
|
+
end
|
418
|
+
end
|
419
|
+
```
|
420
|
+
|
421
|
+
It is also possible to use `expose_nil` with `with_options` if you want to add the configuration to multiple exposures at once.
|
422
|
+
|
423
|
+
```ruby
|
424
|
+
module Entities
|
425
|
+
class MyModel < Grape::Entity
|
426
|
+
# None of the exposures in the with_options block will render nil values as null
|
427
|
+
with_options(expose_nil: false) do
|
428
|
+
expose :name
|
429
|
+
expose :age
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
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.
|
436
|
+
|
437
|
+
```ruby
|
438
|
+
module Entities
|
439
|
+
class MyModel < Grape::Entity
|
440
|
+
# None of the exposures in the with_options block will render nil values as null
|
441
|
+
with_options(expose_nil: false) do
|
442
|
+
expose :name
|
443
|
+
expose :age, expose_nil: true # nil values would be rendered as null in the JSON
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
```
|
448
|
+
|
352
449
|
#### Documentation
|
353
450
|
|
354
451
|
Expose documentation with the field. Gets bubbled up when used with Grape and various API documentation systems.
|
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'bundler'
|
@@ -17,4 +17,4 @@ RSpec::Core::RakeTask.new(:spec)
|
|
17
17
|
require 'rubocop/rake_task'
|
18
18
|
RuboCop::RakeTask.new(:rubocop)
|
19
19
|
|
20
|
-
task default: [
|
20
|
+
task default: %i[spec rubocop]
|
data/UPGRADING.md
CHANGED
@@ -1,5 +1,34 @@
|
|
1
|
-
Upgrading Grape Entity
|
2
|
-
|
1
|
+
# Upgrading Grape Entity
|
2
|
+
|
3
|
+
### Upgrading to >= 0.8.2
|
4
|
+
|
5
|
+
In Ruby 3.0: the block handling will be changed
|
6
|
+
[language-changes point 3, Proc](https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes).
|
7
|
+
This:
|
8
|
+
```ruby
|
9
|
+
expose :that_method_without_args, &:method_without_args
|
10
|
+
```
|
11
|
+
will be deprecated.
|
12
|
+
|
13
|
+
Prefer to use this pattern for simple setting a value
|
14
|
+
```ruby
|
15
|
+
expose :method_without_args, as: :that_method_without_args
|
16
|
+
```
|
17
|
+
|
18
|
+
### Upgrading to >= 0.8.2
|
19
|
+
|
20
|
+
In Ruby 3.0: the block handling will be changed
|
21
|
+
[language-changes point 3, Proc](https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes).
|
22
|
+
This:
|
23
|
+
```ruby
|
24
|
+
expose :that_method_without_args, &:method_without_args
|
25
|
+
```
|
26
|
+
will be deprecated.
|
27
|
+
|
28
|
+
Prefer to use this pattern for simple setting a value
|
29
|
+
```ruby
|
30
|
+
expose :method_without_args, as: :that_method_without_args
|
31
|
+
```
|
3
32
|
|
4
33
|
### Upgrading to >= 0.6.0
|
5
34
|
|
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,22 +14,20 @@ 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.required_ruby_version = '>= 2.
|
16
|
-
|
17
|
-
s.rubyforge_project = 'grape-entity'
|
17
|
+
s.required_ruby_version = '>= 2.4'
|
18
18
|
|
19
|
+
s.add_runtime_dependency 'activesupport', '>= 3.0.0'
|
20
|
+
# FIXME: remove dependecy
|
19
21
|
s.add_runtime_dependency 'multi_json', '>= 1.3.2'
|
20
|
-
s.add_runtime_dependency 'activesupport', '>= 5.0.0'
|
21
22
|
|
22
23
|
s.add_development_dependency 'bundler'
|
23
|
-
s.add_development_dependency 'rake'
|
24
|
-
s.add_development_dependency 'rubocop', '~> 0.40'
|
25
|
-
s.add_development_dependency 'rspec', '~> 3.0'
|
26
|
-
s.add_development_dependency 'rack-test'
|
27
24
|
s.add_development_dependency 'maruku'
|
28
|
-
s.add_development_dependency 'yard'
|
29
25
|
s.add_development_dependency 'pry' unless RUBY_PLATFORM.eql?('java') || RUBY_ENGINE.eql?('rbx')
|
30
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'
|
30
|
+
s.add_development_dependency 'yard'
|
31
31
|
|
32
32
|
s.files = `git ls-files`.split("\n")
|
33
33
|
s.test_files = `git ls-files -- {test,spec}/*`.split("\n")
|
data/lib/grape-entity.rb
CHANGED
data/lib/grape_entity.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_support/version'
|
2
4
|
require 'active_support/core_ext/string/inflections'
|
3
5
|
require 'active_support/core_ext/hash/reverse_merge'
|
@@ -7,3 +9,4 @@ require 'grape_entity/entity'
|
|
7
9
|
require 'grape_entity/delegator'
|
8
10
|
require 'grape_entity/exposure'
|
9
11
|
require 'grape_entity/options'
|
12
|
+
require 'grape_entity/deprecated'
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'grape_entity/condition/base'
|
2
4
|
require 'grape_entity/condition/block_condition'
|
3
5
|
require 'grape_entity/condition/hash_condition'
|
@@ -6,19 +8,26 @@ require 'grape_entity/condition/symbol_condition'
|
|
6
8
|
module Grape
|
7
9
|
class Entity
|
8
10
|
module Condition
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
when Proc then BlockCondition.new false, &arg
|
13
|
-
when Symbol then SymbolCondition.new false, arg
|
11
|
+
class << self
|
12
|
+
def new_if(arg)
|
13
|
+
condition(false, arg)
|
14
14
|
end
|
15
|
-
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
def new_unless(arg)
|
17
|
+
condition(true, arg)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def condition(inverse, arg)
|
23
|
+
condition_klass =
|
24
|
+
case arg
|
25
|
+
when Hash then HashCondition
|
26
|
+
when Proc then BlockCondition
|
27
|
+
when Symbol then SymbolCondition
|
28
|
+
end
|
29
|
+
|
30
|
+
condition_klass.new(inverse, arg)
|
22
31
|
end
|
23
32
|
end
|
24
33
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Condition
|
@@ -19,7 +21,7 @@ module Grape
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def met?(entity, options)
|
22
|
-
|
24
|
+
@inverse ? unless_value(entity, options) : if_value(entity, options)
|
23
25
|
end
|
24
26
|
|
25
27
|
def if_value(_entity, _options)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'grape_entity/delegator/base'
|
2
4
|
require 'grape_entity/delegator/hash_object'
|
3
5
|
require 'grape_entity/delegator/openstruct_object'
|
@@ -8,15 +10,14 @@ module Grape
|
|
8
10
|
class Entity
|
9
11
|
module Delegator
|
10
12
|
def self.new(object)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
13
|
+
delegator_klass =
|
14
|
+
if object.is_a?(Hash) then HashObject
|
15
|
+
elsif defined?(OpenStruct) && object.is_a?(OpenStruct) then OpenStructObject
|
16
|
+
elsif object.respond_to?(:fetch, true) then FetchableObject
|
17
|
+
else PlainObject
|
18
|
+
end
|
19
|
+
|
20
|
+
delegator_klass.new(object)
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Grape
|
2
4
|
class Entity
|
3
5
|
module Delegator
|
4
6
|
class HashObject < Base
|
5
|
-
def delegate(attribute)
|
6
|
-
object[attribute]
|
7
|
+
def delegate(attribute, hash_access: :to_sym)
|
8
|
+
object[attribute.send(hash_access)]
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|