hanami-router 1.3.2 → 2.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -3
  3. data/README.md +192 -154
  4. data/hanami-router.gemspec +23 -20
  5. data/lib/hanami/middleware/body_parser.rb +17 -13
  6. data/lib/hanami/middleware/body_parser/class_interface.rb +56 -56
  7. data/lib/hanami/middleware/body_parser/errors.rb +7 -4
  8. data/lib/hanami/middleware/body_parser/json_parser.rb +5 -3
  9. data/lib/hanami/middleware/error.rb +16 -0
  10. data/lib/hanami/router.rb +262 -149
  11. data/lib/hanami/router/version.rb +3 -1
  12. data/lib/hanami/routing.rb +193 -0
  13. data/lib/hanami/routing/endpoint.rb +122 -104
  14. data/lib/hanami/routing/endpoint_resolver.rb +20 -16
  15. data/lib/hanami/routing/prefix.rb +102 -0
  16. data/lib/hanami/routing/recognized_route.rb +40 -26
  17. data/lib/hanami/routing/resource.rb +9 -7
  18. data/lib/hanami/routing/resource/action.rb +58 -33
  19. data/lib/hanami/routing/resource/nested.rb +4 -1
  20. data/lib/hanami/routing/resource/options.rb +3 -1
  21. data/lib/hanami/routing/resources.rb +6 -4
  22. data/lib/hanami/routing/resources/action.rb +11 -6
  23. data/lib/hanami/routing/routes_inspector.rb +22 -20
  24. data/lib/hanami/routing/scope.rb +112 -0
  25. metadata +47 -25
  26. data/lib/hanami-router.rb +0 -1
  27. data/lib/hanami/routing/error.rb +0 -7
  28. data/lib/hanami/routing/force_ssl.rb +0 -212
  29. data/lib/hanami/routing/http_router.rb +0 -220
  30. data/lib/hanami/routing/http_router_monkey_patch.rb +0 -38
  31. data/lib/hanami/routing/namespace.rb +0 -98
  32. data/lib/hanami/routing/parsers.rb +0 -113
  33. data/lib/hanami/routing/parsing/json_parser.rb +0 -33
  34. data/lib/hanami/routing/parsing/parser.rb +0 -61
  35. data/lib/hanami/routing/route.rb +0 -71
@@ -1,6 +1,8 @@
1
- require 'hanami/utils/string'
2
- require 'hanami/utils/class'
3
- require 'hanami/routing/endpoint'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
4
+ require "hanami/utils/class"
5
+ require "hanami/routing/endpoint"
4
6
 
5
7
  module Hanami
6
8
  module Routing
@@ -12,11 +14,11 @@ module Hanami
12
14
  class EndpointResolver
13
15
  # @since 0.2.0
14
16
  # @api private
15
- NAMING_PATTERN = '%{controller}::%{action}'.freeze
17
+ NAMING_PATTERN = "%<controller>s::%<action>s"
16
18
 
17
19
  # @since 0.7.0
18
20
  # @api private
19
- DEFAULT_RESPONSE = [404, {'X-Cascade' => 'pass'}, 'Not Found'].freeze
21
+ DEFAULT_RESPONSE = [404, { "X-Cascade" => "pass" }, "Not Found"].freeze
20
22
 
21
23
  # Default separator for controller and action.
22
24
  # A different separator can be passed to #initialize with the `:separator` option.
@@ -32,7 +34,7 @@ module Hanami
32
34
  # router = Hanami::Router.new do
33
35
  # get '/', to: 'articles#show'
34
36
  # end
35
- ACTION_SEPARATOR = '#'.freeze
37
+ ACTION_SEPARATOR = "#"
36
38
 
37
39
  attr_reader :action_separator
38
40
 
@@ -183,10 +185,11 @@ module Hanami
183
185
  end
184
186
 
185
187
  protected
188
+
186
189
  # @api private
187
190
  def default
