hanami-router 0.0.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +101 -0
- data/LICENSE.md +22 -0
- data/README.md +667 -9
- data/hanami-router.gemspec +18 -12
- data/lib/hanami-router.rb +1 -0
- data/lib/hanami/router.rb +1160 -3
- data/lib/hanami/router/version.rb +3 -2
- data/lib/hanami/routing/endpoint.rb +151 -0
- data/lib/hanami/routing/endpoint_resolver.rb +225 -0
- data/lib/hanami/routing/error.rb +7 -0
- data/lib/hanami/routing/force_ssl.rb +209 -0
- data/lib/hanami/routing/http_router.rb +187 -0
- data/lib/hanami/routing/namespace.rb +92 -0
- data/lib/hanami/routing/parsers.rb +71 -0
- data/lib/hanami/routing/parsing/json_parser.rb +28 -0
- data/lib/hanami/routing/parsing/parser.rb +58 -0
- data/lib/hanami/routing/recognized_route.rb +153 -0
- data/lib/hanami/routing/resource.rb +116 -0
- data/lib/hanami/routing/resource/action.rb +387 -0
- data/lib/hanami/routing/resource/nested.rb +39 -0
- data/lib/hanami/routing/resource/options.rb +74 -0
- data/lib/hanami/routing/resources.rb +48 -0
- data/lib/hanami/routing/resources/action.rb +150 -0
- data/lib/hanami/routing/route.rb +62 -0
- data/lib/hanami/routing/routes_inspector.rb +215 -0
- metadata +94 -16
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'hanami/utils/class_attribute'
|
2
|
+
require 'hanami/routing/resource/options'
|
3
|
+
require 'hanami/routing/resource/action'
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Routing
|
7
|
+
# Set of RESTful resource routes
|
8
|
+
# Implementation of Hanami::Router#resource
|
9
|
+
#
|
10
|
+
# @since 0.1.0
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
#
|
14
|
+
# @see Hanami::Router#resource
|
15
|
+
class Resource
|
16
|
+
include Utils::ClassAttribute
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
# @since 0.4.0
|
20
|
+
NESTED_ROUTES_SEPARATOR = '/'.freeze
|
21
|
+
|
22
|
+
# Set of default routes
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
# @since 0.1.0
|
26
|
+
class_attribute :actions
|
27
|
+
self.actions = [:new, :create, :show, :edit, :update, :destroy]
|
28
|
+
|
29
|
+
# Action class
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
# @since 0.1.0
|
33
|
+
class_attribute :action
|
34
|
+
self.action = Resource::Action
|
35
|
+
|
36
|
+
# Member action class
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
# @since 0.1.0
|
40
|
+
class_attribute :member
|
41
|
+
self.member = Resource::MemberAction
|
42
|
+
|
43
|
+
# Collection action class
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
# @since 0.1.0
|
47
|
+
class_attribute :collection
|
48
|
+
self.collection = Resource::CollectionAction
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
# @since 0.4.0
|
52
|
+
attr_reader :parent
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @since 0.1.0
|
56
|
+
def initialize(router, name, options = {}, parent = nil, &blk)
|
57
|
+
@router = router
|
58
|
+
@name = name
|
59
|
+
@parent = parent
|
60
|
+
@options = Options.new(self.class.actions, options.merge(name: @name))
|
61
|
+
generate(&blk)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Allow nested resources inside resource or resources
|
65
|
+
#
|
66
|
+
# @since 0.4.0
|
67
|
+
#
|
68
|
+
# @see Hanami::Router#resources
|
69
|
+
def resources(name, options = {}, &blk)
|
70
|
+
_resource(Resources, name, options, &blk)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Allow nested resource inside resource or resources
|
74
|
+
#
|
75
|
+
# @since 0.4.0
|
76
|
+
#
|
77
|
+
# @see Hanami::Router#resource
|
78
|
+
def resource(name, options = {}, &blk)
|
79
|
+
_resource(Resource, name, options, &blk)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return separator
|
83
|
+
#
|
84
|
+
# @api private
|
85
|
+
# @since 0.4.0
|
86
|
+
def wildcard_param(route_param = nil)
|
87
|
+
NESTED_ROUTES_SEPARATOR
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
# @since 0.4.0
|
94
|
+
def _resource(klass, name, options, &blk)
|
95
|
+
options = options.merge(separator: @options[:separator], namespace: @options[:namespace])
|
96
|
+
klass.new(@router, [@name, name].join(NESTED_ROUTES_SEPARATOR), options, self, &blk)
|
97
|
+
end
|
98
|
+
|
99
|
+
def generate(&blk)
|
100
|
+
instance_eval(&blk) if block_given?
|
101
|
+
|
102
|
+
@options.actions.each do |action|
|
103
|
+
self.class.action.generate(@router, action, @options, self)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def member(&blk)
|
108
|
+
self.class.member.new(@router, @options, self, &blk)
|
109
|
+
end
|
110
|
+
|
111
|
+
def collection(&blk)
|
112
|
+
self.class.collection.new(@router, @options, self, &blk)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,387 @@
|
|
1
|
+
require 'hanami/utils/string'
|
2
|
+
require 'hanami/utils/path_prefix'
|
3
|
+
require 'hanami/utils/class_attribute'
|
4
|
+
require 'hanami/routing/resource/nested'
|
5
|
+
|
6
|
+
module Hanami
|
7
|
+
module Routing
|
8
|
+
class Resource
|
9
|
+
# Action for RESTful resource
|
10
|
+
#
|
11
|
+
# @since 0.1.0
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
#
|
15
|
+
# @see Hanami::Router#resource
|
16
|
+
class Action
|
17
|
+
include Utils::ClassAttribute
|
18
|
+
|
19
|
+
# Nested routes separator
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
# @since 0.4.0
|
23
|
+
NESTED_ROUTES_SEPARATOR = '/'.freeze
|
24
|
+
|
25
|
+
# Ruby namespace where lookup for default subclasses.
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
# @since 0.1.0
|
29
|
+
class_attribute :namespace
|
30
|
+
self.namespace = Resource
|
31
|
+
|
32
|
+
# Accepted HTTP verb
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
# @since 0.1.0
|
36
|
+
class_attribute :verb
|
37
|
+
self.verb = :get
|
38
|
+
|
39
|
+
# Separator for named routes
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
# @since 0.2.0
|
43
|
+
class_attribute :named_route_separator
|
44
|
+
self.named_route_separator = '_'.freeze
|
45
|
+
|
46
|
+
# Generate an action for the given router
|
47
|
+
#
|
48
|
+
# @param router [Hanami::Router]
|
49
|
+
# @param action [Hanami::Routing::Resource::Action]
|
50
|
+
# @param options [Hash]
|
51
|
+
# @param resource [Hanami::Routing::Resource, Hanami::Routing::Resources]
|
52
|
+
#
|
53
|
+
# @api private
|
54
|
+
#
|
55
|
+
# @since 0.1.0
|
56
|
+
def self.generate(router, action, options = {}, resource = nil)
|
57
|
+
class_for(action).new(router, options, resource)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Initialize an action
|
61
|
+
#
|
62
|
+
# @param router [Hanami::Router]
|
63
|
+
# @param options [Hash]
|
64
|
+
# @param resource [Hanami::Routing::Resource, Hanami::Routing::Resources]
|
65
|
+
# @param blk [Proc]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
#
|
69
|
+
# @since 0.1.0
|
70
|
+
def initialize(router, options = {}, resource = nil, &blk)
|
71
|
+
@router = router
|
72
|
+
@options = options
|
73
|
+
@resource = resource
|
74
|
+
generate(&blk)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Generate an action for the given router
|
78
|
+
#
|
79
|
+
# @param blk [Proc]
|
80
|
+
#
|
81
|
+
# @api private
|
82
|
+
#
|
83
|
+
# @since 0.1.0
|
84
|
+
def generate(&blk)
|
85
|
+
@router.send verb, path, to: endpoint, as: as
|
86
|
+
instance_eval(&blk) if block_given?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Resource name
|
90
|
+
#
|
91
|
+
# @return [String]
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
# @since 0.1.0
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# require 'hanami/router'
|
98
|
+
#
|
99
|
+
# Hanami::Router.new do
|
100
|
+
# resource 'identity'
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# # 'identity' is the name passed in the @options
|
104
|
+
def resource_name
|
105
|
+
@resource_name ||= @options[:name].to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
# Namespace
|
109
|
+
#
|
110
|
+
# @api private
|
111
|
+
# @since 0.2.0
|
112
|
+
def namespace
|
113
|
+
@namespace ||= Utils::PathPrefix.new @options[:namespace]
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
# Load a subclass, according to the given action name
|
118
|
+
#
|
119
|
+
# @param action [String] the action name
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# Hanami::Routing::Resource::Action.send(:class_for, 'New') # =>
|
123
|
+
# Hanami::Routing::Resource::New
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
# @since 0.1.0
|
127
|
+
def self.class_for(action)
|
128
|
+
Utils::Class.load!(Utils::String.new(action).classify, namespace)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Accepted HTTP verb
|
132
|
+
#
|
133
|
+
# @see Hanami::Routing::Resource::Action.verb
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
# @since 0.1.0
|
137
|
+
def verb
|
138
|
+
self.class.verb
|
139
|
+
end
|
140
|
+
|
141
|
+
# The namespaced URL relative path
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# require 'hanami/router'
|
145
|
+
#
|
146
|
+
# Hanami::Router.new do
|
147
|
+
# resources 'flowers'
|
148
|
+
#
|
149
|
+
# namespace 'animals' do
|
150
|
+
# resources 'mammals'
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# # It will generate paths like '/flowers', '/flowers/:id' ..
|
155
|
+
# # It will generate paths like '/animals/mammals', '/animals/mammals/:id' ..
|
156
|
+
#
|
157
|
+
# @api private
|
158
|
+
# @since 0.1.0
|
159
|
+
def path
|
160
|
+
rest_path
|
161
|
+
end
|
162
|
+
|
163
|
+
# The URL relative path
|
164
|
+
#
|
165
|
+
# @example
|
166
|
+
# '/flowers'
|
167
|
+
# '/flowers/new'
|
168
|
+
# '/flowers/:id'
|
169
|
+
#
|
170
|
+
# @api private
|
171
|
+
# @since 0.1.0
|
172
|
+
def rest_path
|
173
|
+
namespace.join(_nested_rest_path || resource_name.to_s)
|
174
|
+
end
|
175
|
+
|
176
|
+
# The namespaced name of the action within the whole context of the router.
|
177
|
+
#
|
178
|
+
# @example
|
179
|
+
# require 'hanami/router'
|
180
|
+
#
|
181
|
+
# Hanami::Router.new do
|
182
|
+
# resources 'flowers'
|
183
|
+
#
|
184
|
+
# namespace 'animals' do
|
185
|
+
# resources 'mammals'
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# # It will generate named routes like :flowers, :new_flowers ..
|
190
|
+
# # It will generate named routes like :animals_mammals, :animals_new_mammals ..
|
191
|
+
#
|
192
|
+
# @api private
|
193
|
+
# @since 0.1.0
|
194
|
+
def as
|
195
|
+
namespace.relative_join(_singularized_as, self.class.named_route_separator).to_sym
|
196
|
+
end
|
197
|
+
|
198
|
+
# The name of the RESTful action.
|
199
|
+
#
|
200
|
+
# @example
|
201
|
+
# 'index'
|
202
|
+
# 'new'
|
203
|
+
# 'create'
|
204
|
+
#
|
205
|
+
# @api private
|
206
|
+
# @since 0.1.0
|
207
|
+
def action_name
|
208
|
+
Utils::String.new(self.class.name).demodulize.downcase
|
209
|
+
end
|
210
|
+
|
211
|
+
# A string that represents the endpoint to be loaded.
|
212
|
+
# It is composed by controller and action name.
|
213
|
+
#
|
214
|
+
# @see Hanami::Routing::Resource::Action#separator
|
215
|
+
#
|
216
|
+
# @example
|
217
|
+
# 'flowers#index'
|
218
|
+
#
|
219
|
+
# @api private
|
220
|
+
# @since 0.1.0
|
221
|
+
def endpoint
|
222
|
+
[ controller_name, action_name ].join separator
|
223
|
+
end
|
224
|
+
|
225
|
+
# Separator between controller and action name
|
226
|
+
#
|
227
|
+
# @see Hanami::Routing::EndpointResolver#separator
|
228
|
+
#
|
229
|
+
# @example
|
230
|
+
# '#' # default
|
231
|
+
#
|
232
|
+
# @api private
|
233
|
+
# @since 0.1.0
|
234
|
+
def separator
|
235
|
+
@options[:separator]
|
236
|
+
end
|
237
|
+
|
238
|
+
# Resource controller name
|
239
|
+
#
|
240
|
+
# @example
|
241
|
+
# Hanami::Router.new do
|
242
|
+
# resources 'flowers', controller: 'rocks'
|
243
|
+
# end
|
244
|
+
#
|
245
|
+
# # It will mount path 'flowers/new' to Rocks::New instead of Flowers::New
|
246
|
+
# # Same for other action names
|
247
|
+
#
|
248
|
+
# @api private
|
249
|
+
# @since 0.4.0
|
250
|
+
def controller_name
|
251
|
+
@options[:controller] || resource_name
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
# Singularize as (helper route)
|
257
|
+
#
|
258
|
+
# @api private
|
259
|
+
# @since 0.4.0
|
260
|
+
def _singularized_as
|
261
|
+
resource_name.split(NESTED_ROUTES_SEPARATOR).map do |name|
|
262
|
+
Hanami::Utils::String.new(name).singularize
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Create nested rest path
|
267
|
+
#
|
268
|
+
# @api private
|
269
|
+
# @since 0.4.0
|
270
|
+
def _nested_rest_path
|
271
|
+
Nested.new(resource_name, @resource).to_path
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Collection action
|
276
|
+
# It implements #collection within a #resource block.
|
277
|
+
#
|
278
|
+
# @api private
|
279
|
+
# @since 0.1.0
|
280
|
+
# @see Hanami::Router#resource
|
281
|
+
class CollectionAction < Action
|
282
|
+
def generate(&blk)
|
283
|
+
instance_eval(&blk) if block_given?
|
284
|
+
end
|
285
|
+
|
286
|
+
protected
|
287
|
+
def method_missing(m, *args)
|
288
|
+
verb = m
|
289
|
+
action_name = Utils::PathPrefix.new(args.first).relative_join(nil)
|
290
|
+
|
291
|
+
@router.__send__ verb, path(action_name),
|
292
|
+
to: endpoint(action_name), as: as(action_name)
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
def path(action_name)
|
297
|
+
rest_path.join(action_name)
|
298
|
+
end
|
299
|
+
|
300
|
+
def endpoint(action_name)
|
301
|
+
[ controller_name, action_name ].join separator
|
302
|
+
end
|
303
|
+
|
304
|
+
def as(action_name)
|
305
|
+
[ action_name, super() ].join(self.class.named_route_separator).to_sym
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# Collection action
|
310
|
+
# It implements #member within a #resource block.
|
311
|
+
#
|
312
|
+
# @api private
|
313
|
+
# @since 0.1.0
|
314
|
+
# @see Hanami::Router#resource
|
315
|
+
class MemberAction < CollectionAction
|
316
|
+
end
|
317
|
+
|
318
|
+
# Implementation of common methods for concrete member actions
|
319
|
+
#
|
320
|
+
# @api private
|
321
|
+
# @since 0.1.0
|
322
|
+
module DefaultMemberAction
|
323
|
+
private
|
324
|
+
def path
|
325
|
+
rest_path.join(action_name)
|
326
|
+
end
|
327
|
+
|
328
|
+
def as
|
329
|
+
[ action_name, super ].join(self.class.named_route_separator).to_sym
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# New action
|
334
|
+
#
|
335
|
+
# @api private
|
336
|
+
# @since 0.1.0
|
337
|
+
# @see Hanami::Router#resource
|
338
|
+
class New < Action
|
339
|
+
include DefaultMemberAction
|
340
|
+
end
|
341
|
+
|
342
|
+
# Create action
|
343
|
+
#
|
344
|
+
# @api private
|
345
|
+
# @since 0.1.0
|
346
|
+
# @see Hanami::Router#resource
|
347
|
+
class Create < Action
|
348
|
+
self.verb = :post
|
349
|
+
end
|
350
|
+
|
351
|
+
# Show action
|
352
|
+
#
|
353
|
+
# @api private
|
354
|
+
# @since 0.1.0
|
355
|
+
# @see Hanami::Router#resource
|
356
|
+
class Show < Action
|
357
|
+
end
|
358
|
+
|
359
|
+
# Edit action
|
360
|
+
#
|
361
|
+
# @api private
|
362
|
+
# @since 0.1.0
|
363
|
+
# @see Hanami::Router#resource
|
364
|
+
class Edit < Action
|
365
|
+
include DefaultMemberAction
|
366
|
+
end
|
367
|
+
|
368
|
+
# Update action
|
369
|
+
#
|
370
|
+
# @api private
|
371
|
+
# @since 0.1.0
|
372
|
+
# @see Hanami::Router#resource
|
373
|
+
class Update < Action
|
374
|
+
self.verb = :patch
|
375
|
+
end
|
376
|
+
|
377
|
+
# Destroy action
|
378
|
+
#
|
379
|
+
# @api private
|
380
|
+
# @since 0.1.0
|
381
|
+
# @see Hanami::Router#resource
|
382
|
+
class Destroy < Action
|
383
|
+
self.verb = :delete
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|