grape 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +3 -7
  3. data/CHANGELOG.md +21 -1
  4. data/Gemfile.lock +26 -26
  5. data/README.md +109 -29
  6. data/UPGRADING.md +45 -0
  7. data/gemfiles/rack_1.5.2.gemfile.lock +232 -0
  8. data/gemfiles/rails_3.gemfile +1 -1
  9. data/gemfiles/rails_3.gemfile.lock +288 -0
  10. data/gemfiles/rails_4.gemfile +1 -1
  11. data/gemfiles/rails_4.gemfile.lock +280 -0
  12. data/gemfiles/rails_5.gemfile +1 -1
  13. data/gemfiles/rails_5.gemfile.lock +312 -0
  14. data/lib/grape.rb +1 -0
  15. data/lib/grape/api.rb +74 -195
  16. data/lib/grape/api/instance.rb +242 -0
  17. data/lib/grape/dsl/desc.rb +17 -1
  18. data/lib/grape/dsl/middleware.rb +7 -0
  19. data/lib/grape/dsl/parameters.rb +9 -4
  20. data/lib/grape/dsl/routing.rb +5 -1
  21. data/lib/grape/endpoint.rb +1 -1
  22. data/lib/grape/exceptions/base.rb +9 -1
  23. data/lib/grape/exceptions/invalid_response.rb +9 -0
  24. data/lib/grape/locale/en.yml +1 -0
  25. data/lib/grape/middleware/error.rb +8 -2
  26. data/lib/grape/middleware/versioner/header.rb +2 -2
  27. data/lib/grape/validations/params_scope.rb +1 -0
  28. data/lib/grape/validations/validators/multiple_params_base.rb +1 -1
  29. data/lib/grape/version.rb +1 -1
  30. data/pkg/grape-1.2.0.gem +0 -0
  31. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  32. data/spec/grape/api_remount_spec.rb +85 -0
  33. data/spec/grape/api_spec.rb +70 -1
  34. data/spec/grape/dsl/desc_spec.rb +17 -1
  35. data/spec/grape/dsl/middleware_spec.rb +8 -0
  36. data/spec/grape/dsl/routing_spec.rb +10 -0
  37. data/spec/grape/exceptions/base_spec.rb +61 -0
  38. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  39. data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
  40. data/spec/grape/middleware/exception_spec.rb +1 -1
  41. data/spec/grape/middleware/versioner/header_spec.rb +6 -0
  42. data/spec/grape/validations/params_scope_spec.rb +133 -0
  43. data/spec/spec_helper.rb +3 -1
  44. metadata +99 -87
  45. data/gemfiles/rack_1.5.2.gemfile +0 -35
  46. data/pkg/grape-0.17.0.gem +0 -0
  47. data/pkg/grape-0.19.0.gem +0 -0
@@ -75,6 +75,7 @@ module Grape
75
75
  autoload :InvalidAcceptHeader
76
76
  autoload :InvalidVersionHeader
77
77
  autoload :MethodNotAllowed
78
+ autoload :InvalidResponse
78
79
  end
79
80
 
80
81
  module Extensions
@@ -1,233 +1,112 @@
1
1
  require 'grape/router'
2
+ require 'grape/api/instance'
2
3
 
3
4
  module Grape
4
5
  # The API class is the primary entry point for creating Grape APIs. Users
5
6
  # should subclass this class in order to build an API.
6
7
  class API
7
- include Grape::DSL::API
8
+ # Class methods that we want to call on the API rather than on the API object
9
+ NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of?
10
+ respond_to? respond_to_missing? const_defined? const_missing parent
11
+ parent_name name equal? to_s parents anonymous?].freeze
8
12
 
9
13
  class << self
10
- attr_reader :instance
11
-
12
- # A class-level lock to ensure the API is not compiled by multiple
13
- # threads simultaneously within the same process.
14
- LOCK = Mutex.new
15
-
16
- # Clears all defined routes, endpoints, etc., on this API.
17
- def reset!
18
- reset_endpoints!
19
- reset_routes!
20
- reset_validations!
21
- end
22
-
23
- # Parses the API's definition and compiles it into an instance of
24
- # Grape::API.
25
- def compile
26
- @instance ||= new
27
- end
28
-
29
- # Wipe the compiled API so we can recompile after changes were made.
30
- def change!
31
- @instance = nil
14
+ attr_accessor :base_instance, :instances
15
+ # When inherited, will create a list of all instances (times the API was mounted)
16
+ # It will listen to the setup required to mount that endpoint, and replicate it on any new instance
17
+ def inherited(api, base_instance_parent = Grape::API::Instance)
18
+ api.initial_setup(base_instance_parent)
19
+ api.override_all_methods!
20
+ make_inheritable(api)
32
21
  end
