grape 2.0.0 → 2.4.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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +151 -1
  3. data/CONTRIBUTING.md +1 -1
  4. data/README.md +404 -334
  5. data/UPGRADING.md +279 -7
  6. data/grape.gemspec +8 -8
  7. data/lib/grape/api/instance.rb +34 -66
  8. data/lib/grape/api.rb +47 -70
  9. data/lib/grape/content_types.rb +13 -10
  10. data/lib/grape/cookies.rb +31 -24
  11. data/lib/grape/dry_types.rb +0 -2
  12. data/lib/grape/dsl/api.rb +0 -2
  13. data/lib/grape/dsl/desc.rb +49 -44
  14. data/lib/grape/dsl/headers.rb +2 -2
  15. data/lib/grape/dsl/helpers.rb +8 -4
  16. data/lib/grape/dsl/inside_route.rb +67 -54
  17. data/lib/grape/dsl/parameters.rb +10 -9
  18. data/lib/grape/dsl/request_response.rb +14 -18
  19. data/lib/grape/dsl/routing.rb +34 -17
  20. data/lib/grape/dsl/validations.rb +13 -0
  21. data/lib/grape/endpoint.rb +120 -118
  22. data/lib/grape/{util/env.rb → env.rb} +0 -5
  23. data/lib/grape/error_formatter/base.rb +51 -21
  24. data/lib/grape/error_formatter/json.rb +7 -15
  25. data/lib/grape/error_formatter/serializable_hash.rb +7 -0
  26. data/lib/grape/error_formatter/txt.rb +11 -17
  27. data/lib/grape/error_formatter/xml.rb +3 -13
  28. data/lib/grape/error_formatter.rb +5 -25
  29. data/lib/grape/exceptions/base.rb +18 -30
  30. data/lib/grape/exceptions/conflicting_types.rb +11 -0
  31. data/lib/grape/exceptions/invalid_parameters.rb +11 -0
  32. data/lib/grape/exceptions/too_deep_parameters.rb +11 -0
  33. data/lib/grape/exceptions/unknown_auth_strategy.rb +11 -0
  34. data/lib/grape/exceptions/unknown_params_builder.rb +11 -0
  35. data/lib/grape/exceptions/validation.rb +5 -6
  36. data/lib/grape/exceptions/validation_array_errors.rb +1 -0
  37. data/lib/grape/exceptions/validation_errors.rb +4 -6
  38. data/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +2 -5
  39. data/lib/grape/extensions/hash.rb +7 -2
  40. data/lib/grape/extensions/hashie/mash.rb +3 -5
  41. data/lib/grape/formatter/base.rb +16 -0
  42. data/lib/grape/formatter/json.rb +4 -6
  43. data/lib/grape/formatter/serializable_hash.rb +1 -1
  44. data/lib/grape/formatter/txt.rb +3 -5
  45. data/lib/grape/formatter/xml.rb +4 -6
  46. data/lib/grape/formatter.rb +7 -25
  47. data/lib/grape/{util/json.rb → json.rb} +1 -3
  48. data/lib/grape/locale/en.yml +46 -42
  49. data/lib/grape/middleware/auth/base.rb +11 -34
  50. data/lib/grape/middleware/auth/dsl.rb +23 -31
  51. data/lib/grape/middleware/base.rb +41 -23
  52. data/lib/grape/middleware/error.rb +77 -76
  53. data/lib/grape/middleware/formatter.rb +48 -79
  54. data/lib/grape/middleware/globals.rb +1 -3
  55. data/lib/grape/middleware/stack.rb +26 -37
  56. data/lib/grape/middleware/versioner/accept_version_header.rb +6 -33
  57. data/lib/grape/middleware/versioner/base.rb +74 -0
  58. data/lib/grape/middleware/versioner/header.rb +59 -126
  59. data/lib/grape/middleware/versioner/param.rb +4 -25
  60. data/lib/grape/middleware/versioner/path.rb +10 -34
  61. data/lib/grape/middleware/versioner.rb +7 -14
  62. data/lib/grape/namespace.rb +4 -5
  63. data/lib/grape/params_builder/base.rb +18 -0
  64. data/lib/grape/params_builder/hash.rb +11 -0
  65. data/lib/grape/params_builder/hash_with_indifferent_access.rb +11 -0
  66. data/lib/grape/params_builder/hashie_mash.rb +11 -0
  67. data/lib/grape/params_builder.rb +32 -0
  68. data/lib/grape/parser/base.rb +16 -0
  69. data/lib/grape/parser/json.rb +6 -8
  70. data/lib/grape/parser/xml.rb +6 -8
  71. data/lib/grape/parser.rb +5 -23
  72. data/lib/grape/path.rb +38 -60
  73. data/lib/grape/request.rb +161 -30
  74. data/lib/grape/router/base_route.rb +39 -0
  75. data/lib/grape/router/greedy_route.rb +20 -0
  76. data/lib/grape/router/pattern.rb +45 -31
  77. data/lib/grape/router/route.rb +28 -57
  78. data/lib/grape/router.rb +56 -43
  79. data/lib/grape/util/base_inheritable.rb +4 -4
  80. data/lib/grape/util/cache.rb +0 -3
  81. data/lib/grape/util/endpoint_configuration.rb +1 -1
  82. data/lib/grape/util/header.rb +13 -0
  83. data/lib/grape/util/inheritable_values.rb +0 -2
  84. data/lib/grape/util/lazy/block.rb +29 -0
  85. data/lib/grape/util/lazy/value.rb +38 -0
  86. data/lib/grape/util/lazy/value_array.rb +21 -0
  87. data/lib/grape/util/lazy/value_enumerable.rb +34 -0
  88. data/lib/grape/util/lazy/value_hash.rb +21 -0
  89. data/lib/grape/util/media_type.rb +70 -0
  90. data/lib/grape/util/registry.rb +27 -0
  91. data/lib/grape/util/reverse_stackable_values.rb +1 -6
  92. data/lib/grape/util/stackable_values.rb +1 -6
  93. data/lib/grape/util/strict_hash_configuration.rb +3 -3
  94. data/lib/grape/validations/attributes_doc.rb +38 -36
  95. data/lib/grape/validations/attributes_iterator.rb +1 -0
  96. data/lib/grape/validations/contract_scope.rb +34 -0
  97. data/lib/grape/validations/params_scope.rb +36 -32
  98. data/lib/grape/validations/types/array_coercer.rb +0 -2
  99. data/lib/grape/validations/types/dry_type_coercer.rb +9 -15
  100. data/lib/grape/validations/types/json.rb +0 -2
  101. data/lib/grape/validations/types/primitive_coercer.rb +0 -2
  102. data/lib/grape/validations/types/set_coercer.rb +0 -3
  103. data/lib/grape/validations/types.rb +0 -3
  104. data/lib/grape/validations/validator_factory.rb +2 -2
  105. data/lib/grape/validations/validators/allow_blank_validator.rb +1 -1
  106. data/lib/grape/validations/validators/base.rb +8 -11
  107. data/lib/grape/validations/validators/coerce_validator.rb +1 -1
  108. data/lib/grape/validations/validators/contract_scope_validator.rb +41 -0
  109. data/lib/grape/validations/validators/default_validator.rb +6 -2
  110. data/lib/grape/validations/validators/exactly_one_of_validator.rb +1 -1
  111. data/lib/grape/validations/validators/except_values_validator.rb +2 -2
  112. data/lib/grape/validations/validators/length_validator.rb +49 -0
  113. data/lib/grape/validations/validators/presence_validator.rb +1 -1
  114. data/lib/grape/validations/validators/regexp_validator.rb +2 -2
  115. data/lib/grape/validations/validators/values_validator.rb +20 -57
  116. data/lib/grape/validations.rb +8 -21
  117. data/lib/grape/version.rb +1 -1
  118. data/lib/grape/{util/xml.rb → xml.rb} +1 -1
  119. data/lib/grape.rb +42 -274
  120. metadata +45 -44
  121. data/lib/grape/eager_load.rb +0 -20
  122. data/lib/grape/http/headers.rb +0 -71
  123. data/lib/grape/middleware/helpers.rb +0 -12
  124. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +0 -24
  125. data/lib/grape/router/attribute_translator.rb +0 -63
  126. data/lib/grape/util/lazy_block.rb +0 -27
  127. data/lib/grape/util/lazy_object.rb +0 -43
  128. data/lib/grape/util/lazy_value.rb +0 -91
  129. data/lib/grape/util/registrable.rb +0 -15
  130. data/lib/grape/validations/types/build_coercer.rb +0 -94
