grape-entity 0.4.8 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -0
  3. data/.github/dependabot.yml +20 -0
  4. data/.github/workflows/ci.yml +41 -0
  5. data/.gitignore +5 -1
  6. data/.rspec +2 -1
  7. data/.rubocop.yml +85 -2
  8. data/.rubocop_todo.yml +41 -33
  9. data/CHANGELOG.md +243 -37
  10. data/CONTRIBUTING.md +1 -1
  11. data/Dangerfile +3 -0
  12. data/Gemfile +11 -7
  13. data/Guardfile +4 -2
  14. data/LICENSE +1 -1
  15. data/README.md +272 -19
  16. data/Rakefile +9 -11
  17. data/UPGRADING.md +35 -0
  18. data/bench/serializing.rb +7 -0
  19. data/grape-entity.gemspec +13 -8
  20. data/lib/grape-entity.rb +2 -0
  21. data/lib/grape_entity/condition/base.rb +37 -0
  22. data/lib/grape_entity/condition/block_condition.rb +23 -0
  23. data/lib/grape_entity/condition/hash_condition.rb +27 -0
  24. data/lib/grape_entity/condition/symbol_condition.rb +23 -0
  25. data/lib/grape_entity/condition.rb +35 -0
  26. data/lib/grape_entity/delegator/base.rb +7 -0
  27. data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
  28. data/lib/grape_entity/delegator/hash_object.rb +4 -2
  29. data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
  30. data/lib/grape_entity/delegator/plain_object.rb +2 -0
  31. data/lib/grape_entity/delegator.rb +14 -9
  32. data/lib/grape_entity/deprecated.rb +13 -0
  33. data/lib/grape_entity/entity.rb +192 -258
  34. data/lib/grape_entity/exposure/base.rb +138 -0
  35. data/lib/grape_entity/exposure/block_exposure.rb +31 -0
  36. data/lib/grape_entity/exposure/delegator_exposure.rb +13 -0
  37. data/lib/grape_entity/exposure/formatter_block_exposure.rb +27 -0
  38. data/lib/grape_entity/exposure/formatter_exposure.rb +32 -0
  39. data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +83 -0
  40. data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +66 -0
  41. data/lib/grape_entity/exposure/nesting_exposure.rb +133 -0
  42. data/lib/grape_entity/exposure/represent_exposure.rb +50 -0
  43. data/lib/grape_entity/exposure.rb +105 -0
  44. data/lib/grape_entity/options.rb +132 -0
  45. data/lib/grape_entity/version.rb +3 -1
  46. data/lib/grape_entity.rb +9 -2
  47. data/spec/grape_entity/entity_spec.rb +903 -184
  48. data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +58 -0
  49. data/spec/grape_entity/exposure/represent_exposure_spec.rb +32 -0
  50. data/spec/grape_entity/exposure_spec.rb +102 -0
  51. data/spec/grape_entity/hash_spec.rb +91 -0
  52. data/spec/grape_entity/options_spec.rb +66 -0
  53. data/spec/spec_helper.rb +21 -2
  54. metadata +91 -18
  55. data/.travis.yml +0 -19
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'multi_json'
2
4
  require 'set'
3
5
 
@@ -99,37 +101,69 @@ module Grape
99
101
  end
100
102
 
101
103
  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
104
+ def root_exposure
105
+ @root_exposure ||= Exposure.new(nil, nesting: true)
106
+ end
107
+
108
+ attr_writer :root_exposure, :formatters
109
+
108
110
  # Returns all formatters that are registered for this and it's ancestors
109
111
  # @return [Hash] of formatters
110
- attr_accessor :formatters
111
- attr_accessor :nested_attribute_names
112
- attr_accessor :nested_exposures
112
+ def formatters
113
+ @formatters ||= {}
114
+ end
115
+
116
+ def hash_access
117
+ @hash_access ||= :to_sym
118
+ end
119
+
120
+ def hash_access=(value)
121
+ @hash_access =
122
+ case value
123
+ when :to_s, :str, :string
124
+ :to_s
125
+ else
126
+ :to_sym
127
+ end
128
+ end
129
+
130
+ def delegation_opts
131
+ @delegation_opts ||= { hash_access: hash_access }
132
+ end
113
133
  end
114
134
 
115
- @exposures = {}
116
- @root_exposures = {}
117
- @nested_exposures = {}
118
- @nested_attribute_names = {}
119
135
  @formatters = {}
120
136
 
121
137
  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
138
+ subclass.root_exposure = root_exposure.dup
126
139
  subclass.formatters = formatters.dup
140
+
141
+ super
127
142
  end
128
143
 