33
22
 
34
- # This is the interface point between Rack and Grape; it accepts a request
35
- # from Rack and ultimately returns an array of three values: the status,
36
- # the headers, and the body. See [the rack specification]
37
- # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
38
- def call(env)
39
- LOCK.synchronize { compile } unless instance
40
- call!(env)
23
+ # Initialize the instance variables on the remountable class, and the base_instance
24
+ # an instance that will be used to create the set up but will not be mounted
25
+ def initial_setup(base_instance_parent)
26
+ @instances = []
27
+ @setup = []
28
+ @base_parent = base_instance_parent
29
+ @base_instance = mount_instance
41
30
  end
42
31
 
43
- # A non-synchronized version of ::call.
44
- def call!(env)
45
- instance.call(env)
46
- end
47
-
48
- # (see #cascade?)
49
- def cascade(value = nil)
50
- if value.nil?
51
- inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
52
- else
53
- namespace_inheritable(:cascade, value)
32
+ # Redefines all methods so that are forwarded to add_setup and be recorded
33
+ def override_all_methods!
34
+ (base_instance.methods - NON_OVERRIDABLE).each do |method_override|
35
+ define_singleton_method(method_override) do |*args, &block|
36
+ add_setup(method_override, *args, &block)
37
+ end
54
38
  end
55
39
  end
56
40
 
57
- # see Grape::Router#recognize_path
58
- def recognize_path(path)
59
- LOCK.synchronize { compile } unless instance
60
- instance.router.recognize_path(path)
61
- end
62
-
63
- protected
64
-
65
- def prepare_routes
66
- endpoints.map(&:routes).flatten
41
+ # Allows an API to itself be inheritable:
42
+ def make_inheritable(api)
43
+ # When a child API inherits from a parent API.
44
+ def api.inherited(child_api)
45
+ # The instances of the child API inherit from the instances of the parent API
46
+ Grape::API.inherited(child_api, base_instance)
47
+ end
67
48
  end
68
49
 
69
- # Execute first the provided block, then each of the
70
- # block passed in. Allows for simple 'before' setups
71
- # of settings stack pushes.
72
- def nest(*blocks, &block)
73
- blocks.reject!(&:nil?)
74
- if blocks.any?
75
- instance_eval(&block) if block_given?
76
- blocks.each { |b| instance_eval(&b) }
77
- reset_validations!
50
+ # Alleviates problems with autoloading by tring to search for the constant
51
+ def const_missing(*args)
52
+ if base_instance.const_defined?(*args)
53
+ base_instance.const_get(*args)
54
+ elsif parent && parent.const_defined?(*args)
55
+ parent.const_get(*args)
78
56
  else
79
- instance_eval(&block)
57
+ super
80
58
  end
81
59
  end
82
60
 
83
- def inherited(subclass)
84
- subclass.reset!
85
- subclass.logger = logger.clone
61
+ # The remountable class can have a configuration hash to provide some dynamic class-level variables.
62
+ # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary
63
+ # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration
64
+ # too much, you may actually want to provide a new API rather than remount it.
65
+ def mount_instance(opts = {})
66
+ instance = Class.new(@base_parent)
67
+ instance.configuration = opts[:configuration] || {}
68
+ instance.base = self
69
+ replay_setup_on(instance)
70
+ instance
86
71
  end
87
72
 
88
- def inherit_settings(other_settings)
89
- top_level_setting.inherit_from other_settings.point_in_time_copy
90
-
91
- # Propagate any inherited params down to our endpoints, and reset any
92
- # compiled routes.
93
- endpoints.each do |e|
94
- e.inherit_settings(top_level_setting.namespace_stackable)
95
- e.reset_routes!
73
+ # Replays the set up to produce an API as defined in this class, can be called
74
+ # on classes that inherit from Grape::API
75
+ def replay_setup_on(instance)
76
+ @setup.each do |setup_stage|
77
+ instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block])
96
78
  end
97
-
98
- reset_routes!
99
79
  end
100
- end
101
-
102
- attr_reader :router
103
80
 
