grape-entity 0.4.8 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +12 -12
- data/CHANGELOG.md +18 -1
- data/README.md +30 -4
- data/Rakefile +1 -1
- data/lib/grape_entity.rb +6 -2
- data/lib/grape_entity/condition.rb +26 -0
- data/lib/grape_entity/condition/base.rb +35 -0
- data/lib/grape_entity/condition/block_condition.rb +21 -0
- data/lib/grape_entity/condition/hash_condition.rb +25 -0
- data/lib/grape_entity/condition/symbol_condition.rb +21 -0
- data/lib/grape_entity/entity.rb +85 -238
- data/lib/grape_entity/exposure.rb +77 -0
- data/lib/grape_entity/exposure/base.rb +118 -0
- data/lib/grape_entity/exposure/block_exposure.rb +29 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +11 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +25 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +30 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +128 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +70 -0
- data/lib/grape_entity/exposure/represent_exposure.rb +47 -0
- data/lib/grape_entity/options.rb +142 -0
- data/lib/grape_entity/version.rb +1 -1
- data/spec/grape_entity/entity_spec.rb +378 -154
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +34 -0
- data/spec/grape_entity/exposure_spec.rb +90 -0
- metadata +47 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae09d2c2eb04cf66e6c186d175814bbd656d745c
|
4
|
+
data.tar.gz: cb06bd7e21f3b873631a59bcd736ff4c12f981af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd4ec8cf775bf7e9ea5db80cb14ef4eec682aafb29610cf0581530f372a1929926ba8abb6e4f8e8aa9b8a5b97a0bc224548346a9e05158f75ae250d24eaa08f8
|
7
|
+
data.tar.gz: f626ea6a74c2bf15945a3c0a3c29b8609939227bcb228361eedeb9dd247fc33ab7d2df2bb7c768b1a2073cc640ddfd39db51b908ff1d6a73f4a64602b66260fa
|
data/.rubocop_todo.yml
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
-
# on 2015-08-10
|
2
|
+
# on 2015-08-10 13:14:22 +0300 using RuboCop version 0.31.0.
|
3
3
|
# The point is for the user to remove these configuration records
|
4
4
|
# one by one as the offenses are removed from the code base.
|
5
5
|
# Note that changes in the inspected code, or installation of new
|
6
6
|
# versions of RuboCop, may require this file to be generated again.
|
7
7
|
|
8
|
-
# Offense count:
|
8
|
+
# Offense count: 6
|
9
9
|
Metrics/AbcSize:
|
10
|
-
Max:
|
10
|
+
Max: 33
|
11
11
|
|
12
|
-
# Offense count:
|
12
|
+
# Offense count: 2
|
13
13
|
# Configuration parameters: CountComments.
|
14
14
|
Metrics/ClassLength:
|
15
|
-
Max:
|
15
|
+
Max: 202
|
16
16
|
|
17
|
-
# Offense count:
|
17
|
+
# Offense count: 3
|
18
18
|
Metrics/CyclomaticComplexity:
|
19
|
-
Max:
|
19
|
+
Max: 11
|
20
20
|
|
21
|
-
# Offense count:
|
21
|
+
# Offense count: 210
|
22
22
|
# Configuration parameters: AllowURI, URISchemes.
|
23
23
|
Metrics/LineLength:
|
24
24
|
Max: 146
|
@@ -26,13 +26,13 @@ Metrics/LineLength:
|
|
26
26
|
# Offense count: 8
|
27
27
|
# Configuration parameters: CountComments.
|
28
28
|
Metrics/MethodLength:
|
29
|
-
Max:
|
29
|
+
Max: 28
|
30
30
|
|
31
|
-
# Offense count:
|
31
|
+
# Offense count: 5
|
32
32
|
Metrics/PerceivedComplexity:
|
33
|
-
Max:
|
33
|
+
Max: 13
|
34
34
|
|
35
|
-
# Offense count:
|
35
|
+
# Offense count: 58
|
36
36
|
Style/Documentation:
|
37
37
|
Enabled: false
|
38
38
|
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,27 @@
|
|
1
|
+
0.5.0 (2015-12-07)
|
2
|
+
==================
|
3
|
+
|
4
|
+
* [#139](https://github.com/ruby-grape/grape-entity/pull/139): Keep a track of attribute nesting path during condition check or runtime exposure - [@calfzhou](https://github.com/calfzhou).
|
5
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): `.exposures` is removed and substituted with `.root_exposures` array - [@marshall-lee](https://github.com/marshall-lee).
|
6
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): `.nested_exposures` is removed too - [@marshall-lee](https://github.com/marshall-lee).
|
7
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): `#should_return_attribute?`, `#only_fields` and `#except_fields` are moved to other classes - [@marshall-lee](https://github.com/marshall-lee).
|
8
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: double exposures with conditions does not rewrite previously defined now: [#56](https://github.com/ruby-grape/grape-entity/issues/56) - [@marshall-lee](https://github.com/marshall-lee).
|
9
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: nested exposures were flattened in `.documentation`: [#112](https://github.com/ruby-grape/grape-entity/issues/112) - [@marshall-lee](https://github.com/marshall-lee).
|
10
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: `@only_fields` and `@except_fields` memoization: [#149](https://github.com/ruby-grape/grape-entity/issues/149) - [@marshall-lee](https://github.com/marshall-lee).
|
11
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: `:unless` condition with `Hash` argument logic: [#150](https://github.com/ruby-grape/grape-entity/issues/150) - [@marshall-lee](https://github.com/marshall-lee).
|
12
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Nested `unexpose` now raises an exception: [#152](https://github.com/ruby-grape/grape-entity/issues/152) - [@marshall-lee](https://github.com/marshall-lee).
|
13
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: `@documentation` memoization: [#153](https://github.com/ruby-grape/grape-entity/issues/153) - [@marshall-lee](https://github.com/marshall-lee).
|
14
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: serializing of deeply nested presenter exposures: [#155](https://github.com/ruby-grape/grape-entity/issues/155) - [@marshall-lee](https://github.com/marshall-lee).
|
15
|
+
* [#151](https://github.com/ruby-grape/grape-entity/pull/151): Fix: deep projections (`:only`, `:except`) were unaware of nesting: [#156](https://github.com/ruby-grape/grape-entity/issues/156) - [@marshall-lee](https://github.com/marshall-lee).
|
16
|
+
|
1
17
|
0.4.8 (2015-08-10)
|
2
18
|
==================
|
19
|
+
|
3
20
|
* [#167](https://github.com/ruby-grape/grape-entity/pull/167): Regression: global settings (exposures, formatters) on `Grape::Entity` should work: [#166](https://github.com/ruby-grape/grape-entity/issues/166) - [@marshall-lee](http://github.com/marshall-lee).
|
4
21
|
|
5
22
|
0.4.7 (2015-08-03)
|
6
23
|
==================
|
24
|
+
|
7
25
|
* [#164](https://github.com/ruby-grape/grape-entity/pull/164): Regression: entity instance methods were exposed with `NoMethodError`: [#163](https://github.com/ruby-grape/grape-entity/issues/163) - [@marshall-lee](http://github.com/marshall-lee).
|
8
26
|
|
9
27
|
0.4.6 (2015-07-27)
|
@@ -87,4 +105,3 @@
|
|
87
105
|
==================
|
88
106
|
|
89
107
|
* Initial public release - [@agileanimal](https://github.com/agileanimal).
|
90
|
-
|
data/README.md
CHANGED
@@ -311,7 +311,7 @@ present s, with: Status, user: current_user
|
|
311
311
|
```
|
312
312
|
|
313
313
|
#### Passing Additional Option To Nested Exposure
|
314
|
-
|
314
|
+
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
315
|
|
316
316
|
```ruby
|
317
317
|
# api/contact.rb
|
@@ -327,12 +327,38 @@ end
|
|
327
327
|
# api/address.rb
|
328
328
|
expose :state, if: lambda {|instance, options| !!options[:full_format]} # the new option could be retrieved in options hash for conditional exposure
|
329
329
|
expose :city, if: lambda {|instance, options| !!options[:full_format]}
|
330
|
-
expose :
|
330
|
+
expose :street do |instance, options|
|
331
331
|
# the new option could be retrieved in options hash for runtime exposure
|
332
332
|
!!options[:full_format] ? instance.full_street_name : instance.simple_street_name
|
333
333
|
end
|
334
334
|
```
|
335
|
-
**Notice**: In the above code, you should pay attention to [**Safe Exposure**](#safe-exposure) yourself
|
335
|
+
**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.
|
336
|
+
|
337
|
+
#### Attribute Path Tracking
|
338
|
+
|
339
|
+
Sometimes, especially when there are nested attributes, you might want to know which attribute
|
340
|
+
is being exposed. For example, some APIs allow users to provide a parameter to control which fields
|
341
|
+
will be included in (or excluded from) the response.
|
342
|
+
|
343
|
+
GrapeEntity can track the path of each attribute, which you can access during conditions checking
|
344
|
+
or runtime exposure via `options[:attr_path]`.
|
345
|
+
|
346
|
+
The attribute path is an array. The last item of this array is the name (alias) of current attribute.
|
347
|
+
If the attribute is nested, the former items are names (aliases) of its ancestor attributes.
|
348
|
+
|
349
|
+
Example:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
class Status < Grape::Entity
|
353
|
+
expose :user # path is [:user]
|
354
|
+
expose :foo, as: :bar # path is [:bar]
|
355
|
+
expose :a do
|
356
|
+
expose :b, as: :xx do
|
357
|
+
expose :c # path is [:a, :xx, :c]
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
```
|
336
362
|
|
337
363
|
### Using the Exposure DSL
|
338
364
|
|
@@ -388,7 +414,7 @@ class Status
|
|
388
414
|
end
|
389
415
|
```
|
390
416
|
|
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
|
417
|
+
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
418
|
|
393
419
|
### Caveats
|
394
420
|
|
data/Rakefile
CHANGED
data/lib/grape_entity.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
require 'active_support/core_ext'
|
1
|
+
require 'active_support/version'
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
4
|
+
require 'active_support/core_ext/object/try'
|
3
5
|
require 'grape_entity/version'
|
4
6
|
require 'grape_entity/entity'
|
5
7
|
require 'grape_entity/delegator'
|
8
|
+
require 'grape_entity/exposure'
|
9
|
+
require 'grape_entity/options'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'grape_entity/condition/base'
|
2
|
+
require 'grape_entity/condition/block_condition'
|
3
|
+
require 'grape_entity/condition/hash_condition'
|
4
|
+
require 'grape_entity/condition/symbol_condition'
|
5
|
+
|
6
|
+
module Grape
|
7
|
+
class Entity
|
8
|
+
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
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
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
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Grape
|
2
|
+
class Entity
|
3
|
+
module Condition
|
4
|
+
class Base
|
5
|
+
def self.new(inverse, *args, &block)
|
6
|
+
super(inverse).tap { |e| e.setup(*args, &block) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(inverse = false)
|
10
|
+
@inverse = inverse
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
(self.class == other.class) && (self.inversed? == other.inversed?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inversed?
|
18
|
+
@inverse
|
19
|
+
end
|
20
|
+
|
21
|
+
def met?(entity, options)
|
22
|
+
!@inverse ? if_value(entity, options) : unless_value(entity, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def if_value(_entity, _options)
|
26
|
+
fail NotImplementedError
|
27
|
+
end
|
28
|
+
|
29
|
+
def unless_value(entity, options)
|
30
|
+
!if_value(entity, options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Grape
|
2
|
+
class Entity
|
3
|
+
module Condition
|
4
|
+
class BlockCondition < Base
|
5
|
+
attr_reader :block
|
6
|
+
|
7
|
+
def setup(&block)
|
8
|
+
@block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
super && @block == other.block
|
13
|
+
end
|
14
|
+
|
15
|
+
def if_value(entity, options)
|
16
|
+
entity.exec_with_object(options, &@block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Grape
|
2
|
+
class Entity
|
3
|
+
module Condition
|
4
|
+
class HashCondition < Base
|
5
|
+
attr_reader :cond_hash
|
6
|
+
|
7
|
+
def setup(cond_hash)
|
8
|
+
@cond_hash = cond_hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
super && @cond_hash == other.cond_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def if_value(_entity, options)
|
16
|
+
@cond_hash.all? { |k, v| options[k.to_sym] == v }
|
17
|
+
end
|
18
|
+
|
19
|
+
def unless_value(_entity, options)
|
20
|
+
@cond_hash.any? { |k, v| options[k.to_sym] != v }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Grape
|
2
|
+
class Entity
|
3
|
+
module Condition
|
4
|
+
class SymbolCondition < Base
|
5
|
+
attr_reader :symbol
|
6
|
+
|
7
|
+
def setup(symbol)
|
8
|
+
@symbol = symbol
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
super && @symbol == other.symbol
|
13
|
+
end
|
14
|
+
|
15
|
+
def if_value(_entity, options)
|
16
|
+
options[symbol]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/grape_entity/entity.rb
CHANGED
@@ -99,30 +99,25 @@ module Grape
|
|
99
99
|
end
|
100
100
|
|
101
101
|
class << self
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
def root_exposure
|
103
|
+
@root_exposure ||= Exposure.new(nil, nesting: true)
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_writer :root_exposure
|
107
|
+
|
108
108
|
# Returns all formatters that are registered for this and it's ancestors
|
109
109
|
# @return [Hash] of formatters
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
def formatters
|
111
|
+
@formatters ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_writer :formatters
|
113
115
|
end
|
114
116
|
|
115
|
-
@exposures = {}
|
116
|
-
@root_exposures = {}
|
117
|
-
@nested_exposures = {}
|
118
|
-
@nested_attribute_names = {}
|
119
117
|
@formatters = {}
|
120
118
|
|
121
119
|
def self.inherited(subclass)
|
122
|
-
subclass.
|
123
|
-
subclass.root_exposures = root_exposures.dup
|
124
|
-
subclass.nested_exposures = nested_exposures.dup
|
125
|
-
subclass.nested_attribute_names = nested_attribute_names.dup
|
120
|
+
subclass.root_exposure = root_exposure.dup
|
126
121
|
subclass.formatters = formatters.dup
|
127
122
|
end
|
128
123
|
|
@@ -161,38 +156,64 @@ module Grape
|
|
161
156
|
|
162
157
|
fail ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)
|
163
158
|
|
164
|
-
|
159
|
+
if block_given?
|
160
|
+
if block.parameters.any?
|
161
|
+
options[:proc] = block
|
162
|
+
else
|
163
|
+
options[:nesting] = true
|
164
|
+
end
|
165
|
+
end
|
165
166
|
|
166
|
-
@
|
167
|
+
@documentation = nil
|
168
|
+
@nesting_stack ||= []
|
167
169
|
|
168
170
|
# rubocop:disable Style/Next
|
169
171
|
args.each do |attribute|
|
170
|
-
|
171
|
-
|
172
|
+
exposure = Exposure.new(attribute, options)
|
173
|
+
|
174
|
+
if @nesting_stack.empty?
|
175
|
+
root_exposures << exposure
|
172
176
|
else
|
173
|
-
|
174
|
-
attribute = "#{@nested_attributes.last}__#{attribute}".to_sym
|
175
|
-
nested_attribute_names[attribute] = orig_attribute
|
176
|
-
options[:nested] = true
|
177
|
-
nested_exposures.deep_merge!(@nested_attributes.last.to_sym => { attribute => options })
|
177
|
+
@nesting_stack.last.nested_exposures << exposure
|
178
178
|
end
|
179
179
|
|
180
|
-
exposures[attribute] = options
|
181
|
-
|
182
180
|
# Nested exposures are given in a block with no parameters.
|
183
|
-
if
|
184
|
-
@
|
181
|
+
if exposure.nesting?
|
182
|
+
@nesting_stack << exposure
|
185
183
|
block.call
|
186
|
-
@
|
184
|
+
@nesting_stack.pop
|
187
185
|
end
|
188
186
|
end
|
189
187
|
end
|
190
188
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
nested_exposures
|
195
|
-
|
189
|
+
# Returns exposures that have been declared for this Entity on the top level.
|
190
|
+
# @return [Array] of exposures
|
191
|
+
def self.root_exposures
|
192
|
+
root_exposure.nested_exposures
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.find_exposure(attribute)
|
196
|
+
root_exposures.find_by(attribute)
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.unexpose(*attributes)
|
200
|
+
cannot_unexpose! unless can_unexpose?
|
201
|
+
@documentation = nil
|
202
|
+
root_exposures.delete_by(*attributes)
|
203
|
+
end
|
204
|
+
|
205
|
+
def self.unexpose_all
|
206
|
+
cannot_unexpose! unless can_unexpose?
|
207
|
+
@documentation = nil
|
208
|
+
root_exposures.clear
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.can_unexpose?
|
212
|
+
(@nesting_stack ||= []).empty?
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.cannot_unexpose!
|
216
|
+
fail "You cannot call 'unexpose` inside of nesting exposure!"
|
196
217
|
end
|
197
218
|
|
198
219
|
# Set options that will be applied to any exposures declared inside the block.
|
@@ -214,9 +235,9 @@ module Grape
|
|
214
235
|
# the values are document keys in the entity's documentation key. When calling
|
215
236
|
# #docmentation, any exposure without a documentation key will be ignored.
|
216
237
|
def self.documentation
|
217
|
-
@documentation ||=
|
218
|
-
if
|
219
|
-
memo[
|
238
|
+
@documentation ||= root_exposures.each_with_object({}) do |exposure, memo|
|
239
|
+
if exposure.documentation && !exposure.documentation.empty?
|
240
|
+
memo[exposure.key] = exposure.documentation
|
220
241
|
end
|
221
242
|
end
|
222
243
|
end
|
@@ -315,7 +336,7 @@ module Grape
|
|
315
336
|
#
|
316
337
|
# class Users < Grape::Entity
|
317
338
|
# present_collection true
|
318
|
-
# expose :items, as: 'users', using: API::Entities::
|
339
|
+
# expose :items, as: 'users', using: API::Entities::User
|
319
340
|
# expose :version, documentation: { type: 'string',
|
320
341
|
# desc: 'actual api version',
|
321
342
|
# required: true }
|
@@ -372,7 +393,7 @@ module Grape
|
|
372
393
|
def self.represent(objects, options = {})
|
373
394
|
if objects.respond_to?(:to_ary) && ! @present_collection
|
374
395
|
root_element = root_element(:collection_root)
|
375
|
-
inner = objects.to_ary.map { |object| new(object,
|
396
|
+
inner = objects.to_ary.map { |object| new(object, options.reverse_merge(collection: true)).presented }
|
376
397
|
else
|
377
398
|
objects = { @collection_name => objects } if @present_collection
|
378
399
|
root_element = root_element(:root)
|
@@ -405,17 +426,21 @@ module Grape
|
|
405
426
|
def initialize(object, options = {})
|
406
427
|
@object = object
|
407
428
|
@delegator = Delegator.new object
|
408
|
-
@options = options
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
429
|
+
@options = if options.is_a? Options
|
430
|
+
options
|
431
|
+
else
|
432
|
+
Options.new options
|
433
|
+
end
|
413
434
|
end
|
414
435
|
|
415
436
|
def root_exposures
|
416
437
|
self.class.root_exposures
|
417
438
|
end
|
418
439
|
|
440
|
+
def root_exposure
|
441
|
+
self.class.root_exposure
|
442
|
+
end
|
443
|
+
|
419
444
|
def documentation
|
420
445
|
self.class.documentation
|
421
446
|
end
|
@@ -436,74 +461,26 @@ module Grape
|
|
436
461
|
|
437
462
|
opts = options.merge(runtime_options || {})
|
438
463
|
|
439
|
-
|
440
|
-
next unless should_return_attribute?(attribute, opts) && conditions_met?(exposure_options, opts)
|
441
|
-
|
442
|
-
partial_output = value_for(attribute, opts)
|
443
|
-
|
444
|
-
output[self.class.key_for(attribute)] =
|
445
|
-
if partial_output.respond_to?(:serializable_hash)
|
446
|
-
partial_output.serializable_hash(runtime_options)
|
447
|
-
elsif partial_output.is_a?(Array) && partial_output.all? { |o| o.respond_to?(:serializable_hash) }
|
448
|
-
partial_output.map(&:serializable_hash)
|
449
|
-
elsif partial_output.is_a?(Hash)
|
450
|
-
partial_output.each do |key, value|
|
451
|
-
partial_output[key] = value.serializable_hash if value.respond_to?(:serializable_hash)
|
452
|
-
end
|
453
|
-
else
|
454
|
-
partial_output
|
455
|
-
end
|
456
|
-
end
|
464
|
+
root_exposure.serializable_value(self, opts)
|
457
465
|
end
|
458
466
|
|
459
|
-
def
|
460
|
-
|
461
|
-
only = only_fields(options).nil? ||
|
462
|
-
only_fields(options).include?(key)
|
463
|
-
except = except_fields(options) && except_fields(options).include?(key) &&
|
464
|
-
except_fields(options)[key] == true
|
465
|
-
only && !except
|
467
|
+
def exec_with_object(options, &block)
|
468
|
+
instance_exec(object, options, &block)
|
466
469
|
end
|
467
470
|
|
468
|
-
def
|
469
|
-
|
470
|
-
|
471
|
-
@only_fields ||= options[:only].each_with_object({}) do |attribute, allowed_fields|
|
472
|
-
if attribute.is_a?(Hash)
|
473
|
-
attribute.each do |attr, nested_attrs|
|
474
|
-
allowed_fields[attr] ||= []
|
475
|
-
allowed_fields[attr] += nested_attrs
|
476
|
-
end
|
477
|
-
else
|
478
|
-
allowed_fields[attribute] = true
|
479
|
-
end
|
480
|
-
end.symbolize_keys
|
481
|
-
|
482
|
-
if for_attribute && @only_fields[for_attribute].is_a?(Array)
|
483
|
-
@only_fields[for_attribute]
|
484
|
-
elsif for_attribute.nil?
|
485
|
-
@only_fields
|
486
|
-
end
|
471
|
+
def exec_with_attribute(attribute, &block)
|
472
|
+
instance_exec(delegate_attribute(attribute), &block)
|
487
473
|
end
|
488
474
|
|
489
|
-
def
|
490
|
-
|
491
|
-
|
492
|
-
@except_fields ||= options[:except].each_with_object({}) do |attribute, allowed_fields|
|
493
|
-
if attribute.is_a?(Hash)
|
494
|
-
attribute.each do |attr, nested_attrs|
|
495
|
-
allowed_fields[attr] ||= []
|
496
|
-
allowed_fields[attr] += nested_attrs
|
497
|
-
end
|
498
|
-
else
|
499
|
-
allowed_fields[attribute] = true
|
500
|
-
end
|
501
|
-
end.symbolize_keys
|
475
|
+
def value_for(key, options = Options.new)
|
476
|
+
root_exposure.valid_value_for(key, self, options)
|
477
|
+
end
|
502
478
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
479
|
+
def delegate_attribute(attribute)
|
480
|
+
if respond_to?(attribute, true)
|
481
|
+
send(attribute)
|
482
|
+
else
|
483
|
+
delegator.delegate(attribute)
|
507
484
|
end
|
508
485
|
end
|
509
486
|
|
@@ -519,139 +496,9 @@ module Grape
|
|
519
496
|
serializable_hash(options).to_xml(options)
|
520
497
|
end
|
521
498
|
|
522
|
-
protected
|
523
|
-
|
524
|
-
def self.name_for(attribute)
|
525
|
-
attribute = attribute.to_sym
|
526
|
-
nested_attribute_names[attribute] || attribute
|
527
|
-
end
|
528
|
-
|
529
|
-
def self.key_for(attribute)
|
530
|
-
exposures[attribute.to_sym][:as] || name_for(attribute)
|
531
|
-
end
|
532
|
-
|
533
|
-
def self.nested_exposures_for?(attribute)
|
534
|
-
nested_exposures.key?(attribute)
|
535
|
-
end
|
536
|
-
|
537
|
-
def nested_value_for(attribute, options)
|
538
|
-
nested_exposures = self.class.nested_exposures[attribute]
|
539
|
-
nested_attributes =
|
540
|
-
nested_exposures.map do |nested_attribute, nested_exposure_options|
|
541
|
-
if conditions_met?(nested_exposure_options, options)
|
542
|
-
[self.class.key_for(nested_attribute), value_for(nested_attribute, options)]
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
Hash[nested_attributes.compact]
|
547
|
-
end
|
548
|
-
|
549
|
-
def value_for(attribute, options = {})
|
550
|
-
exposure_options = exposures[attribute.to_sym]
|
551
|
-
return unless valid_exposure?(attribute, exposure_options)
|
552
|
-
|
553
|
-
if exposure_options[:using]
|
554
|
-
exposure_options[:using] = exposure_options[:using].constantize if exposure_options[:using].respond_to? :constantize
|
555
|
-
|
556
|
-
using_options = options_for_using(attribute, options)
|
557
|
-
|
558
|
-
if exposure_options[:proc]
|
559
|
-
exposure_options[:using].represent(instance_exec(object, options, &exposure_options[:proc]), using_options)
|
560
|
-
else
|
561
|
-
exposure_options[:using].represent(delegate_attribute(attribute), using_options)
|
562
|
-
end
|
563
|
-
|
564
|
-
elsif exposure_options[:proc]
|
565
|
-
instance_exec(object, options, &exposure_options[:proc])
|
566
|
-
|
567
|
-
elsif exposure_options[:format_with]
|
568
|
-
format_with = exposure_options[:format_with]
|
569
|
-
|
570
|
-
if format_with.is_a?(Symbol) && formatters[format_with]
|
571
|
-
instance_exec(delegate_attribute(attribute), &formatters[format_with])
|
572
|
-
elsif format_with.is_a?(Symbol)
|
573
|
-
send(format_with, delegate_attribute(attribute))
|
574
|
-
elsif format_with.respond_to? :call
|
575
|
-
instance_exec(delegate_attribute(attribute), &format_with)
|
576
|
-
end
|
577
|
-
|
578
|
-
elsif self.class.nested_exposures_for?(attribute)
|
579
|
-
nested_value_for(attribute, options)
|
580
|
-
else
|
581
|
-
delegate_attribute(attribute)
|
582
|
-
end
|
583
|
-
end
|
584
|
-
|
585
|
-
def delegate_attribute(attribute)
|
586
|
-
name = self.class.name_for(attribute)
|
587
|
-
if respond_to?(name, true)
|
588
|
-
send(name)
|
589
|
-
else
|
590
|
-
delegator.delegate(name)
|
591
|
-
end
|
592
|
-
end
|
593
|
-
|
594
|
-
def valid_exposure?(attribute, exposure_options)
|
595
|
-
if self.class.nested_exposures_for?(attribute)
|
596
|
-
self.class.nested_exposures[attribute].all? { |a, o| valid_exposure?(a, o) }
|
597
|
-
elsif exposure_options.key?(:proc)
|
598
|
-
true
|
599
|
-
else
|
600
|
-
name = self.class.name_for(attribute)
|
601
|
-
is_delegatable = delegator.delegatable?(name) || respond_to?(name, true)
|
602
|
-
if exposure_options[:safe]
|
603
|
-
is_delegatable
|
604
|
-
else
|
605
|
-
is_delegatable || fail(NoMethodError, "#{self.class.name} missing attribute `#{name}' on #{object}")
|
606
|
-
end
|
607
|
-
end
|
608
|
-
end
|
609
|
-
|
610
|
-
def conditions_met?(exposure_options, options)
|
611
|
-
if_conditions = []
|
612
|
-
unless exposure_options[:if_extras].nil?
|
613
|
-
if_conditions.concat(exposure_options[:if_extras])
|
614
|
-
end
|
615
|
-
if_conditions << exposure_options[:if] unless exposure_options[:if].nil?
|
616
|
-
|
617
|
-
if_conditions.each do |if_condition|
|
618
|
-
case if_condition
|
619
|
-
when Hash then if_condition.each_pair { |k, v| return false if options[k.to_sym] != v }
|
620
|
-
when Proc then return false unless instance_exec(object, options, &if_condition)
|
621
|
-
when Symbol then return false unless options[if_condition]
|
622
|
-
end
|
623
|
-
end
|
624
|
-
|
625
|
-
unless_conditions = []
|
626
|
-
unless exposure_options[:unless_extras].nil?
|
627
|
-
unless_conditions.concat(exposure_options[:unless_extras])
|
628
|
-
end
|
629
|
-
unless_conditions << exposure_options[:unless] unless exposure_options[:unless].nil?
|
630
|
-
|
631
|
-
unless_conditions.each do |unless_condition|
|
632
|
-
case unless_condition
|
633
|
-
when Hash then unless_condition.each_pair { |k, v| return false if options[k.to_sym] == v }
|
634
|
-
when Proc then return false if instance_exec(object, options, &unless_condition)
|
635
|
-
when Symbol then return false if options[unless_condition]
|
636
|
-
end
|
637
|
-
end
|
638
|
-
|
639
|
-
true
|
640
|
-
end
|
641
|
-
|
642
|
-
def options_for_using(attribute, options)
|
643
|
-
using_options = options.dup
|
644
|
-
using_options.delete(:collection)
|
645
|
-
using_options[:root] = nil
|
646
|
-
using_options[:only] = only_fields(using_options, attribute)
|
647
|
-
using_options[:except] = except_fields(using_options, attribute)
|
648
|
-
|
649
|
-
using_options
|
650
|
-
end
|
651
|
-
|
652
499
|
# All supported options.
|
653
500
|
OPTIONS = [
|
654
|
-
:as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :if_extras, :unless_extras
|
501
|
+
:rewrite, :as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :attr_path, :if_extras, :unless_extras
|
655
502
|
].to_set.freeze
|
656
503
|
|
657
504
|
# Merges the given options with current block options.
|