129
144
  # This method is the primary means by which you will declare what attributes
130
145
  # should be exposed by the entity.
131
146
  #
147
+ # @option options :expose_nil When set to false the associated exposure will not
148
+ # be rendered if its value is nil.
149
+ #
132
150
  # @option options :as Declare an alias for the representation of this attribute.
151
+ # If a proc is presented it is evaluated in the context of the entity so object
152
+ # and the entity methods are available to it.
153
+ #
154
+ # @example as: a proc or lambda
155
+ #
156
+ # object = OpenStruct(awesomeness: 'awesome_key', awesome: 'not-my-key', other: 'other-key' )
157
+ #
158
+ # class MyEntity < Grape::Entity
159
+ # expose :awesome, as: proc { object.awesomeness }
160
+ # expose :awesomeness, as: ->(object, opts) { object.other }
161
+ # end
162
+ #
163
+ # => { 'awesome_key': 'not-my-key', 'other-key': 'awesome_key' }
164
+ #
165
+ # Note the parameters passed in via the lambda syntax.
166
+ #
133
167
  # @option options :if When passed a Hash, the attribute will only be exposed if the
134
168
  # runtime options match all the conditions passed in. When passed a lambda, the
135
169
  # lambda will execute with two arguments: the object being represented and the
@@ -151,48 +185,82 @@ module Grape
151
185
  # block to the expose call to achieve the same effect.
152
186
  # @option options :documentation Define documenation for an exposed
153
187
  # field, typically the value is a hash with two fields, type and desc.
188
+ # @option options :merge This option allows you to merge an exposed field to the root
189
+ #
190
+ # rubocop:disable Layout/LineLength
154
191
  def self.expose(*args, &block)
155
192
  options = merge_options(args.last.is_a?(Hash) ? args.pop : {})
156
193
 
157
194
  if args.size > 1
158
- fail ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
159
- fail ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
160
- end
161
-
162
- fail ArgumentError, 'You may not use block-setting when also using format_with' if block_given? && options[:format_with].respond_to?(:call)
163
195
 
164
- options[:proc] = block if block_given? && block.parameters.any?
196
+ raise ArgumentError, 'You may not use the :as option on multi-attribute exposures.' if options[:as]
197
+ raise ArgumentError, 'You may not use the :expose_nil on multi-attribute exposures.' if options.key?(:expose_nil)
198
+ raise ArgumentError, 'You may not use block-setting on multi-attribute exposures.' if block_given?
199
+ end
165
200
 
166
- @nested_attributes ||= []
201
+ if block_given?
202
+ if options[:format_with].respond_to?(:call)
203
+ raise ArgumentError, 'You may not use block-setting when also using format_with'
204
+ end
167
205
 
168
- # rubocop:disable Style/Next
169
- args.each do |attribute|
170
- if @nested_attributes.empty?
171
- root_exposures[attribute] = options
206
+ if block.parameters.any?
207
+ options[:proc] = block
172
208
  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 })
209
+ options[:nesting] = true
178
210
  end
211
+ end
179
212
 
180
- exposures[attribute] = options
213
+ @documentation = nil
214
+ @nesting_stack ||= []
215
+ args.each { |attribute| build_exposure_for_attribute(attribute, @nesting_stack, options, block) }
216
+ end
217
+ # rubocop:enable Layout/LineLength
181
218
 
182
- # Nested exposures are given in a block with no parameters.
183
- if block_given? && block.parameters.empty?
184
- @nested_attributes << attribute
185
- block.call
186
- @nested_attributes.pop
187
- end
188
- end
219
+ def self.build_exposure_for_attribute(attribute, nesting_stack, options, block)
220
+ exposure_list = nesting_stack.empty? ? root_exposures : nesting_stack.last.nested_exposures
221
+
222
+ exposure = Exposure.new(attribute, options)
223
+
224
+ exposure_list.delete_by(attribute) if exposure.override?
225
+
226
+ exposure_list << exposure
227
+
228
+ # Nested exposures are given in a block with no parameters.
229
+ return unless exposure.nesting?
230
+
231
+ nesting_stack << exposure
232
+ block.call
233
+ nesting_stack.pop
234
+ end
235
+
236
+ # Returns exposures that have been declared for this Entity on the top level.
237
+ # @return [Array] of exposures
238
+ def self.root_exposures
239
+ root_exposure.nested_exposures
240
+ end
241
+
242
+ def self.find_exposure(attribute)
243
+ root_exposures.find_by(attribute)
244
+ end
245
+
246
+ def self.unexpose(*attributes)
247
+ cannot_unexpose! unless can_unexpose?
248
+ @documentation = nil
249
+ root_exposures.delete_by(*attributes)
250
+ end
251
+
252
+ def self.unexpose_all
253
+ cannot_unexpose! unless can_unexpose?
254
+ @documentation = nil
255
+ root_exposures.clear
256
+ end
257
+
258
+ def self.can_unexpose?
259
+ (@nesting_stack ||= []).empty?
189
260
  end