data/lib/grape/api.rb CHANGED
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/router'
4
- require 'grape/api/instance'
5
-
6
3
  module Grape
7
4
  # The API class is the primary entry point for creating Grape APIs. Users
8
5
  # should subclass this class in order to build an API.
9
6
  class API
10
7
  # Class methods that we want to call on the API rather than on the API object
11
- NON_OVERRIDABLE = %i[call call! configuration compile! inherited].freeze
8
+ NON_OVERRIDABLE = %i[call call! configuration compile! inherited recognize_path].freeze
12
9
 
13
10
  class Boolean
14
11
  def self.build(val)
@@ -23,19 +20,25 @@ module Grape
23
20
  end
24
21
 
25
22
  class << self
23
+ extend Forwardable
26
24
  attr_accessor :base_instance, :instances
27
25
 
28
- # Rather than initializing an object of type Grape::API, create an object of type Instance
29
- def new(*args, &block)
30
- base_instance.new(*args, &block)
31
- end
26
+ delegate_missing_to :base_instance
27
+ def_delegators :base_instance, :new, :configuration
28
+
29
+ # This is the interface point between Rack and Grape; it accepts a request
30
+ # from Rack and ultimately returns an array of three values: the status,
31
+ # the headers, and the body. See [the rack specification]
32
+ # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
33
+ # NOTE: This will only be called on an API directly mounted on RACK
34
+ def_delegators :instance_for_rack, :call, :compile!
32
35
 