188
191
  @endpoint_class.new(
189
- ->(env) { DEFAULT_RESPONSE }
192
+ ->(_env) { DEFAULT_RESPONSE }
190
193
  )
191
194
  end
192
195
 
@@ -208,6 +211,7 @@ module Hanami
208
211
  end
209
212
 
210
213
  private
214
+
211
215
  # @api private
212
216
  def resolve_callable(callable)
213
217
  if callable.respond_to?(:call)
@@ -219,19 +223,19 @@ module Hanami
219
223
 
220
224
  # @api private
221
225
  def resolve_matchable(matchable)
222
- if matchable.respond_to?(:match)
223
- constantize(
224
- resolve_action(matchable) || classify(matchable)
225
- )
226
- end
226
+ return unless matchable.respond_to?(:match)
227
+
228
+ constantize(
229
+ resolve_action(matchable) || classify(matchable)
230
+ )
227
231
  end
228
232
 
229
233
  # @api private
230
234
  def resolve_action(string)
231
- if string.match(action_separator)
232
- controller, action = string.split(action_separator).map {|token| classify(token) }
233
- @pattern % {controller: controller, action: action}
234
- end
235
+ return unless string.match?(action_separator)
236
+
237
+ controller, action = string.split(action_separator).map { |token| classify(token) }
238
+ format(@pattern, controller: controller, action: action)
235
239
  end
236
240
  end
237
241
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "hanami/utils/path_prefix"
5
+
6
+ module Hanami
7
+ module Routing
8
+ # Prefix for routes.
9
+ # Implementation of Hanami::Router#prefix
10
+ #
11
+ # @since 2.0.0
12
+ # @api private
13
+ #
14
+ # @see Hanami::Router#prefix
15
+ class Prefix < SimpleDelegator
16
+ # @api private
17
+ # @since 2.0.0
18
+ def initialize(router, path, namespace, configuration, &blk)
19
+ @router = router
20
+ @namespace = namespace
21
+ @configuration = configuration
22
+ @path = Utils::PathPrefix.new(path)
23
+ __setobj__(@router)
24
+ instance_eval(&blk)
25
+ end
26
+
27
+ # @api private
28
+ # @since 2.0.0
29
+ def get(path, options = {}, &endpoint)
30
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
31
+ end
32
+
33
+ # @api private
34
+ # @since 2.0.0
35
+ def post(path, options = {}, &endpoint)
36
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
37
+ end
38
+
39
+ # @api private
40
+ # @since 2.0.0
41
+ def put(path, options = {}, &endpoint)
42
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
43
+ end
44
+
45
+ # @api private
46
+ # @since 2.0.0
47
+ def patch(path, options = {}, &endpoint)
48
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
49
+ end
50
+
51
+ # @api private
52
+ # @since 2.0.0
53
+ def delete(path, options = {}, &endpoint)
54
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
55
+ end
56
+
57
+ # @api private
58
+ # @since 2.0.0
59
+ def trace(path, options = {}, &endpoint)
60
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
61
+ end
62
+
63
+ # @api private
64
+ # @since 2.0.0
65
+ def options(path, options = {}, &endpoint)
66
+ super(@path.join(path), options.merge(namespace: @namespace, configuration: @configuration), &endpoint)
67
+ end
68
+
69
+ # @api private
70
+ # @since 2.0.0
71
+ def resource(name, options = {})
72
+ super(name, options.merge(prefix: @path.relative_join(options[:prefix]), namespace: @namespace, configuration: @configuration))
73
+ end
74
+
75
+ # @api private
76
+ # @since 2.0.0
77
+ def resources(name, options = {})
78
+ super(name, options.merge(prefix: @path.relative_join(options[:prefix]), namespace: @namespace, configuration: @configuration))
79
+ end
80
+
81
+ # @api private
82
+ # @since 2.0.0
83
+ def redirect(path, options = {}, &endpoint)
84
+ super(@path.join(path), options.merge(to: @path.join(options[:to])), &endpoint)
85
+ end
86
+
87
+ # @api private
88
+ # @since 2.0.0
89
+ def mount(app, options)
90
+ super(app, options.merge(at: @path.join(options[:at])))
91
+ end
92
+
93
+ # Supports nested prefix
94
+ #
95
+ # @api private
96
+ # @since 2.0.0
97
+ def prefix(path, &blk)
98
+ self.class.new(self, path, @namespace, @configuration, &blk)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,4 +1,6 @@
1
- require 'hanami/utils/string'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
2
4
 