104
- # Builds the routes from the defined endpoints, effectively compiling
105
- # this API into a usable form.
106
- def initialize
107
- @router = Router.new
108
- add_head_not_allowed_methods_and_options_methods
109
- self.class.endpoints.each do |endpoint|
110
- endpoint.mount_in(@router)
81
+ def respond_to?(method, include_private = false)
82
+ super(method, include_private) || base_instance.respond_to?(method, include_private)
111
83
  end
112
84
 
113
- @router.compile!
114
- @router.freeze
115
- end
116
-
117
- # Handle a request. See Rack documentation for what `env` is.
118
- def call(env)
119
- result = @router.call(env)
120
- result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
121
- result
122
- end
123
-
124
- # Some requests may return a HTTP 404 error if grape cannot find a matching
125
- # route. In this case, Grape::Router adds a X-Cascade header to the response
126
- # and sets it to 'pass', indicating to grape's parents they should keep
127
- # looking for a matching route on other resources.
128
- #
129
- # In some applications (e.g. mounting grape on rails), one might need to trap
130
- # errors from reaching upstream. This is effectivelly done by unsetting
131
- # X-Cascade. Default :cascade is true.
132
- def cascade?
133
- return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
134
- return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
135
- true
136
- end
137
-
138
- reset!
139
-
140
- private
141
-
142
- # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
143
- # with a list of HTTP methods that can be called. Also add a route that
144
- # will return an HTTP 405 response for any HTTP method that the resource
145
- # cannot handle.
146
- def add_head_not_allowed_methods_and_options_methods
147
- routes_map = {}
148
-
149
- self.class.endpoints.each do |endpoint|
150
- routes = endpoint.routes
151
- routes.each do |route|
152
- # using the :any shorthand produces [nil] for route methods, substitute all manually
153
- route_key = route.pattern.to_regexp
154
- routes_map[route_key] ||= {}
155
- route_settings = routes_map[route_key]
156
- route_settings[:pattern] = route.pattern
157
- route_settings[:requirements] = route.requirements
158
- route_settings[:path] = route.origin
159
- route_settings[:methods] ||= []
160
- route_settings[:methods] << route.request_method
161
- route_settings[:endpoint] = route.app
85
+ def respond_to_missing?(method, include_private = false)
86
+ base_instance.respond_to?(method, include_private)
87
+ end
162
88
 
163
- # using the :any shorthand produces [nil] for route methods, substitute all manually
164
- route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
89
+ def method_missing(method, *args, &block)
90
+ # If there's a missing method, it may be defined on the base_instance instead.
91
+ if respond_to_missing?(method)
92
+ base_instance.send(method, *args, &block)
93
+ else
94
+ super
165
95
  end
166
96
  end
167
97
 
168
- # The paths we collected are prepared (cf. Path#prepare), so they
169
- # contain already versioning information when using path versioning.
170
- # Disable versioning so adding a route won't prepend versioning
171
- # informations again.
172
- without_root_prefix do
173
- without_versioning do
174
- routes_map.each do |_, config|
175
- methods = config[:methods]
176
- allowed_methods = methods.dup
177
-
178
- unless self.class.namespace_inheritable(:do_not_route_head)
179
- allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
180
- end
98
+ private
181
99
 
182
- allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
183
-
184
- unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
185
- config[:endpoint].options[:options_route_enabled] = true
186
- end
187
-
188
- attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
189
- generate_not_allowed_method(config[:pattern], attributes)
190
- end
100
+ # Adds a new stage to the set up require to get a Grape::API up and running
101
+ def add_setup(method, *args, &block)
102
+ setup_stage = { method: method, args: args, block: block }
103
+ @setup << setup_stage
104
+ last_response = nil
105
+ @instances.each do |instance|
106
+ last_response = instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block])
191
107
  end
108
+ last_response
192
109
  end
193
110
  end