33
36
  # When inherited, will create a list of all instances (times the API was mounted)
34
37
  # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
35
38
  def inherited(api)
36
39
  super
37
40
 
38
- api.initial_setup(Grape::API == self ? Grape::API::Instance : @base_instance)
41
+ api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)
39
42
  api.override_all_methods!
40
43
  end
41
44
 
@@ -43,7 +46,7 @@ module Grape
43
46
  # an instance that will be used to create the set up but will not be mounted
44
47
  def initial_setup(base_instance_parent)
45
48
  @instances = []
46
- @setup = Set.new
49
+ @setup = []
47
50
  @base_parent = base_instance_parent
48
51
  @base_instance = mount_instance
49
52
  end
@@ -52,7 +55,7 @@ module Grape
52
55
  def override_all_methods!
53
56
  (base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|
54
57
  define_singleton_method(method_override) do |*args, &block|
55
- add_setup(method_override, *args, &block)
58
+ add_setup(method: method_override, args: args, block: block)
56
59
  end
57
60
  end
58
61
  end
@@ -72,68 +75,28 @@ module Grape
72
75
  end
73
76
  end
74
77
 
75
- # This is the interface point between Rack and Grape; it accepts a request
76
- # from Rack and ultimately returns an array of three values: the status,
77
- # the headers, and the body. See [the rack specification]
78
- # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
79
- # NOTE: This will only be called on an API directly mounted on RACK
80
- def call(*args, &block)
81
- instance_for_rack.call(*args, &block)
82
- end
83
-
84
- # Alleviates problems with autoloading by tring to search for the constant
85
- def const_missing(*args)
86
- if base_instance.const_defined?(*args)
87
- base_instance.const_get(*args)
88
- else
89
- super
90
- end
91
- end
92
-
93
78
  # The remountable class can have a configuration hash to provide some dynamic class-level variables.
94
79
  # For instance, a description could be done using: `desc configuration[:description]` if it may vary
95
80
  # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
96
81
  # too much, you may actually want to provide a new API rather than remount it.
97
- def mount_instance(**opts)
98
- instance = Class.new(@base_parent)
99
- instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {})
100
- instance.base = self
101
- replay_setup_on(instance)
102
- instance
82
+ def mount_instance(configuration: nil)
83
+ Class.new(@base_parent).tap do |instance|
84
+ instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})
85
+ instance.base = self
86
+ replay_setup_on(instance)
87
+ end
103
88
  end
104
89
 
90
+ private
91
+
105
92
  # Replays the set up to produce an API as defined in this class, can be called
106
93
  # on classes that inherit from Grape::API
107
94
  def replay_setup_on(instance)
108
95
  @setup.each do |setup_step|