3
5
  module Hanami
4
6
  module Routing
@@ -10,23 +12,25 @@ module Hanami
10
12
  class RecognizedRoute
11
13
  # @since 0.5.0
12
14
  # @api private
13
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
15
+ REQUEST_METHOD = "REQUEST_METHOD"
14
16
 
15
17
  # @since 0.7.0
16
18
  # @api private
17
- PATH_INFO = 'PATH_INFO'.freeze
19
+ PATH_INFO = "PATH_INFO"
18
20
 
19
21
  # @since 0.5.0
20
22
  # @api private
21
- NAMESPACE = '%s::'.freeze
23
+ NAMESPACE = "%s::"
22
24
 
23
25
  # @since 0.5.0
24
26
  # @api private
25
- NAMESPACE_REPLACEMENT = ''.freeze
27
+ NAMESPACE_REPLACEMENT = ""
26
28
 
27
29
  # @since 0.5.0
28
30
  # @api private
29
- ACTION_PATH_SEPARATOR = '/'.freeze
31
+ ACTION_PATH_SEPARATOR = "/"
32
+
33
+ ACTION_SEPARATOR = "#"
30
34
 
31
35
  # @since 0.5.0
32
36
  # @api public
@@ -42,18 +46,20 @@ module Hanami
42
46
  #
43
47
  # @since 0.5.0
44
48
  # @api private
45
- def initialize(response, env, router)
46
- @env = env
47
- @endpoint = nil
48
- @params = {}
49
-
50
- unless response.nil?
51
- @endpoint = response.route.dest
52
- @params = response.params
53
- end
49
+ def initialize(route, env, namespace)
50
+ @env = env
51
+ @endpoint = nil
52
+ @params = {}
53
+ @namespace = namespace
54
+
55
+ return if route.nil?
56
+
57
+ @endpoint = route.instance_variable_get(:@endpoint)
58
+
59
+ return unless routable?
54
60
 
55
- @namespace = router.namespace
56
- @action_separator = router.action_separator
61
+ route.call(@env)
62
+ @params = @env["router.params"]
57
63
  end
58
64
 
59
65
  # Rack protocol compatibility
@@ -70,7 +76,7 @@ module Hanami
70
76
  # @see Hanami::Routing::RecognizedRoute#routable?
71
77
  # @see Hanami::Router::NotRoutableEndpointError
72
78
  def call(env)
73
- if routable?
79
+ if routable? # rubocop:disable Style/GuardClause
74
80
  @endpoint.call(env)
75
81
  else
76
82
  raise Hanami::Router::NotRoutableEndpointError.new(@env)
@@ -116,12 +122,14 @@ module Hanami
116
122
  # puts router.recognize('/books/23').action # => "books#show"
117
123
  def action
118
124
  return if !routable? || redirect?
125
+
119
126
  namespace = NAMESPACE % @namespace
120
127
 
121
128
  if destination.match(namespace)
122
129
  Hanami::Utils::String.transform(
123
130
  destination.sub(namespace, NAMESPACE_REPLACEMENT),
124
- :underscore, [:rsub, ACTION_PATH_SEPARATOR, @action_separator])
131
+ :underscore, [:rsub, ACTION_PATH_SEPARATOR, ACTION_SEPARATOR]
132
+ )
125
133
  else
126
134
  destination
127
135
  end
@@ -146,7 +154,9 @@ module Hanami
146
154
  # puts router.recognize('/').routable? # => true