190
261
 
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)
262
+ def self.cannot_unexpose!
263
+ raise "You cannot call 'unexpose` inside of nesting exposure!"
196
264
  end
197
265
 
198
266
  # Set options that will be applied to any exposures declared inside the block.
@@ -214,10 +282,8 @@ module Grape
214
282
  # the values are document keys in the entity's documentation key. When calling
215
283
  # #docmentation, any exposure without a documentation key will be ignored.
216
284
  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]
220
- end
285
+ @documentation ||= root_exposures.each_with_object({}) do |exposure, memo|
286
+ memo[exposure.key] = exposure.documentation if exposure.documentation && !exposure.documentation.empty?
221
287
  end
222
288
  end
223
289
 
@@ -248,7 +314,8 @@ module Grape
248
314
  # end
249
315
  #
250
316
  def self.format_with(name, &block)
251
- fail ArgumentError, 'You must pass a block for formatters' unless block_given?
317
+ raise ArgumentError, 'You must pass a block for formatters' unless block_given?
318
+
252
319
  formatters[name.to_sym] = block
253
320
  end
254
321
 
@@ -315,7 +382,7 @@ module Grape
315
382
  #
316
383
  # class Users < Grape::Entity
317
384
  # present_collection true
318
- # expose :items, as: 'users', using: API::Entities::Users
385
+ # expose :items, as: 'users', using: API::Entities::User
319
386
  # expose :version, documentation: { type: 'string',
320
387
  # desc: 'actual api version',
321
388
  # required: true }
@@ -370,9 +437,10 @@ module Grape
370
437
  # @option options :only [Array] all the fields that should be returned
371
438
  # @option options :except [Array] all the fields that should not be returned
372
439
  def self.represent(objects, options = {})
373
- if objects.respond_to?(:to_ary) && ! @present_collection
374
- root_element = root_element(:collection_root)
375
- inner = objects.to_ary.map { |object| new(object, { collection: true }.merge(options)).presented }
440
+ @present_collection ||= nil
441
+ if objects.respond_to?(:to_ary) && !@present_collection
442
+ root_element = root_element(:collection_root)
443
+ inner = objects.to_ary.map { |object| new(object, options.reverse_merge(collection: true)).presented }
376
444
  else
377
445
  objects = { @collection_name => objects } if @present_collection
378
446
  root_element = root_element(:root)
@@ -387,8 +455,9 @@ module Grape
387
455
  # This method returns the entity's root or collection root node, or its parent's
388
456
  # @param root_type: either :collection_root or just :root
389
457
  def self.root_element(root_type)
390
- if instance_variable_get("@#{root_type}")
391
- instance_variable_get("@#{root_type}")
458
+ instance_variable = "@#{root_type}"
459
+ if instance_variable_defined?(instance_variable) && instance_variable_get(instance_variable)
460
+ instance_variable_get(instance_variable)
392
461
  elsif superclass.respond_to? :root_element
393
462
  superclass.root_element(root_type)
394
463
  end
@@ -402,20 +471,26 @@ module Grape
402
471
  end
403
472
  end
404
473
 
405
- def initialize(object, options = {})
406
- @object = object
407
- @delegator = Delegator.new object
408
- @options = options
474
+ # Prevent default serialization of :options or :delegator.
475
+ def inspect
476
+ fields = serializable_hash.map { |k, v| "#{k}=#{v}" }
477
+ "#<#{self.class.name}:#{object_id} #{fields.join(' ')}>"
409
478
  end
410
479
 
411
- def exposures
412
- self.class.exposures
480
+ def initialize(object, options = {})
481
+ @object = object
482
+ @options = options.is_a?(Options) ? options : Options.new(options)
483
+ @delegator = Delegator.new(object)
413
484
  end
414
485
 
415
486
  def root_exposures
416
487
  self.class.root_exposures
417
488
  end
418
489
 
490
+ def root_exposure
491
+ self.class.root_exposure
492
+ end
493
+
419
494
  def documentation
420
495
  self.class.documentation
421
496
  end
@@ -436,222 +511,81 @@ module Grape
436
511
 
437
512
  opts = options.merge(runtime_options || {})
438
513
 
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
457
- end
458
-
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
514
+ root_exposure.serializable_value(self, opts)
466
515
  end