194
-
195
- # Generate a route that returns an HTTP 405 response for a user defined
196
- # path on methods not specified
197
- def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
198
- not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
199
- not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
200
-
201
- return if not_allowed_methods.empty?
202
-
203
- @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
204
- end
205
-
206
- # Allows definition of endpoints that ignore the versioning configuration
207
- # used by the rest of your API.
208
- def without_versioning(&_block)
209
- old_version = self.class.namespace_inheritable(:version)
210
- old_version_options = self.class.namespace_inheritable(:version_options)
211
-
212
- self.class.namespace_inheritable_to_nil(:version)
213
- self.class.namespace_inheritable_to_nil(:version_options)
214
-
215
- yield
216
-
217
- self.class.namespace_inheritable(:version, old_version)
218
- self.class.namespace_inheritable(:version_options, old_version_options)
219
- end
220
-
221
- # Allows definition of endpoints that ignore the root prefix used by the
222
- # rest of your API.
223
- def without_root_prefix(&_block)
224
- old_prefix = self.class.namespace_inheritable(:root_prefix)
225
-
226
- self.class.namespace_inheritable_to_nil(:root_prefix)
227
-
228
- yield
229
-
230
- self.class.namespace_inheritable(:root_prefix, old_prefix)
231
- end
232
111
  end
233
112
  end
