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.
- checksums.yaml +5 -5
- data/.coveralls.yml +1 -0
- data/.github/dependabot.yml +20 -0
- data/.github/workflows/ci.yml +41 -0
- data/.gitignore +5 -1
- data/.rspec +2 -1
- data/.rubocop.yml +85 -2
- data/.rubocop_todo.yml +41 -33
- data/CHANGELOG.md +243 -37
- data/CONTRIBUTING.md +1 -1
- data/Dangerfile +3 -0
- data/Gemfile +11 -7
- data/Guardfile +4 -2
- data/LICENSE +1 -1
- data/README.md +272 -19
- data/Rakefile +9 -11
- data/UPGRADING.md +35 -0
- data/bench/serializing.rb +7 -0
- data/grape-entity.gemspec +13 -8
- data/lib/grape-entity.rb +2 -0
- data/lib/grape_entity/condition/base.rb +37 -0
- data/lib/grape_entity/condition/block_condition.rb +23 -0
- data/lib/grape_entity/condition/hash_condition.rb +27 -0
- data/lib/grape_entity/condition/symbol_condition.rb +23 -0
- data/lib/grape_entity/condition.rb +35 -0
- data/lib/grape_entity/delegator/base.rb +7 -0
- data/lib/grape_entity/delegator/fetchable_object.rb +2 -0
- data/lib/grape_entity/delegator/hash_object.rb +4 -2
- data/lib/grape_entity/delegator/openstruct_object.rb +2 -0
- data/lib/grape_entity/delegator/plain_object.rb +2 -0
- data/lib/grape_entity/delegator.rb +14 -9
- data/lib/grape_entity/deprecated.rb +13 -0
- data/lib/grape_entity/entity.rb +192 -258
- data/lib/grape_entity/exposure/base.rb +138 -0
- data/lib/grape_entity/exposure/block_exposure.rb +31 -0
- data/lib/grape_entity/exposure/delegator_exposure.rb +13 -0
- data/lib/grape_entity/exposure/formatter_block_exposure.rb +27 -0
- data/lib/grape_entity/exposure/formatter_exposure.rb +32 -0
- data/lib/grape_entity/exposure/nesting_exposure/nested_exposures.rb +83 -0
- data/lib/grape_entity/exposure/nesting_exposure/output_builder.rb +66 -0
- data/lib/grape_entity/exposure/nesting_exposure.rb +133 -0
- data/lib/grape_entity/exposure/represent_exposure.rb +50 -0
- data/lib/grape_entity/exposure.rb +105 -0
- data/lib/grape_entity/options.rb +132 -0
- data/lib/grape_entity/version.rb +3 -1
- data/lib/grape_entity.rb +9 -2
- data/spec/grape_entity/entity_spec.rb +903 -184
- data/spec/grape_entity/exposure/nesting_exposure/nested_exposures_spec.rb +58 -0
- data/spec/grape_entity/exposure/represent_exposure_spec.rb +32 -0
- data/spec/grape_entity/exposure_spec.rb +102 -0
- data/spec/grape_entity/hash_spec.rb +91 -0
- data/spec/grape_entity/options_spec.rb +66 -0
- data/spec/spec_helper.rb +21 -2
- metadata +91 -18
- data/.travis.yml +0 -19
data/lib/grape_entity/entity.rb
CHANGED
@@ -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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
169
|
-
|
170
|
-
if @nested_attributes.empty?
|
171
|
-
root_exposures[attribute] = options
|
206
|
+
if block.parameters.any?
|
207
|
+
options[:proc] = block
|
172
208
|
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 })
|
209
|
+
options[:nesting] = true
|
178
210
|
end
|
211
|
+
end
|
179
212
|
|
180
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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.
|
192
|
-
|
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 ||=
|
218
|
-
if
|
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
|
-
|
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::
|
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
|
-
|
374
|
-
|
375
|
-
|
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
|
-
|
391
|
-
|
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
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
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
|
412
|
-
|
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
|
-
|
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
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
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
|
-
|
488
|
-
|
489
|
-
|
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
|
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
|
-
|
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
|
530
|
-
|
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(
|
550
|
-
|
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
|
-
|
587
|
-
|
588
|
-
|
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(
|
547
|
+
delegator.delegate(attribute)
|
591
548
|
end
|
592
549
|
end
|
593
550
|
|
594
|
-
def
|
595
|
-
|
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
|
-
|
618
|
-
|
619
|
-
|
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
|
-
|
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
|
-
|
632
|
-
|
633
|
-
|
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
|
643
|
-
|
644
|
-
|
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
|
-
|
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 [
|
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.
|
691
|
-
|
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)
|