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.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.github/dependabot.yml +20 -0
  4. data/.github/workflows/ci.yml +41 -0
  5. data/.gitignore +5 -1
  6. data/.rspec +2 -1
  7. data/.rubocop.yml +85 -2
  8. data/.rubocop_todo.yml +41 -33
  9. data/CHANGELOG.md +243 -37
  10. data/CONTRIBUTING.md +1 -1
  11. data/Dangerfile +3 -0
  12. data/Gemfile +11 -7
  13. data/Guardfile +4 -2
  14. data/LICENSE +1 -1
  15. data/README.md +272 -19
  16. data/Rakefile +9 -11
  17. data/UPGRADING.md +35 -0
  18. data/bench/serializing.rb +7 -0
  19. data/grape-entity.gemspec +13 -8
  20. data/lib/grape-entity.rb +2 -0
  21. data/lib/grape_entity/condition/base.rb +37 -0
  22. data/lib/grape_entity/condition/block_condition.rb +23 -0
  23. data/lib/grape_entity/condition/hash_condition.rb +27 -0
  24. data/lib/grape_entity/condition/symbol_condition.rb +23 -0
  25. data/lib/grape_entity/condition.rb +35 -0
  26. data/lib/grape_entity/delegator/base.rb +7 -0
  27. data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
  28. data/lib/grape_entity/delegator/hash_object.rb +4 -2
  29. data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
  30. data/lib/grape_entity/delegator/plain_object.rb +2 -0
  31. data/lib/grape_entity/delegator.rb +14 -9
  32. data/lib/grape_entity/deprecated.rb +13 -0
  33. data/lib/grape_entity/entity.rb +192 -258
  34. data/lib/grape_entity/exposure/base.rb +138 -0
  35. data/lib/grape_entity/exposure/block_exposure.rb +31 -0
  36. data/lib/grape_entity/exposure/delegator_exposure.rb +13 -0
  37. data/lib/grape_entity/exposure/formatter_block_exposure.rb +27 -0
  38. data/lib/grape_entity/exposure/formatter_exposure.rb +32 -0
  39. data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +83 -0
  40. data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +66 -0
  41. data/lib/grape_entity/exposure/nesting_exposure.rb +133 -0
  42. data/lib/grape_entity/exposure/represent_exposure.rb +50 -0
  43. data/lib/grape_entity/exposure.rb +105 -0
  44. data/lib/grape_entity/options.rb +132 -0
  45. data/lib/grape_entity/version.rb +3 -1
  46. data/lib/grape_entity.rb +9 -2
  47. data/spec/grape_entity/entity_spec.rb +903 -184
  48. data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +58 -0
  49. data/spec/grape_entity/exposure/represent_exposure_spec.rb +32 -0
  50. data/spec/grape_entity/exposure_spec.rb +102 -0
  51. data/spec/grape_entity/hash_spec.rb +91 -0
  52. data/spec/grape_entity/options_spec.rb +66 -0
  53. data/spec/spec_helper.rb +21 -2
  54. metadata +91 -18
  55. 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$}) { |m| "spec/#{m[1]}_spec.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') { 'spec/' }
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 and Intridea, Inc.
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
  [![Gem Version](http://img.shields.io/gem/v/grape-entity.svg)](http://badge.fury.io/rb/grape-entity)
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)
2
+ ![Ruby](https://github.com/ruby-grape/grape-entity/workflows/Ruby/badge.svg)
3
+ [![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
4
  [![Code Climate](https://codeclimate.com/github/ruby-grape/grape-entity.svg)](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 always access to the presented instance with `object`
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
- format_with(:iso_timestamp) { |dt| dt.iso8601 }
280
- with_options(format_with: :iso_timestamp) do
281
- expose :created_at
282
- expose :updated_at
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
- There are sometimes that you want to pass additional option or parameter to nested exposure. Assume that you need to expose an address for a contact info, but it has both two different format: **full** and **simple**. You can pass an additional `full_format` option to specify that if the nested entity should render address in `:full` format.
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 :stree do |instance, options|
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, for example, `instance.address` might be `nil`, in this situation, it is better to expose it as nil directly.
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 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.
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-2014 Michael Bleigh, Intridea, Inc., and contributors.
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
- Bundler.setup :default, :test, :development
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(:rcov) do |spec|
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: [:rubocop, :spec]
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
- $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,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.rubyforge_project = 'grape-entity'
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 'rake'
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,features}/*`.split("\n")
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
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'grape_entity'
@@ -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