147
155
  # puts router.recognize('/foo').routable? # => false
148
156
  def routable?
149
- @endpoint&.routable? || false
157
+ return false if @endpoint.nil?
158
+
159
+ @endpoint.respond_to?(:routable?) ? @endpoint.routable? : true
150
160
  end
151
161
 
152
162
  # Check if redirect
@@ -169,7 +179,9 @@ module Hanami
169
179
  # puts router.recognize('/home').redirect? # => true
170
180
  # puts router.recognize('/').redirect? # => false
171
181
  def redirect?
172
- @endpoint&.redirect? || false
182
+ return false if @endpoint.nil?
183
+
184
+ @endpoint.respond_to?(:redirect?) ? @endpoint.redirect? : false
173
185
  end
174
186
 
175
187
  # Returns the redirect destination path
@@ -193,7 +205,9 @@ module Hanami
193
205
  # puts router.recognize('/home').destination_path # => "/"
194
206
  # puts router.recognize('/').destination_path # => nil
195
207
  def redirection_path
196
- @endpoint&.destination_path
208
+ return unless redirect?
209
+
210
+ @endpoint.destination_path if @endpoint.respond_to?(:destination_path)
197
211
  end
198
212
 
199
213
  private
@@ -204,13 +218,13 @@ module Hanami
204
218
  # @see Hanami::Routing::Endpoint
205
219
  def destination
206
220
  @destination ||= begin
207
- case k = @endpoint.__getobj__
208
- when Class
209
- k.name
221
+ case @endpoint
210
222
  when Proc
211
223
  @endpoint.inspect
224
+ when Class
225
+ @endpoint.name
212
226
  else
213
- k.class.name
227
+ @endpoint.class.name
214
228
  end
215
229
  end
216
230
  end
@@ -1,6 +1,8 @@
1
- require 'hanami/utils/class_attribute'
2
- require 'hanami/routing/resource/options'
3
- require 'hanami/routing/resource/action'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/class_attribute"
4
+ require "hanami/routing/resource/options"
5
+ require "hanami/routing/resource/action"
4
6
 
5
7
  module Hanami
6
8
  module Routing
@@ -17,14 +19,14 @@ module Hanami
17
19
 
18
20
  # @api private
19
21
  # @since 0.4.0
20
- NESTED_ROUTES_SEPARATOR = '/'.freeze
22
+ NESTED_ROUTES_SEPARATOR = "/"
21
23
 
22
24
  # Set of default routes
23
25
  #
24
26
  # @api private
25
27
  # @since 0.1.0
26
28
  class_attribute :actions
27
- self.actions = [:new, :create, :show, :edit, :update, :destroy]
29
+ self.actions = %i[new create show edit update destroy]
28
30
 
29
31
  # Action class
30
32
  #
@@ -83,7 +85,7 @@ module Hanami
83
85
  #
84
86
  # @api private
85
87
  # @since 0.4.0
86
- def wildcard_param(route_param = nil)
88
+ def wildcard_param(_route_param = nil)
87
89
  NESTED_ROUTES_SEPARATOR
88
90
  end
89
91
 
@@ -92,7 +94,7 @@ module Hanami
92
94
  # @api private
93
95
  # @since 0.4.0
94
96
  def _resource(klass, name, options, &blk)
95
- options = options.merge(separator: @options[:separator], namespace: @options[:namespace])
97
+ options = options.merge(separator: @options[:separator], prefix: @options[:prefix])
96
98
  klass.new(@router, [@name, name].join(NESTED_ROUTES_SEPARATOR), options, self, &blk)
97
99
  end
98
100
 
@@ -1,7 +1,9 @@
1
- require 'hanami/utils/string'
2
- require 'hanami/utils/path_prefix'
3
- require 'hanami/utils/class_attribute'
4
- require 'hanami/routing/resource/nested'
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/utils/string"
4
+ require "hanami/utils/path_prefix"
5
+ require "hanami/utils/class_attribute"
6
+ require "hanami/routing/resource/nested"
5
7
 
