grape-entity 0.4.8 → 0.5.0
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 +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.
|