@@ -0,0 +1,242 @@
1
+ require 'grape/router'
2
+
3
+ module Grape
4
+ class API
5
+ # The API Instance class, is the engine behind Grape::API. Each class that inherits
6
+ # from this will represent a different API instance
7
+ class Instance
8
+ include Grape::DSL::API
9
+
10
+ class << self
11
+ attr_reader :instance
12
+ attr_reader :base
13
+ attr_accessor :configuration
14
+
15
+ def base=(grape_api)
16
+ @base = grape_api
17
+ grape_api.instances << self
18
+ end
19
+
20
+ # A class-level lock to ensure the API is not compiled by multiple
21
+ # threads simultaneously within the same process.
22
+ LOCK = Mutex.new
23
+
24
+ # Clears all defined routes, endpoints, etc., on this API.
25
+ def reset!
26
+ reset_endpoints!
27
+ reset_routes!
28
+ reset_validations!
29
+ end
30
+
31
+ # Parses the API's definition and compiles it into an instance of
32
+ # Grape::API.
33
+ def compile
34
+ @instance ||= new
35
+ end
36
+
37
+ # Wipe the compiled API so we can recompile after changes were made.
38
+ def change!
39
+ @instance = nil
40
+ end
41
+
42
+ # This is the interface point between Rack and Grape; it accepts a request
43
+ # from Rack and ultimately returns an array of three values: the status,
44
+ # the headers, and the body. See [the rack specification]
45
+ # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.
46
+ def call(env)
47
+ LOCK.synchronize { compile } unless instance
48
+ call!(env)
49
+ end
50
+
51
+ # A non-synchronized version of ::call.
52
+ def call!(env)
53
+ instance.call(env)
54
+ end
55
+
56
+ # (see #cascade?)
57
+ def cascade(value = nil)
58
+ if value.nil?
59
+ inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true
60
+ else
61
+ namespace_inheritable(:cascade, value)
62
+ end
63
+ end
64
+
65
+ # see Grape::Router#recognize_path
66
+ def recognize_path(path)
67
+ LOCK.synchronize { compile } unless instance
68
+ instance.router.recognize_path(path)
69
+ end
70
+
71
+ protected
72
+
73
+ def prepare_routes
74
+ endpoints.map(&:routes).flatten
75
+ end
76
+
77
+ # Execute first the provided block, then each of the
78
+ # block passed in. Allows for simple 'before' setups
79
+ # of settings stack pushes.
80
+ def nest(*blocks, &block)
81
+ blocks.reject!(&:nil?)
82
+ if blocks.any?
83
+ instance_eval(&block) if block_given?
84
+ blocks.each { |b| instance_eval(&b) }
85
+ reset_validations!
86
+ else
87
+ instance_eval(&block)
88
+ end
89
+ end
90
+
91
+ def inherited(subclass)
92
+ subclass.reset!
93
+ subclass.logger = logger.clone
94
+ end
95
+
96
+ def inherit_settings(other_settings)
97
+ top_level_setting.inherit_from other_settings.point_in_time_copy
98
+
99
+ # Propagate any inherited params down to our endpoints, and reset any
100
+ # compiled routes.
101
+ endpoints.each do |e|
102
+ e.inherit_settings(top_level_setting.namespace_stackable)
103
+ e.reset_routes!
104
+ end
105
+
106
+ reset_routes!
107
+ end
108
+ end
109
+
110
+ attr_reader :router
111
+
112
+ # Builds the routes from the defined endpoints, effectively compiling
113
+ # this API into a usable form.
114
+ def initialize
115
+ @router = Router.new
116
+ add_head_not_allowed_methods_and_options_methods
117
+ self.class.endpoints.each do |endpoint|
118
+ endpoint.mount_in(@router)
119
+ end
120
+
121
+ @router.compile!
122
+ @router.freeze
123
+ end
124
+
125
+ # Handle a request. See Rack documentation for what `env` is.
126
+ def call(env)
127
+ result = @router.call(env)
128
+ result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade?
129
+ result
130
+ end
131
+
132
+ # Some requests may return a HTTP 404 error if grape cannot find a matching
133
+ # route. In this case, Grape::Router adds a X-Cascade header to the response
134
+ # and sets it to 'pass', indicating to grape's parents they should keep
135
+ # looking for a matching route on other resources.
136
+ #
137
+ # In some applications (e.g. mounting grape on rails), one might need to trap
138
+ # errors from reaching upstream. This is effectivelly done by unsetting
139
+ # X-Cascade. Default :cascade is true.
140
+ def cascade?
141
+ return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
142
+ return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
143
+ true
144
+ end
145
+
146
+ reset!
147
+
148
+ private
149
+
150
+ # For every resource add a 'OPTIONS' route that returns an HTTP 204 response
151
+ # with a list of HTTP methods that can be called. Also add a route that
152
+ # will return an HTTP 405 response for any HTTP method that the resource
153
+ # cannot handle.
154
+ def add_head_not_allowed_methods_and_options_methods
155
+ routes_map = {}
156
+
157
+ self.class.endpoints.each do |endpoint|
158
+ routes = endpoint.routes
159
+ routes.each do |route|
160
+ # using the :any shorthand produces [nil] for route methods, substitute all manually
161
+ route_key = route.pattern.to_regexp
162
+ routes_map[route_key] ||= {}
163
+ route_settings = routes_map[route_key]
164
+ route_settings[:pattern] = route.pattern
165
+ route_settings[:requirements] = route.requirements
166
+ route_settings[:path] = route.origin
167
+ route_settings[:methods] ||= []
168
+ route_settings[:methods] << route.request_method
169
+ route_settings[:endpoint] = route.app
170
+
171
+ # using the :any shorthand produces [nil] for route methods, substitute all manually
172
+ route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*')
173
+ end
174
+ end
175
+
176
+ # The paths we collected are prepared (cf. Path#prepare), so they
177
+ # contain already versioning information when using path versioning.
178
+ # Disable versioning so adding a route won't prepend versioning
179
+ # informations again.
180
+ without_root_prefix do
181
+ without_versioning do
182
+ routes_map.each do |_, config|
183
+ methods = config[:methods]
184
+ allowed_methods = methods.dup
185
+
186
+ unless self.class.namespace_inheritable(:do_not_route_head)
187
+ allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET)
188
+ end
189
+
190
+ allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ')
191
+
192
+ unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS)
193
+ config[:endpoint].options[:options_route_enabled] = true
194
+ end
195
+
196
+ attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header)
197
+ generate_not_allowed_method(config[:pattern], attributes)
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # Generate a route that returns an HTTP 405 response for a user defined
204
+ # path on methods not specified
205
+ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes)
206
+ not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods
207
+ not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options)
208
+
209
+ return if not_allowed_methods.empty?
210
+
211
+ @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes)
212
+ end
213
+
214
+ # Allows definition of endpoints that ignore the versioning configuration
215
+ # used by the rest of your API.
216
+ def without_versioning(&_block)
217
+ old_version = self.class.namespace_inheritable(:version)
218
+ old_version_options = self.class.namespace_inheritable(:version_options)
219
+
220
+ self.class.namespace_inheritable_to_nil(:version)
221
+ self.class.namespace_inheritable_to_nil(:version_options)
222
+
223
+ yield
224
+
225
+ self.class.namespace_inheritable(:version, old_version)
226
+ self.class.namespace_inheritable(:version_options, old_version_options)
227
+ end
228
+
229
+ # Allows definition of endpoints that ignore the root prefix used by the
230
+ # rest of your API.
231
+ def without_root_prefix(&_block)
232
+ old_prefix = self.class.namespace_inheritable(:root_prefix)
233
+
234
+ self.class.namespace_inheritable_to_nil(:root_prefix)
235
+
236
+ yield
237
+
238
+ self.class.namespace_inheritable(:root_prefix, old_prefix)
239
+ end
240
+ end
241
+ end
242
+ end