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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c56fe3e09566c9c52b9bba52b1532971003ee171
|
4
|
+
data.tar.gz: 8dd727332e2d8f642b69b1c2bc9b775253bb5295
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75299b0aa4540923f2bc275b44a2a0fedee0252ca3ff3be2d25ac9f89979125626bf73d6a21483b9d42a5c6a790d9fe3f642a87fd621166a56a3996ae149b353
|
7
|
+
data.tar.gz: 1cfff39ddebd9aeb968eb7dc9220f7da03984fab27b0e94f159eacb9f170de6f4ac9c72ee734bce30ef1dc5f4cd8eccf47498c5ef61a82f2a761c5a1515ea8a6
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Hanami::Router
|
2
|
+
Rack compatible HTTP router for Ruby
|
3
|
+
|
4
|
+
## v0.6.0 - 2016-01-22
|
5
|
+
### Changed
|
6
|
+
- [Luca Guidi] Renamed the project
|
7
|
+
|
8
|
+
## v0.5.1 - 2016-01-19
|
9
|
+
- [Anton Davydov] Print stacked lines for routes inspection
|
10
|
+
|
11
|
+
## v0.5.0 - 2016-01-12
|
12
|
+
### Added
|
13
|
+
- [Luca Guidi] Added `Lotus::Router#recognize` as a testing facility. Example `router.recognize('/') # => associated route`
|
14
|
+
- [Luca Guidi] Added `Lotus::Router.define` in order to wrap routes definitions in `config/routes.rb` when `Lotus::Router` is used outside of Lotus projects
|
15
|
+
- [David Strauß] Make `Lotus::Routing::Parsing::JsonParser` compatible with `application/vnd.api+json` MIME Type
|
16
|
+
- [Alfonso Uceda Pompa] Improved exception messages for `Lotus::Router#path` and `#url`
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
- [Alfonso Uceda Pompa] Ensure `Lotus::Router#path` and `#url` to generate correct URL for mounted applications
|
20
|
+
- [Vladislav Zarakovsky] Ensure Force SSL mode to respect Rack SPEC
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
- [Alfonso Uceda Pompa] A failure for body parsers raises a `Lotus::Routing::Parsing::BodyParsingError` exception
|
24
|
+
- [Karim Tarek] Introduced `Lotus::Router::Error` and let all the framework exceptions to inherit from it.
|
25
|
+
|
26
|
+
## v0.4.3 - 2015-09-30
|
27
|
+
### Added
|
28
|
+
- [Luca Guidi] Official support for JRuby 9k+
|
29
|
+
|
30
|
+
## v0.4.2 - 2015-07-10
|
31
|
+
### Fixed
|
32
|
+
- [Alfonso Uceda Pompa] Ensure mounted applications to not repeat their prefix (eg `/admin/admin`)
|
33
|
+
- [Thiago Felippe] Ensure router inspector properly prints routes with repeated entries (eg `/admin/dashboard/admin`)
|
34
|
+
|
35
|
+
## v0.4.1 - 2015-06-23
|
36
|
+
### Added
|
37
|
+
- [Alfonso Uceda Pompa] Force SSL (eg `Lotus::Router.new(force_ssl: true`).
|
38
|
+
- [Alfonso Uceda Pompa] Allow router to accept a `:prefix` option, in order to generate prefixed routes.
|
39
|
+
|
40
|
+
## v0.4.0 - 2015-05-15
|
41
|
+
### Added
|
42
|
+
- [Alfonso Uceda Pompa] Nested RESTful resource(s)
|
43
|
+
|
44
|
+
### Changed
|
45
|
+
- [Alfonso Uceda Pompa] RESTful resource(s) have a correct pluralization/singularization for variables and named routes (eg. `/books/:id` is now `:book` instead of `:books`)
|
46
|
+
|
47
|
+
## v0.3.0 - 2015-03-23
|
48
|
+
|
49
|
+
## v0.2.1 - 2015-01-30
|
50
|
+
### Added
|
51
|
+
- [Alfonso Uceda Pompa] Lotus::Action compat: invoke `.call` if defined, otherwise fall back to `#call`.
|
52
|
+
|
53
|
+
## v0.2.0 - 2014-12-23
|
54
|
+
### Added
|
55
|
+
- [Luca Guidi & Alfonso Uceda Pompa] Introduced routes inspector for CLI
|
56
|
+
- [Luca Guidi & Janko Marohnić] Introduced body parser for JSON
|
57
|
+
- [Luca Guidi] Introduced request body parsers: they parse body and turn into params.
|
58
|
+
- [Fred Wu] Introduced Router#define
|
59
|
+
|
60
|
+
### Fixed
|
61
|
+
- [Luca Guidi] Fix for member/collection actions in RESTful resource(s): allow to take actions with a leading slash.
|
62
|
+
- [Janko Marohnić] Fix for nested namespaces and RESTful resource(s) under namespace. They were generating wrong route names.
|
63
|
+
- [Luca Guidi] Made InvalidRouteException to inherit from StandardError so it can be catched from anonymous `rescue` clause
|
64
|
+
- [Luca Guidi] Fix RESTful resource(s) to respect :only/:except options
|
65
|
+
|
66
|
+
### Changed
|
67
|
+
- [Luca Guidi] Aligned naming conventions with Lotus::Controller: no more BooksController::Index. Use Books::Index instead.
|
68
|
+
- [Luca Guidi] Removed `:prefix` option for routes. Use `#namespace` blocks instead.
|
69
|
+
- [Janko Marohnić] Make 301 the default redirect status
|
70
|
+
|
71
|
+
## v0.1.1 - 2014-06-23
|
72
|
+
### Added
|
73
|
+
- [Luca Guidi] Introduced Lotus::Router#mount
|
74
|
+
- [Luca Guidi] Let specify a pattern for Lotus::Routing::EndpointResolver
|
75
|
+
- [Luca Guidi] Make Lotus::Routing::Endpoint::EndpointNotFound to inherit from StandardError, instead of Exception. This make it compatible with Rack::ShowExceptions.
|
76
|
+
|
77
|
+
## v0.1.0 - 2014-01-23
|
78
|
+
### Added
|
79
|
+
- [Luca Guidi] Official support for Ruby 2.1
|
80
|
+
- [Luca Guidi] Added support for OPTIONS HTTP verb
|
81
|
+
- [Luca Guidi] Added Lotus::Routing::EndpointNotFound when a lazy endpoint can't be found
|
82
|
+
- [Luca Guidi] Make action separator customizable via Lotus::Router options.
|
83
|
+
- [Luca Guidi] Catch http_router exceptions and re-raise them with names under Lotus::Routing. This helps to have a stable public API.
|
84
|
+
- [Luca Guidi] Lotus::Routing::Resource::CollectionAction use configurable controller and action name separator over the hardcoded value
|
85
|
+
- [Luca Guidi] Implemented Lotus::Routing::Namespace#resource
|
86
|
+
- [Luca Guidi] Lotus::Routing::EndpointResolver now accepts options to inject namespace and suffix
|
87
|
+
- [Luca Guidi] Allow resolver and route class to be injected via options
|
88
|
+
- [Luca Guidi] Return 404 for not found and 405 for unacceptable HTTP method
|
89
|
+
- [Luca Guidi] Allow non-finished Rack responses to be used
|
90
|
+
- [Luca Guidi] Implemented lazy loading for endpoints
|
91
|
+
- [Luca Guidi] Implemented Lotus::Router.new to take a block and define routes
|
92
|
+
- [Luca Guidi] Add support for resource
|
93
|
+
- [Luca Guidi] Support for resource's member and collection
|
94
|
+
- [Luca Guidi] Add support for namespaces
|
95
|
+
- [Luca Guidi] Added support for RESTful resources
|
96
|
+
- [Luca Guidi] Add support for POST, DELETE, PUT, PATCH, TRACE
|
97
|
+
- [Luca Guidi] Routes constraints
|
98
|
+
- [Luca Guidi] Named urls
|
99
|
+
- [Luca Guidi] Added support for Procs as endpoints
|
100
|
+
- [Luca Guidi] Implemented redirect
|
101
|
+
- [Luca Guidi] Basic routing
|
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright © 2014-2016 Luca Guidi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,8 +1,29 @@
|
|
1
1
|
# Hanami::Router
|
2
2
|
|
3
|
-
|
3
|
+
Rack compatible, lightweight and fast HTTP Router for Ruby and [Hanami](http://hanamirb.org).
|
4
|
+
|
5
|
+
## Status
|
6
|
+
|
7
|
+
[](https://badge.fury.io/rb/hanami-router)
|
8
|
+
[](https://travis-ci.org/hanami/router?branch=master)
|
9
|
+
[](https://coveralls.io/r/hanami/router)
|
10
|
+
[](https://codeclimate.com/github/hanami/router)
|
11
|
+
[](https://gemnasium.com/hanami/router)
|
12
|
+
[](http://inch-ci.org/github/hanami/router)
|
13
|
+
|
14
|
+
## Contact
|
15
|
+
|
16
|
+
* Home page: http://hanamirb.org
|
17
|
+
* Mailing List: http://hanamirb.org/mailing-list
|
18
|
+
* API Doc: http://rdoc.info/gems/hanami-router
|
19
|
+
* Bugs/Issues: https://github.com/hanami/router/issues
|
20
|
+
* Support: http://stackoverflow.com/questions/tagged/hanami
|
21
|
+
* Chat: http://chat.hanamirb.org
|
22
|
+
|
23
|
+
## Rubies
|
24
|
+
|
25
|
+
__Hanami::Router__ supports Ruby (MRI) 2.2+, JRuby 9k+
|
4
26
|
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
6
27
|
|
7
28
|
## Installation
|
8
29
|
|
@@ -14,23 +35,660 @@ gem 'hanami-router'
|
|
14
35
|
|
15
36
|
And then execute:
|
16
37
|
|
17
|
-
|
38
|
+
```shell
|
39
|
+
$ bundle
|
40
|
+
```
|
18
41
|
|
19
42
|
Or install it yourself as:
|
20
43
|
|
21
|
-
|
44
|
+
```shell
|
45
|
+
$ gem install hanami-router
|
46
|
+
```
|
47
|
+
|
48
|
+
## Getting Started
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'hanami/router'
|
52
|
+
|
53
|
+
app = Hanami::Router.new do
|
54
|
+
get '/', to: ->(env) { [200, {}, ['Welcome to Hanami::Router!']] }
|
55
|
+
end
|
56
|
+
|
57
|
+
Rack::Server.start app: app, Port: 2300
|
58
|
+
```
|
22
59
|
|
23
60
|
## Usage
|
24
61
|
|
25
|
-
|
62
|
+
__Hanami::Router__ is designed to work as a standalone framework or within a
|
63
|
+
context of a [Hanami](http://hanamirb.org) application.
|
64
|
+
|
65
|
+
For the standalone usage, it supports neat features:
|
66
|
+
|
67
|
+
### A Beautiful DSL:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
Hanami::Router.new do
|
71
|
+
get '/', to: ->(env) { [200, {}, ['Hi!']] }
|
72
|
+
get '/dashboard', to: Dashboard::Index
|
73
|
+
get '/rack-app', to: RackApp.new
|
74
|
+
get '/flowers', to: 'flowers#index'
|
75
|
+
get '/flowers/:id', to: 'flowers#show'
|
76
|
+
|
77
|
+
redirect '/legacy', to: '/'
|
78
|
+
|
79
|
+
mount Api::App, at: '/api'
|
80
|
+
|
81
|
+
namespace 'admin' do
|
82
|
+
get '/users', to: Users::Index
|
83
|
+
end
|
84
|
+
|
85
|
+
resource 'identity' do
|
86
|
+
member do
|
87
|
+
get '/avatar'
|
88
|
+
end
|
89
|
+
|
90
|
+
collection do
|
91
|
+
get '/api_keys'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
resources 'robots' do
|
96
|
+
member do
|
97
|
+
patch '/activate'
|
98
|
+
end
|
99
|
+
|
100
|
+
collection do
|
101
|
+
get '/search'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
### Fixed string matching:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
router = Hanami::Router.new
|
113
|
+
router.get '/hanami', to: ->(env) { [200, {}, ['Hello from Hanami!']] }
|
114
|
+
```
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
### String matching with variables:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
router = Hanami::Router.new
|
122
|
+
router.get '/flowers/:id', to: ->(env) { [200, {}, ["Hello from Flower no. #{ env['router.params'][:id] }!"]] }
|
123
|
+
```
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
### Variables Constraints:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
router = Hanami::Router.new
|
131
|
+
router.get '/flowers/:id', id: /\d+/, to: ->(env) { [200, {}, [":id must be a number!"]] }
|
132
|
+
```
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
### String matching with globbing:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
router = Hanami::Router.new
|
140
|
+
router.get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
|
141
|
+
```
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
### String matching with optional tokens:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
router = Hanami::Router.new
|
149
|
+
router.get '/hanami(.:format)' to: ->(env) { [200, {}, ["You've requested #{ env['router.params'][:format] }!"]] }
|
150
|
+
```
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
### Support for the most common HTTP methods:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
router = Hanami::Router.new
|
158
|
+
endpoint = ->(env) { [200, {}, ['Hello from Hanami!']] }
|
159
|
+
|
160
|
+
router.get '/hanami', to: endpoint
|
161
|
+
router.post '/hanami', to: endpoint
|
162
|
+
router.put '/hanami', to: endpoint
|
163
|
+
router.patch '/hanami', to: endpoint
|
164
|
+
router.delete '/hanami', to: endpoint
|
165
|
+
router.trace '/hanami', to: endpoint
|
166
|
+
```
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
### Redirect:
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
router = Hanami::Router.new
|
174
|
+
router.get '/redirect_destination', to: ->(env) { [200, {}, ['Redirect destination!']] }
|
175
|
+
router.redirect '/legacy', to: '/redirect_destination'
|
176
|
+
```
|
177
|
+
|
178
|
+
|
179
|
+
|
180
|
+
### Named routes:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
router = Hanami::Router.new(scheme: 'https', host: 'hanamirb.org')
|
184
|
+
router.get '/hanami', to: ->(env) { [200, {}, ['Hello from Hanami!']] }, as: :hanami
|
185
|
+
|
186
|
+
router.path(:hanami) # => "/hanami"
|
187
|
+
router.url(:hanami) # => "https://hanamirb.org/hanami"
|
188
|
+
```
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
### Namespaced routes:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
router = Hanami::Router.new
|
196
|
+
router.namespace 'animals' do
|
197
|
+
namespace 'mammals' do
|
198
|
+
get '/cats', to: ->(env) { [200, {}, ['Meow!']] }, as: :cats
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# and it generates:
|
203
|
+
|
204
|
+
router.path(:animals_mammals_cats) # => "/animals/mammals/cats"
|
205
|
+
```
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
### Mount Rack applications:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
Hanami::Router.new do
|
213
|
+
mount RackOne, at: '/rack1'
|
214
|
+
mount RackTwo, at: '/rack2'
|
215
|
+
mount RackThree.new, at: '/rack3'
|
216
|
+
mount ->(env) {[200, {}, ['Rack Four']]}, at: '/rack4'
|
217
|
+
mount 'dashboard#index', at: '/dashboard'
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
1. `RackOne` is used as it is (class), because it respond to `.call`
|
222
|
+
2. `RackTwo` is initialized, because it respond to `#call`
|
223
|
+
3. `RackThree` is used as it is (object), because it respond to `#call`
|
224
|
+
4. That Proc is used as it is, because it respond to `#call`
|
225
|
+
5. That string is resolved as `Dashboard::Index` ([Hanami::Controller](https://github.com/hanami/controller) integration)
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
### Duck typed endpoints:
|
230
|
+
|
231
|
+
Everything that responds to `#call` is invoked as it is:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
router = Hanami::Router.new
|
235
|
+
router.get '/hanami', to: ->(env) { [200, {}, ['Hello from Hanami!']] }
|
236
|
+
router.get '/middleware', to: Middleware
|
237
|
+
router.get '/rack-app', to: RackApp.new
|
238
|
+
router.get '/method', to: ActionControllerSubclass.action(:new)
|
239
|
+
```
|
240
|
+
|
241
|
+
|
242
|
+
If it's a string, it tries to instantiate a class from it:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
class RackApp
|
246
|
+
def call(env)
|
247
|
+
# ...
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
router = Hanami::Router.new
|
252
|
+
router.get '/hanami', to: 'rack_app' # it will map to RackApp.new
|
253
|
+
```
|
254
|
+
|
255
|
+
It also supports Controller + Action syntax:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
module Flowers
|
259
|
+
class Index
|
260
|
+
def call(env)
|
261
|
+
# ...
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
router = Hanami::Router.new
|
267
|
+
router.get '/flowers', to: 'flowers#index' # it will map to Flowers::Index.new
|
268
|
+
```
|
269
|
+
|
270
|
+
|
271
|
+
|
272
|
+
### Implicit Not Found (404):
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
router = Hanami::Router.new
|
276
|
+
router.call(Rack::MockRequest.env_for('/unknown')).status # => 404
|
277
|
+
```
|
278
|
+
|
279
|
+
### Controllers:
|
280
|
+
|
281
|
+
`Hanami::Router` has a special convention for controllers naming.
|
282
|
+
It allows to declare an action as an endpoint, with a special syntax: `<controller>#<action>`.
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
Hanami::Router.new do
|
286
|
+
get '/', to: 'welcome#index'
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
In the example above, the router will look for the `Welcome::Index` action.
|
26
291
|
|
27
|
-
|
292
|
+
#### Namespaces
|
28
293
|
|
29
|
-
|
294
|
+
In applications where for maintainability or technical reasons, this convention
|
295
|
+
can't work, `Hanami::Router` can accept a `:namespace` option, which defines the
|
296
|
+
Ruby namespace where to look for actions.
|
30
297
|
|
31
|
-
|
298
|
+
For instance, given a Hanami full stack application called `Bookshelf`, the
|
299
|
+
controllers are available under `Bookshelf::Controllers`.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
Hanami::Router.new(namespace: Bookshelf::Controllers) do
|
303
|
+
get '/', to: 'welcome#index'
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
In the example above, the router will look for the `Bookshelf::Controllers::Welcome::Index` action.
|
308
|
+
|
309
|
+
### RESTful Resource:
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
router = Hanami::Router.new
|
313
|
+
router.resource 'identity'
|
314
|
+
```
|
315
|
+
|
316
|
+
It will map:
|
317
|
+
|
318
|
+
<table>
|
319
|
+
<tr>
|
320
|
+
<th>Verb</th>
|
321
|
+
<th>Path</th>
|
322
|
+
<th>Action</th>
|
323
|
+
<th>Name</th>
|
324
|
+
<th>Named Route</th>
|
325
|
+
</tr>
|
326
|
+
<tr>
|
327
|
+
<td>GET</td>
|
328
|
+
<td>/identity</td>
|
329
|
+
<td>Identity::Show</td>
|
330
|
+
<td>:show</td>
|
331
|
+
<td>:identity</td>
|
332
|
+
</tr>
|
333
|
+
<tr>
|
334
|
+
<td>GET</td>
|
335
|
+
<td>/identity/new</td>
|
336
|
+
<td>Identity::New</td>
|
337
|
+
<td>:new</td>
|
338
|
+
<td>:new_identity</td>
|
339
|
+
</tr>
|
340
|
+
<tr>
|
341
|
+
<td>POST</td>
|
342
|
+
<td>/identity</td>
|
343
|
+
<td>Identity::Create</td>
|
344
|
+
<td>:create</td>
|
345
|
+
<td>:identity</td>
|
346
|
+
</tr>
|
347
|
+
<tr>
|
348
|
+
<td>GET</td>
|
349
|
+
<td>/identity/edit</td>
|
350
|
+
<td>Identity::Edit</td>
|
351
|
+
<td>:edit</td>
|
352
|
+
<td>:edit_identity</td>
|
353
|
+
</tr>
|
354
|
+
<tr>
|
355
|
+
<td>PATCH</td>
|
356
|
+
<td>/identity</td>
|
357
|
+
<td>Identity::Update</td>
|
358
|
+
<td>:update</td>
|
359
|
+
<td>:identity</td>
|
360
|
+
</tr>
|
361
|
+
<tr>
|
362
|
+
<td>DELETE</td>
|
363
|
+
<td>/identity</td>
|
364
|
+
<td>Identity::Destroy</td>
|
365
|
+
<td>:destroy</td>
|
366
|
+
<td>:identity</td>
|
367
|
+
</tr>
|
368
|
+
</table>
|
369
|
+
|
370
|
+
If you don't need all the default endpoints, just do:
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
router = Hanami::Router.new
|
374
|
+
router.resource 'identity', only: [:edit, :update]
|
375
|
+
|
376
|
+
#### which is equivalent to:
|
377
|
+
|
378
|
+
router.resource 'identity', except: [:show, :new, :create, :destroy]
|
379
|
+
```
|
380
|
+
|
381
|
+
|
382
|
+
If you need extra endpoints:
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
router = Hanami::Router.new
|
386
|
+
router.resource 'identity' do
|
387
|
+
member do
|
388
|
+
get 'avatar' # maps to Identity::Avatar
|
389
|
+
end
|
390
|
+
|
391
|
+
collection do
|
392
|
+
get 'authorizations' # maps to Identity::Authorizations
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
router.path(:avatar_identity) # => /identity/avatar
|
397
|
+
router.path(:authorizations_identity) # => /identity/authorizations
|
398
|
+
```
|
399
|
+
|
400
|
+
|
401
|
+
Configure controller:
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
router = Hanami::Router.new
|
405
|
+
router.resource 'profile', controller: 'identity'
|
406
|
+
|
407
|
+
router.path(:profile) # => /profile # Will route to Identity::Show
|
408
|
+
```
|
409
|
+
|
410
|
+
#### Nested Resources
|
411
|
+
|
412
|
+
We can nest resource(s):
|
413
|
+
|
414
|
+
```ruby
|
415
|
+
router = Hanami::Router.new
|
416
|
+
router.resource :identity do
|
417
|
+
resource :avatar
|
418
|
+
resources :api_keys
|
419
|
+
end
|
420
|
+
|
421
|
+
router.path(:identity_avatar) # => /identity/avatar
|
422
|
+
router.path(:new_identity_avatar) # => /identity/avatar/new
|
423
|
+
router.path(:edit_identity_avatar) # => /identity/avatar/new
|
424
|
+
|
425
|
+
router.path(:identity_api_keys) # => /identity/api_keys
|
426
|
+
router.path(:identity_api_key) # => /identity/api_keys/:id
|
427
|
+
router.path(:new_identity_api_key) # => /identity/api_keys/new
|
428
|
+
router.path(:edit_identity_api_key) # => /identity/api_keys/:id/edit
|
429
|
+
```
|
430
|
+
|
431
|
+
|
432
|
+
|
433
|
+
### RESTful Resources:
|
434
|
+
|
435
|
+
```ruby
|
436
|
+
router = Hanami::Router.new
|
437
|
+
router.resources 'flowers'
|
438
|
+
```
|
439
|
+
|
440
|
+
It will map:
|
441
|
+
|
442
|
+
<table>
|
443
|
+
<tr>
|
444
|
+
<th>Verb</th>
|
445
|
+
<th>Path</th>
|
446
|
+
<th>Action</th>
|
447
|
+
<th>Name</th>
|
448
|
+
<th>Named Route</th>
|
449
|
+
</tr>
|
450
|
+
<tr>
|
451
|
+
<td>GET</td>
|
452
|
+
<td>/flowers</td>
|
453
|
+
<td>Flowers::Index</td>
|
454
|
+
<td>:index</td>
|
455
|
+
<td>:flowers</td>
|
456
|
+
</tr>
|
457
|
+
<tr>
|
458
|
+
<td>GET</td>
|
459
|
+
<td>/flowers/:id</td>
|
460
|
+
<td>Flowers::Show</td>
|
461
|
+
<td>:show</td>
|
462
|
+
<td>:flower</td>
|
463
|
+
</tr>
|
464
|
+
<tr>
|
465
|
+
<td>GET</td>
|
466
|
+
<td>/flowers/new</td>
|
467
|
+
<td>Flowers::New</td>
|
468
|
+
<td>:new</td>
|
469
|
+
<td>:new_flower</td>
|
470
|
+
</tr>
|
471
|
+
<tr>
|
472
|
+
<td>POST</td>
|
473
|
+
<td>/flowers</td>
|
474
|
+
<td>Flowers::Create</td>
|
475
|
+
<td>:create</td>
|
476
|
+
<td>:flowers</td>
|
477
|
+
</tr>
|
478
|
+
<tr>
|
479
|
+
<td>GET</td>
|
480
|
+
<td>/flowers/:id/edit</td>
|
481
|
+
<td>Flowers::Edit</td>
|
482
|
+
<td>:edit</td>
|
483
|
+
<td>:edit_flower</td>
|
484
|
+
</tr>
|
485
|
+
<tr>
|
486
|
+
<td>PATCH</td>
|
487
|
+
<td>/flowers/:id</td>
|
488
|
+
<td>Flowers::Update</td>
|
489
|
+
<td>:update</td>
|
490
|
+
<td>:flower</td>
|
491
|
+
</tr>
|
492
|
+
<tr>
|
493
|
+
<td>DELETE</td>
|
494
|
+
<td>/flowers/:id</td>
|
495
|
+
<td>Flowers::Destroy</td>
|
496
|
+
<td>:destroy</td>
|
497
|
+
<td>:flower</td>
|
498
|
+
</tr>
|
499
|
+
</table>
|
500
|
+
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
router.path(:flowers) # => /flowers
|
504
|
+
router.path(:flower, id: 23) # => /flowers/23
|
505
|
+
router.path(:edit_flower, id: 23) # => /flowers/23/edit
|
506
|
+
```
|
507
|
+
|
508
|
+
|
509
|
+
|
510
|
+
If you don't need all the default endpoints, just do:
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
router = Hanami::Router.new
|
514
|
+
router.resources 'flowers', only: [:new, :create, :show]
|
515
|
+
|
516
|
+
#### which is equivalent to:
|
517
|
+
|
518
|
+
router.resources 'flowers', except: [:index, :edit, :update, :destroy]
|
519
|
+
```
|
520
|
+
|
521
|
+
|
522
|
+
If you need extra endpoints:
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
router = Hanami::Router.new
|
526
|
+
router.resources 'flowers' do
|
527
|
+
member do
|
528
|
+
get 'toggle' # maps to Flowers::Toggle
|
529
|
+
end
|
530
|
+
|
531
|
+
collection do
|
532
|
+
get 'search' # maps to Flowers::Search
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
router.path(:toggle_flower, id: 23) # => /flowers/23/toggle
|
537
|
+
router.path(:search_flowers) # => /flowers/search
|
538
|
+
```
|
539
|
+
|
540
|
+
|
541
|
+
Configure controller:
|
542
|
+
|
543
|
+
```ruby
|
544
|
+
router = Hanami::Router.new
|
545
|
+
router.resources 'blossoms', controller: 'flowers'
|
546
|
+
|
547
|
+
router.path(:blossom, id: 23) # => /blossoms/23 # Will route to Flowers::Show
|
548
|
+
```
|
549
|
+
|
550
|
+
#### Nested Resources
|
551
|
+
|
552
|
+
We can nest resource(s):
|
553
|
+
|
554
|
+
```ruby
|
555
|
+
router = Hanami::Router.new
|
556
|
+
router.resources :users do
|
557
|
+
resource :avatar
|
558
|
+
resources :favorites
|
559
|
+
end
|
560
|
+
|
561
|
+
router.path(:user_avatar, user_id: 1) # => /users/1/avatar
|
562
|
+
router.path(:new_user_avatar, user_id: 1) # => /users/1/avatar/new
|
563
|
+
router.path(:edit_user_avatar, user_id: 1) # => /users/1/avatar/edit
|
564
|
+
|
565
|
+
router.path(:user_favorites, user_id: 1) # => /users/1/favorites
|
566
|
+
router.path(:user_favorite, user_id: 1, id: 2) # => /users/1/favorites/2
|
567
|
+
router.path(:new_user_favorites, user_id: 1) # => /users/1/favorites/new
|
568
|
+
router.path(:edit_user_favorites, user_id: 1, id: 2) # => /users/1/favorites/2/edit
|
569
|
+
```
|
570
|
+
|
571
|
+
### Body Parsers
|
572
|
+
|
573
|
+
Rack ignores request bodies unless they come from a form submission.
|
574
|
+
If we have a JSON endpoint, the payload isn't available in the params hash:
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
Rack::Request.new(env).params # => {}
|
578
|
+
```
|
579
|
+
|
580
|
+
This feature enables body parsing for specific MIME Types.
|
581
|
+
It comes with a built-in JSON parser and allows to pass custom parsers.
|
582
|
+
|
583
|
+
#### JSON Parsing
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
require 'hanami/router'
|
587
|
+
|
588
|
+
endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }
|
589
|
+
|
590
|
+
router = Hanami::Router.new(parsers: [:json]) do
|
591
|
+
patch '/books/:id', to: endpoint
|
592
|
+
end
|
593
|
+
```
|
594
|
+
|
595
|
+
```shell
|
596
|
+
curl http://localhost:2300/books/1 \
|
597
|
+
-H "Content-Type: application/json" \
|
598
|
+
-H "Accept: application/json" \
|
599
|
+
-d '{"published":"true"}' \
|
600
|
+
-X PATCH
|
601
|
+
|
602
|
+
# => [200, {}, ["{:published=>\"true\",:id=>\"1\"}"]]
|
603
|
+
```
|
604
|
+
|
605
|
+
If the json can't be parsed an exception is raised:
|
606
|
+
|
607
|
+
```ruby
|
608
|
+
Hanami::Routing::Parsing::BodyParsingError
|
609
|
+
```
|
610
|
+
|
611
|
+
#### Custom Parsers
|
612
|
+
|
613
|
+
```ruby
|
614
|
+
require 'hanami/router'
|
615
|
+
|
616
|
+
# See Hanami::Routing::Parsing::Parser
|
617
|
+
class XmlParser
|
618
|
+
def mime_types
|
619
|
+
['application/xml', 'text/xml']
|
620
|
+
end
|
621
|
+
|
622
|
+
# Parse body and return a Hash
|
623
|
+
def parse(body)
|
624
|
+
# parse xml
|
625
|
+
rescue SomeXmlParsingError => e
|
626
|
+
raise Hanami::Routing::Parsing::BodyParsingError.new(e)
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
endpoint = ->(env) { [200, {},[env['router.params'].inspect]] }
|
631
|
+
|
632
|
+
router = Hanami::Router.new(parsers: [XmlParser.new]) do
|
633
|
+
patch '/authors/:id', to: endpoint
|
634
|
+
end
|
635
|
+
```
|
636
|
+
|
637
|
+
```shell
|
638
|
+
curl http://localhost:2300/authors/1 \
|
639
|
+
-H "Content-Type: application/xml" \
|
640
|
+
-H "Accept: application/xml" \
|
641
|
+
-d '<name>LG</name>' \
|
642
|
+
-X PATCH
|
643
|
+
|
644
|
+
# => [200, {}, ["{:name=>\"LG\",:id=>\"1\"}"]]
|
645
|
+
```
|
646
|
+
|
647
|
+
## Testing
|
648
|
+
|
649
|
+
```ruby
|
650
|
+
require 'hanami/router'
|
651
|
+
|
652
|
+
router = Hanami::Router.new do
|
653
|
+
get '/books/:id', to: 'books#show', as: :book
|
654
|
+
end
|
655
|
+
|
656
|
+
route = router.recognize('/books/23')
|
657
|
+
route.verb # "GET"
|
658
|
+
route.action # => "books#show"
|
659
|
+
route.params # => {:id=>"23"}
|
660
|
+
route.routable? # => true
|
661
|
+
|
662
|
+
route = router.recognize(:book, id: 23)
|
663
|
+
route.verb # "GET"
|
664
|
+
route.action # => "books#show"
|
665
|
+
route.params # => {:id=>"23"}
|
666
|
+
route.routable? # => true
|
667
|
+
|
668
|
+
route = router.recognize('/books/23', method: :post)
|
669
|
+
route.verb # "POST"
|
670
|
+
route.routable? # => false
|
671
|
+
```
|
672
|
+
|
673
|
+
## Versioning
|
674
|
+
|
675
|
+
__Hanami::Router__ uses [Semantic Versioning 2.0.0](http://semver.org)
|
32
676
|
|
33
677
|
## Contributing
|
34
678
|
|
35
|
-
|
679
|
+
1. Fork it
|
680
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
681
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
682
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
683
|
+
5. Create new Pull Request
|
684
|
+
|
685
|
+
## Acknowledgements
|
686
|
+
|
687
|
+
Thanks to Joshua Hull ([@joshbuddy](https://github.com/joshbuddy)) for his
|
688
|
+
[http_router](http://rubygems.org/gems/http_router).
|
689
|
+
|
690
|
+
## Copyright
|
691
|
+
|
692
|
+
Copyright © 2014-2016 Luca Guidi – Released under MIT License
|
36
693
|
|
694
|
+
This project was formerly known as Lotus (`lotus-router`).
|