6
8
  module Hanami
7
9
  module Routing
@@ -20,7 +22,7 @@ module Hanami
20
22
  #
21
23
  # @api private
22
24
  # @since 0.4.0
23
- NESTED_ROUTES_SEPARATOR = '/'.freeze
25
+ NESTED_ROUTES_SEPARATOR = "/"
24
26
 
25
27
  # Ruby namespace where lookup for default subclasses.
26
28
  #
@@ -41,7 +43,7 @@ module Hanami
41
43
  # @api private
42
44
  # @since 0.2.0
43
45
  class_attribute :named_route_separator
44
- self.named_route_separator = '_'.freeze
46
+ self.named_route_separator = "_"
45
47
 
46
48
  # Generate an action for the given router
47
49
  #
@@ -82,7 +84,7 @@ module Hanami
82
84
  #
83
85
  # @since 0.1.0
84
86
  def generate(&blk)
85
- @router.send verb, path, to: endpoint, as: as
87
+ @router.send verb, path, to: endpoint, as: as, namespace: namespace, configuration: configuration
86
88
  instance_eval(&blk) if block_given?
87
89
  end
88
90
 
@@ -105,15 +107,30 @@ module Hanami
105
107
  @resource_name ||= @options[:name].to_s
106
108
  end
107
109
 
110
+ # Prefix
111
+ #
112
+ # @api private
113
+ # @since 2.0.0
114
+ def prefix
115
+ @prefix ||= Utils::PathPrefix.new(@options[:prefix])
116
+ end
117
+
108
118
  # Namespace
109
119
  #
110
120
  # @api private
111
- # @since 0.2.0
121
+ # @since 2.0.0
112
122
  def namespace
113
- @namespace ||= Utils::PathPrefix.new @options[:namespace]
123
+ @namespace ||= @options[:namespace]
124
+ end
125
+
126
+ # Configuration
127
+ #
128
+ # @api private
129
+ # @since 2.0.0
130
+ def configuration
131
+ @configuration ||= @options[:configuration]
114
132
  end
115
133
 
116
- private
117
134
  # Load a subclass, according to the given action name
118
135
  #
119
136
  # @param action [String] the action name
@@ -128,6 +145,8 @@ module Hanami
128
145
  Utils::Class.load!(Utils::String.classify(action), namespace)
129
146
  end
130
147
 
148
+ private_class_method :class_for
149
+
131
150
  # Accepted HTTP verb
132
151
  #
133
152
  # @see Hanami::Routing::Resource::Action.verb
@@ -138,21 +157,21 @@ module Hanami
138
157
  self.class.verb
139
158
  end
140
159
 
141
- # The namespaced URL relative path
160
+ # The prefixed relative URL
142
161
  #
143
162
  # @example
144
- # require 'hanami/router'
163
+ # require "hanami/router"
145
164
  #
146
165
  # Hanami::Router.new do
147
- # resources 'flowers'
166
+ # resources "flowers"
148
167
  #
149
- # namespace 'animals' do
150
- # resources 'mammals'
168
+ # prefix "animals" do
169
+ # resources "mammals"
151
170
  # end
152
171
  # end
153
172
  #
154
- # # It will generate paths like '/flowers', '/flowers/:id' ..
155
- # # It will generate paths like '/animals/mammals', '/animals/mammals/:id' ..
173
+ # # It will generate paths like "/flowers", "/flowers/:id" ..
174
+ # # It will generate paths like "/animals/mammals", "/animals/mammals/:id" ..
156
175
  #
157
176
  # @api private
158
177
  # @since 0.1.0
@@ -170,19 +189,19 @@ module Hanami
170
189
  # @api private
171
190
  # @since 0.1.0
172
191
  def rest_path
173
- namespace.join(_nested_rest_path || resource_name.to_s)
192
+ prefix.join(_nested_rest_path || resource_name.to_s)
174
193
  end