109
- replay_step_on(instance, setup_step)
110
- end
111
- end
112
-
113
- def respond_to?(method, include_private = false)
114
- super(method, include_private) || base_instance.respond_to?(method, include_private)
115
- end
116
-
117
- def respond_to_missing?(method, include_private = false)
118
- base_instance.respond_to?(method, include_private)
119
- end
120
-
121
- def method_missing(method, *args, &block)
122
- # If there's a missing method, it may be defined on the base_instance instead.
123
- if respond_to_missing?(method)
124
- base_instance.send(method, *args, &block)
125
- else
126
- super
96
+ replay_step_on(instance, **setup_step)
127
97
  end
128
98
  end
129
99
 
130
- def compile!
131
- require 'grape/eager_load'
132
- instance_for_rack.compile! # See API::Instance.compile!
133
- end
134
-
135
- private
136
-
137
100
  def instance_for_rack
138
101
  if never_mounted?
139
102
  base_instance
@@ -143,21 +106,35 @@ module Grape
143
106
  end
144
107
 
145
108
  # Adds a new stage to the set up require to get a Grape::API up and running
146
- def add_setup(method, *args, &block)
147
- setup_step = { method: method, args: args, block: block }
148
- @setup += [setup_step]
109
+ def add_setup(step)
110
+ @setup << step
149
111
  last_response = nil
150
112
  @instances.each do |instance|
151
- last_response = replay_step_on(instance, setup_step)
113
+ last_response = replay_step_on(instance, **step)
152
114
  end
115
+
116
+ refresh_mount_step if step[:method] != :mount
153
117
  last_response
154
118
  end
155
119
 
156
- def replay_step_on(instance, setup_step)
157
- return if skip_immediate_run?(instance, setup_step[:args])
120
+ # Updating all previously mounted classes in the case that new methods have been executed.
121
+ def refresh_mount_step
122
+ @setup.each do |setup_step|
123
+ next if setup_step[:method] != :mount
124
+
125
+ refresh_mount_step = setup_step.merge(method: :refresh_mounted_api)
126
+ @setup << refresh_mount_step
127
+ @instances.each do |instance|
128
+ replay_step_on(instance, **refresh_mount_step)
129
+ end
130
+ end
131
+ end
132
+
133
+ def replay_step_on(instance, method:, args:, block:)
134
+ return if skip_immediate_run?(instance, args)
158
135
 
159
- args = evaluate_arguments(instance.configuration, *setup_step[:args])
160
- response = instance.send(setup_step[:method], *args, &setup_step[:block])
136
+ eval_args = evaluate_arguments(instance.configuration, *args)
137
+ response = instance.send(method, *eval_args, &block)
161
138
  if skip_immediate_run?(instance, [response])
162
139
  response
163
140
  else
@@ -172,12 +149,12 @@ module Grape
172
149
  end
173
150
 
174
151
  def any_lazy?(args)
175
- args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? }
152
+ args.any? { |argument| argument.try(:lazy?) }
176
153
  end
177
154
 
178
155
  def evaluate_arguments(configuration, *args)
179
156
  args.map do |argument|
180
- if argument.respond_to?(:lazy?) && argument.lazy?
157
+ if argument.try(:lazy?)
181
158
  argument.evaluate_from(configuration)
182
159
  elsif argument.is_a?(Hash)
183
160
  argument.transform_values { |value| evaluate_arguments(configuration, value).first }
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'grape/util/registrable'
4
-
5
3
  module Grape
6
4
  module ContentTypes
7
- extend Util::Registrable
5
+ module_function
8
6
 
9
7
  # Content types are listed in order of preference.
