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 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.