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.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +5 -1
  4. data/.rubocop.yml +82 -2
  5. data/.rubocop_todo.yml +16 -33
  6. data/.travis.yml +18 -17
  7. data/CHANGELOG.md +75 -0
  8. data/Dangerfile +2 -0
  9. data/Gemfile +6 -1
  10. data/Guardfile +4 -2
  11. data/README.md +101 -4
  12. data/Rakefile +2 -2
  13. data/UPGRADING.md +31 -2
  14. data/bench/serializing.rb +7 -0
  15. data/grape-entity.gemspec +10 -10
  16. data/lib/grape-entity.rb +2 -0
  17. data/lib/grape_entity.rb +3 -0
  18. data/lib/grape_entity/condition.rb +20 -11
  19. data/lib/grape_entity/condition/base.rb +3 -1
  20. data/lib/grape_entity/condition/block_condition.rb +3 -1
  21. data/lib/grape_entity/condition/hash_condition.rb +2 -0
  22. data/lib/grape_entity/condition/symbol_condition.rb +2 -0
  23. data/lib/grape_entity/delegator.rb +10 -9
  24. data/lib/grape_entity/delegator/base.rb +2 -0
  25. data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
  26. data/lib/grape_entity/delegator/hash_object.rb +4 -2
  27. data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
  28. data/lib/grape_entity/delegator/plain_object.rb +2 -0
  29. data/lib/grape_entity/deprecated.rb +13 -0
  30. data/lib/grape_entity/entity.rb +115 -36
  31. data/lib/grape_entity/exposure.rb +64 -41
  32. data/lib/grape_entity/exposure/base.rb +21 -8
  33. data/lib/grape_entity/exposure/block_exposure.rb +2 -0
  34. data/lib/grape_entity/exposure/delegator_exposure.rb +2 -0
  35. data/lib/grape_entity/exposure/formatter_block_exposure.rb +2 -0
  36. data/lib/grape_entity/exposure/formatter_exposure.rb +2 -0
  37. data/lib/grape_entity/exposure/nesting_exposure.rb +36 -30
  38. data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +26 -15
  39. data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +10 -2
  40. data/lib/grape_entity/exposure/represent_exposure.rb +3 -1
  41. data/lib/grape_entity/options.rb +44 -58
  42. data/lib/grape_entity/version.rb +3 -1
  43. data/spec/grape_entity/entity_spec.rb +270 -47
  44. data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +6 -4
  45. data/spec/grape_entity/exposure/represent_exposure_spec.rb +5 -3
  46. data/spec/grape_entity/exposure_spec.rb +14 -2
  47. data/spec/grape_entity/hash_spec.rb +38 -1
  48. data/spec/grape_entity/options_spec.rb +66 -0
  49. data/spec/spec_helper.rb +17 -0
  50. 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
- [![Dependency Status](https://gemnasium.com/ruby-grape/grape-entity.svg)](https://gemnasium.com/ruby-grape/grape-entity)
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 always access to the presented instance with `object`
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
- # encoding: utf-8
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: [:spec, :rubocop]
20
+ task default: %i[spec rubocop]
@@ -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
 
@@ -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]
@@ -1,4 +1,6 @@
1
- $LOAD_PATH.push File.expand_path('../lib', __FILE__)
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.2.6'
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")
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'grape_entity'
@@ -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
- def self.new_if(arg)
10
- case arg
11
- when Hash then HashCondition.new false, arg
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
- def self.new_unless(arg)
18
- case arg
19
- when Hash then HashCondition.new true, arg
20
- when Proc then BlockCondition.new true, &arg
21
- when Symbol then SymbolCondition.new true, arg
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
- !@inverse ? if_value(entity, options) : unless_value(entity, options)
24
+ @inverse ? unless_value(entity, options) : if_value(entity, options)
23
25
  end
24
26
 
25
27
  def if_value(_entity, _options)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Condition
4
6
  class BlockCondition < Base
5
7
  attr_reader :block
6
8
 
7
- def setup(&block)
9
+ def setup(block)
8
10
  @block = block
9
11
  end
10
12
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Condition
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Condition
@@ -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
- if object.is_a?(Hash)
12
- HashObject.new object
13
- elsif defined?(OpenStruct) && object.is_a?(OpenStruct)
14
- OpenStructObject.new object
15
- elsif object.respond_to? :fetch, true
16
- FetchableObject.new object
17
- else
18
- PlainObject.new object
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Delegator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Delegator
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Delegator
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Grape
2
4
  class Entity
3
5
  module Delegator