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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4da3e754b2801be8d8801bf8cc483e457261bee9
4
- data.tar.gz: f5bda0779d4bb3bb76f43f4b6c1207d2b0e8f1d3
3
+ metadata.gz: ae09d2c2eb04cf66e6c186d175814bbd656d745c
4
+ data.tar.gz: cb06bd7e21f3b873631a59bcd736ff4c12f981af
5
5
  SHA512:
6
- metadata.gz: 3c12c01509079508b09981d5f4196f489b38be1d23d33f1dd9db9b0e9f2f1f637b83e87e57ad6a2c98ba59cd0a570520960ad4926ba959e895f9a7cffce31f5e
7
- data.tar.gz: 09f153d167e0e0f5e8007d3947660fa9b2723c46a9ca925b257709e6e5209b8d7fca8ef5ede080d8b50b294dfb04a3a5d1bfb7037b97996677dc048c50720440
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 12:30:52 +0300 using RuboCop version 0.31.0.
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: 7
8
+ # Offense count: 6
9
9
  Metrics/AbcSize:
10
- Max: 45
10
+ Max: 33
11
11
 
12
- # Offense count: 1
12
+ # Offense count: 2
13
13
  # Configuration parameters: CountComments.
14
14
  Metrics/ClassLength:
15
- Max: 333
15
+ Max: 202
16
16
 
17
- # Offense count: 4
17
+ # Offense count: 3
18
18
  Metrics/CyclomaticComplexity:
19
- Max: 17
19
+ Max: 11
20
20
 
21
- # Offense count: 193
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: 26
29
+ Max: 28
30
30
 
31
- # Offense count: 7
31
+ # Offense count: 5
32
32
  Metrics/PerceivedComplexity:
33
- Max: 15
33
+ Max: 13
34
34
 
35
- # Offense count: 37
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
- 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.
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 :stree do |instance, options|
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, for example, `instance.address` might be `nil`, in this situation, it is better to expose it as nil directly.
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 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.
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
@@ -19,4 +19,4 @@ require 'rainbow/ext/string' unless String.respond_to?(:color)
19
19
  require 'rubocop/rake_task'
20
20
  RuboCop::RakeTask.new(:rubocop)
21
21
 
22
- task default: [:rubocop, :spec]
22
+ task default: [:spec, :rubocop]
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
@@ -99,30 +99,25 @@ module Grape
99
99
  end
100
100
 
101
101
  class << self
102
- # Returns exposures that have been declared for this Entity or
103
- # ancestors. The keys are symbolized references to methods on the
104
- # containing object, the values are the options that were passed into expose.
105
- # @return [Hash] of exposures
106
- attr_accessor :exposures
107
- attr_accessor :root_exposures
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
- attr_accessor :formatters
111
- attr_accessor :nested_attribute_names
112
- attr_accessor :nested_exposures
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.exposures = exposures.dup
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
- options[:proc] = block if block_given? && block.parameters.any?
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
- @nested_attributes ||= []
167
+ @documentation = nil
168
+ @nesting_stack ||= []
167
169
 
168
170
  # rubocop:disable Style/Next
169
171
  args.each do |attribute|
170
- if @nested_attributes.empty?
171
- root_exposures[attribute] = options
172
+ exposure = Exposure.new(attribute, options)
173
+
174
+ if @nesting_stack.empty?
175
+ root_exposures << exposure
172
176
  else
173
- orig_attribute = attribute.to_sym
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 block_given? && block.parameters.empty?
184
- @nested_attributes << attribute
181
+ if exposure.nesting?
182
+ @nesting_stack << exposure
185
183
  block.call
186
- @nested_attributes.pop
184
+ @nesting_stack.pop
187
185
  end
188
186
  end
189
187
  end
190
188
 
191
- def self.unexpose(attribute)
192
- root_exposures.delete(attribute)
193
- exposures.delete(attribute)
194
- nested_exposures.delete(attribute)
195
- nested_attribute_names.delete(attribute)
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 ||= exposures.each_with_object({}) do |(attribute, exposure_options), memo|
218
- if exposure_options[:documentation].present?
219
- memo[key_for(attribute)] = exposure_options[:documentation]
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::Users
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, { collection: true }.merge(options)).presented }
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
- end
410
-
411
- def exposures
412
- self.class.exposures
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
- root_exposures.each_with_object({}) do |(attribute, exposure_options), output|
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 should_return_attribute?(attribute, options)
460
- key = self.class.key_for(attribute)
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 only_fields(options, for_attribute = nil)
469
- return nil unless options[:only]
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 except_fields(options, for_attribute = nil)
490
- return nil unless options[:except]
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
- if for_attribute && @except_fields[for_attribute].is_a?(Array)
504
- @except_fields[for_attribute]
505
- elsif for_attribute.nil?
506
- @except_fields
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.