10
- CONTENT_TYPES = {
8
+ DEFAULTS = {
11
9
  xml: 'application/xml',
12
10
  serializable_hash: 'application/json',
13
11
  json: 'application/json',
@@ -15,13 +13,18 @@ module Grape
15
13
  txt: 'text/plain'
16
14
  }.freeze
17
15
 
18
- class << self
19
- def content_types_for_settings(settings)
20
- settings&.inject(:merge!)
21
- end
16
+ MIME_TYPES = Grape::ContentTypes::DEFAULTS.except(:serializable_hash).invert.freeze
17
+
18
+ def content_types_for(from_settings)
19
+ from_settings.presence || DEFAULTS
20
+ end
21
+
22
+ def mime_types_for(from_settings)
23
+ return MIME_TYPES if from_settings == Grape::ContentTypes::DEFAULTS
22
24
 
23
- def content_types_for(from_settings)
24
- from_settings.presence || Grape::ContentTypes::CONTENT_TYPES.merge(default_elements)
25
+ from_settings.each_with_object({}) do |(k, v), types_without_params|
26
+ # remove optional parameter
27
+ types_without_params[v.split(';', 2).first] = k
25
28
  end
26
29
  end
27
30
  end
data/lib/grape/cookies.rb CHANGED
@@ -2,42 +2,49 @@
2
2
 
3
3
  module Grape
4
4
  class Cookies
5
- def initialize
6
- @cookies = {}
7
- @send_cookies = {}
8
- end
5
+ extend Forwardable
9
6
 
10
- def read(request)
11
- request.cookies.each do |name, value|
12
- @cookies[name.to_s] = value
13
- end
7
+ DELETED_COOKIES_ATTRS = {
8
+ max_age: '0',
9
+ value: '',
10
+ expires: Time.at(0)
11
+ }.freeze
12
+
13
+ def_delegators :cookies, :[], :each
14
+
15
+ def initialize(rack_cookies)
16
+ @cookies = rack_cookies
17
+ @send_cookies = nil
14
18
  end
15
19
 
16
- def write(header)
17
- @cookies.select { |key, _value| @send_cookies[key] == true }.each do |name, value|
18
- cookie_value = value.is_a?(Hash) ? value : { value: value }
19
- Rack::Utils.set_cookie_header! header, name, cookie_value
20
+ def response_cookies
21
+ return unless @send_cookies
22
+
23
+ send_cookies.each do |name|
24
+ yield name, cookies[name]
20
25
  end
21
26
  end
22
27
 
23
- def [](name)
24
- @cookies[name.to_s]
28
+ def []=(name, value)
29
+ cookies[name] = value
30
+ send_cookies << name
25
31
  end
26
32
 
27
- def []=(name, value)
28
- @cookies[name.to_s] = value
29
- @send_cookies[name.to_s] = true
33
+ # see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340
34
+ def delete(name, **opts)
35
+ self.[]=(name, opts.merge(DELETED_COOKIES_ATTRS))
30
36
  end
31
37
 
32
- def each(&block)
33
- @cookies.each(&block)
38
+ private
39
+
40
+ def cookies
41
+ return @cookies unless @cookies.is_a?(Proc)
42
+
43
+ @cookies = @cookies.call.with_indifferent_access
34
44
  end
35
45
 
36
- # rubocop:disable Layout/SpaceBeforeBrackets
37
- def delete(name, **opts)
38
- options = opts.merge(value: 'deleted', expires: Time.at(0))
39
- self.[]=(name, options)
46
+ def send_cookies
47
+ @send_cookies ||= Set.new
40
48
  end
41
- # rubocop:enable Layout/SpaceBeforeBrackets
42
49
  end
43
50
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry-types'
4
-
5
3
  module Grape
6
4
  module DryTypes
7
5
  # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is
data/lib/grape/dsl/api.rb CHANGED
@@ -5,8 +5,6 @@ module Grape
5
5
  module API
6
6
  extend ActiveSupport::Concern
7
7
 
8
- include Grape::Middleware::Auth::DSL
9
-
10
8
  include Grape::DSL::Validations
11
9
  include Grape::DSL::Callbacks
12
10
  include Grape::DSL::Configuration
@@ -5,6 +5,27 @@ module Grape
5
5
  module Desc
6
6
  include Grape::DSL::Settings
7
7
 
8
+ ROUTE_ATTRIBUTES = %i[
9
+ body_name
10
+ consumes
11
+ default
12
+ deprecated
13
+ description
14
+ detail
15
+ entity
16
+ headers
17
+ hidden
18
+ http_codes
19
+ is_array
20
+ named
21
+ nickname
22
+ params
23
+ produces
24
+ security
25
+ summary
26
+ tags
27
+ ].freeze
28
+
8
29
  # Add a description to the next namespace or function.
9
30
  # @param description [String] descriptive string for this endpoint
10
31
  # or namespace
@@ -49,58 +70,29 @@ module Grape
49
70
  # # ...
50
71
  # end
51
72
  #
52
- def desc(description, options = {}, &config_block)
53
- if config_block
54
- endpoint_configuration = if defined?(configuration)
55
- # When the instance is mounted - the configuration is executed on mount time
56
- if configuration.respond_to?(:evaluate)
57
- configuration.evaluate
58
- # Within `given` or `mounted blocks` the configuration is already evaluated
59
- elsif configuration.is_a?(Hash)
60
- configuration
61
- end
62
- end
63
- endpoint_configuration ||= {}
64
- config_class = desc_container(endpoint_configuration)
73
+ def desc(description, options = nil, &config_block)
74
+ opts =
75
+ if config_block
76
+ desc_container(endpoint_configuration).then do |config_class|
77
+ config_class.configure do
78
+ description(description)
79
+ end
65
80
 
66
- config_class.configure do
67
- description description
81
+ config_class.configure(&config_block)
82
+ config_class.settings
83
+ end
84
+ else
85
+ options&.merge(description: description) || { description: description }
68
86
  end
69
87
 
70
- config_class.configure(&config_block)
71
- Grape.deprecator.warn('Passing a options hash and a block to `desc` is deprecated. Move all hash options to block.') if options.any?
72
- options = config_class.settings
73
- else
74
- options = options.merge(description: description)
75
- end
76
-
77
- namespace_setting :description, options
78
- route_setting :description, options
88
+ namespace_setting :description, opts
89
+ route_setting :description, opts
79
90
  end
80
91
 
81
92
  # Returns an object which configures itself via an instance-context DSL.
82
93
  def desc_container(endpoint_configuration)
83
94
  Module.new do
84
- include Grape::Util::StrictHashConfiguration.module(
85
- :summary,
86
- :description,
87
- :detail,
88
- :params,
89
- :entity,
90
- :http_codes,
91
- :named,
92
- :body_name,
93
- :headers,
94
- :hidden,
95
- :deprecated,
96
- :is_array,
97
- :nickname,
98
- :produces,
99
- :consumes,
100
- :security,
101
- :tags,
102
- :default
103
- )
95
+ include Grape::Util::StrictHashConfiguration.module(*ROUTE_ATTRIBUTES)
104
96
  config_context.define_singleton_method(:configuration) do
105
97
  endpoint_configuration
106
98
  end
@@ -114,6 +106,19 @@ module Grape
114
106
  end
115
107
  end
116
108
  end
109
+
110
+ private
111
+
112
+ def endpoint_configuration
113
+ return {} unless defined?(configuration)
114
+
115
+ if configuration.respond_to?(:evaluate)
116
+ configuration.evaluate
117
+ # Within `given` or `mounted blocks` the configuration is already evaluated
118
+ elsif configuration.is_a?(Hash)
119
+ configuration
120
+ end
121
+ end
117
122
  end
118
123
  end
119
124
  end
@@ -10,9 +10,9 @@ module Grape
10
10
  # 4. Delete a specifc header key-value pair
11
11
  def header(key = nil, val = nil)
12
12
  if key
13
- val ? header[key.to_s] = val : header.delete(key.to_s)
13
+ val ? header[key] = val : header.delete(key)
14
14
  else
15
- @header ||= {}
15
+ @header ||= Grape::Util::Header.new
16
16
  end
17
17
  end
18
18
  alias headers header
@@ -33,18 +33,22 @@ module Grape
33
33
  # end
34
34
  #
35
35
  def helpers(*new_modules, &block)
36
- include_new_modules(new_modules) if new_modules.any?
37
- include_block(block) if block
36
+ include_new_modules(new_modules)
37
+ include_block(block)
38
38
  include_all_in_scope if !block && new_modules.empty?
39
39
  end
40
40
 
41
41
  protected
42
42
 
43
43
  def include_new_modules(modules)
44
+ return if modules.empty?
45
+
44
46
  modules.each { |mod| make_inclusion(mod) }
45
47
  end
46
48
 
47
49
  def include_block(block)
50
+ return unless block
51
+
48
52
  Module.new.tap do |mod|
49
53
  make_inclusion(mod) { mod.class_eval(&block) }
50
54
  end
@@ -58,7 +62,7 @@ module Grape
58
62
 
59
63
  def include_all_in_scope
60
64
  Module.new.tap do |mod|
61
- namespace_stackable(:helpers).each { |mod_to_include| mod.send :include, mod_to_include }
65
+ namespace_stackable(:helpers).each { |mod_to_include| mod.include mod_to_include }
62
66
  change!
63
67
  end
64
68
  end
@@ -94,7 +98,7 @@ module Grape
94
98
  protected
95
99
 
96
100
  def process_named_params
97
- return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any?
101
+ return if @named_params.blank?
98
102
 
99
103
  api.namespace_stackable(:named_params, @named_params)
100
104
  end