175
194
 
176
- # The namespaced name of the action within the whole context of the router.
195
+ # The prefixed name of the action within the whole context of the router.
177
196
  #
178
197
  # @example
179
- # require 'hanami/router'
198
+ # require "hanami/router"
180
199
  #
181
200
  # Hanami::Router.new do
182
- # resources 'flowers'
201
+ # resources "flowers"
183
202
  #
184
- # namespace 'animals' do
185
- # resources 'mammals'
203
+ # prefix "animals" do
204
+ # resources "mammals"
186
205
  # end
187
206
  # end
188
207
  #
@@ -192,7 +211,7 @@ module Hanami
192
211
  # @api private
193
212
  # @since 0.1.0
194
213
  def as
195
- namespace.relative_join(_singularized_as, self.class.named_route_separator).to_sym
214
+ prefix.relative_join(_singularized_as, self.class.named_route_separator).to_sym
196
215
  end
197
216
 
198
217
  # The name of the RESTful action.
@@ -219,7 +238,7 @@ module Hanami
219
238
  # @api private
220
239
  # @since 0.1.0
221
240
  def endpoint
222
- [ controller_name, action_name ].join separator
241
+ [controller_name, action_name].join separator
223
242
  end
224
243
 
225
244
  # Separator between controller and action name
@@ -260,7 +279,7 @@ module Hanami
260
279
  def _singularized_as
261
280
  name = @options[:as] ? @options[:as].to_s : resource_name
262
281
  name.split(NESTED_ROUTES_SEPARATOR).map do |n|
263
- Hanami::Utils::String.singularize(n)
282
+ @router.inflector.singularize(n)
264
283
  end
265
284
  end
266
285
 
@@ -286,18 +305,23 @@ module Hanami
286
305
  instance_eval(&blk) if block_given?
287
306
  end
288
307
 
289
- protected
290
308
  # @since 0.1.0
291
309
  # @api private
292
- def method_missing(m, *args)
293
- verb = m
310
+ def method_missing(method_name, *args) # rubocop:disable Style/MethodMissingSuper
311
+ verb = method_name
294
312
  action_name = Utils::PathPrefix.new(args.first).relative_join(nil)
295
313
 
296
314
  @router.__send__ verb, path(action_name),
297
- to: endpoint(action_name), as: as(action_name)
315
+ to: endpoint(action_name), as: as(action_name),
316
+ namespace: namespace, configuration: configuration
317
+ end
318
+
319
+ def respond_to_missing?(method_name, include_all)
320
+ @router.respond_to?(method_name, include_all)
298
321
  end
299
322
 
300
323
  private
324
+
301
325
  # @since 0.1.0
302
326
  # @api private
303
327
  def path(action_name)
@@ -307,13 +331,13 @@ module Hanami
307
331
  # @since 0.1.0
308
332
  # @api private
309
333
  def endpoint(action_name)
310
- [ controller_name, action_name ].join separator
334
+ [controller_name, action_name].join separator
311
335
  end
312
336
 
313
337
  # @since 0.1.0
314
338
  # @api private
315
339
  def as(action_name)
316
- [ action_name, super() ].join(self.class.named_route_separator).to_sym
340
+ [action_name, super()].join(self.class.named_route_separator).to_sym
317
341
  end
318
342
  end
319
343
 
@@ -332,6 +356,7 @@ module Hanami
332
356
  # @since 0.1.0
333
357
  module DefaultMemberAction
334
358
  private
359
+
335
360
  # @since 0.1.0
336
361
  # @api private
337
362
  def path
@@ -341,7 +366,7 @@ module Hanami
341
366
  # @since 0.1.0
342
367
  # @api private
343
368
  def as
344
- [ action_name, super ].join(self.class.named_route_separator).to_sym
369
+ [action_name, super].join(self.class.named_route_separator).to_sym
345
370
  end
346
371
  end
347
372