467
516
 
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
517
+ def exec_with_object(options, &block)
518
+ if block.parameters.count == 1
519
+ instance_exec(object, &block)
520
+ else
521
+ instance_exec(object, options, &block)
486
522
  end
487
- end
488
-
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
502
-
503
- if for_attribute && @except_fields[for_attribute].is_a?(Array)
504
- @except_fields[for_attribute]
505
- elsif for_attribute.nil?
506
- @except_fields
523
+ rescue StandardError => e
524
+ # it handles: https://github.com/ruby/ruby/blob/v3_0_0_preview1/NEWS.md#language-changes point 3, Proc
525
+ # accounting for expose :foo, &:bar
526
+ if e.is_a?(ArgumentError) && block.parameters == [[:req], [:rest]]
527
+ raise Grape::Entity::Deprecated.new e.message, 'in ruby 3.0'
507
528
  end
508
- end
509
529
 
510
- alias_method :as_json, :serializable_hash
511
-
512
- def to_json(options = {})
513
- options = options.to_h if options && options.respond_to?(:to_h)
514
- MultiJson.dump(serializable_hash(options))
515
- end
516
-
517
- def to_xml(options = {})
518
- options = options.to_h if options && options.respond_to?(:to_h)
519
- serializable_hash(options).to_xml(options)
520
- end
521
-
522
- protected
523
-
524
- def self.name_for(attribute)
525
- attribute = attribute.to_sym
526
- nested_attribute_names[attribute] || attribute
530
+ raise e
527
531
  end
528
532
 
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]
533
+ def exec_with_attribute(attribute, &block)
534
+ instance_exec(delegate_attribute(attribute), &block)
547
535
  end
548
536
 
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
537
+ def value_for(key, options = Options.new)
538
+ root_exposure.valid_value_for(key, self, options)
583
539
  end
584
540
 
585
541
  def delegate_attribute(attribute)
586
- name = self.class.name_for(attribute)
587
- if respond_to?(name, true)
588
- send(name)
542
+ if is_defined_in_entity?(attribute)
543
+ send(attribute)
544
+ elsif delegator.accepts_options?
545
+ delegator.delegate(attribute, **self.class.delegation_opts)
589
546
  else
590
- delegator.delegate(name)
547
+ delegator.delegate(attribute)
591
548
  end
592
549
  end
593
550
 
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?
551
+ def is_defined_in_entity?(attribute)
552
+ return false unless respond_to?(attribute, true)
616
553
 
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
554
+ ancestors = self.class.ancestors
555
+ ancestors.index(Grape::Entity) > ancestors.index(method(attribute).owner)
556
+ end
624
557
 
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?
558
+ alias as_json serializable_hash
630
559
 
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
560
+ def to_json(options = {})
561
+ options = options.to_h if options&.respond_to?(:to_h)
562
+ MultiJson.dump(serializable_hash(options))
640
563
  end
641
564
 
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
565
+ def to_xml(options = {})
566
+ options = options.to_h if options&.respond_to?(:to_h)
567
+ serializable_hash(options).to_xml(options)
650
568
  end
651
569
 
652
570
  # All supported options.
653
- OPTIONS = [
654
- :as, :if, :unless, :using, :with, :proc, :documentation, :format_with, :safe, :if_extras, :unless_extras
571
+ OPTIONS = %i[
572
+ rewrite
573
+ as
574
+ if
575
+ unless
576
+ using
577
+ with
578
+ proc
579
+ documentation
580
+ format_with
581
+ safe
582
+ attr_path
583
+ if_extras
584
+ unless_extras
585
+ merge
586
+ expose_nil
587
+ override
588
+ default
655
589
  ].to_set.freeze
656
590
 
657
591
  # Merges the given options with current block options.
@@ -661,7 +595,7 @@ module Grape
661
595
  opts = {}
662
596
 
663
597
  merge_logic = proc do |key, existing_val, new_val|
664
- if [:if, :unless].include?(key)
598
+ if %i[if unless].include?(key)
665
599
  if existing_val.is_a?(Hash) && new_val.is_a?(Hash)
666
600
  existing_val.merge(new_val)
667
601
  elsif new_val.is_a?(Hash)
@@ -687,8 +621,8 @@ module Grape
687
621
  #
688
622
  # @param options [Hash] Exposure options.
689
623
  def self.valid_options(options)
690
- options.keys.each do |key|
691
- fail ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key)
624
+ options.each_key do |key|
625
+ raise ArgumentError, "#{key.inspect} is not a valid option." unless OPTIONS.include?(key)
692
626
  end
693
627
 
694
628
  options[:using] = options.delete(:with) if options.key?(:with)