rutter 0.1.2 → 0.2.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 +5 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +14 -4
- data/.travis.yml +12 -2
- data/Gemfile +4 -1
- data/LICENSE.txt +1 -1
- data/README.md +17 -102
- data/Rakefile +7 -1
- data/bench/config.ru +10 -9
- data/lib/rutter.rb +10 -7
- data/lib/rutter/builder.rb +180 -221
- data/lib/rutter/mount.rb +21 -0
- data/lib/rutter/naming.rb +95 -0
- data/lib/rutter/route.rb +69 -126
- data/lib/rutter/routes.rb +36 -9
- data/lib/rutter/scope.rb +39 -40
- data/lib/rutter/verbs.rb +8 -0
- data/lib/rutter/version.rb +1 -1
- data/rutter.gemspec +4 -3
- data/spec/integration/rack_spec.rb +21 -13
- data/spec/spec_helper.rb +6 -48
- data/spec/support/rack.rb +20 -0
- data/spec/unit/builder_spec.rb +86 -74
- data/spec/unit/namespace_spec.rb +26 -0
- data/spec/unit/route_spec.rb +33 -50
- data/spec/unit/routes_spec.rb +20 -11
- data/spec/unit/rutter_spec.rb +3 -2
- data/spec/unit/scope_spec.rb +49 -56
- metadata +25 -17
- data/bench/dynamic_routes +0 -20
- data/bench/expand +0 -19
- data/bench/helper.rb +0 -19
- data/bench/mount +0 -32
- data/bench/routes_helper +0 -24
- data/bench/static_routes +0 -20
- data/spec/integration/mount_spec.rb +0 -18
- data/spec/integration/params_spec.rb +0 -28
- data/spec/integration/redirect_spec.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 68f5fb5ae85793cb16d135928e3a26cf043a3eda09338f8c2a03117a16985b2d
|
4
|
+
data.tar.gz: c9766d83b10a8b6f103f26cc100ca753ecace464770fb739eccd4ecb2f2a876e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b0bcaae9355facffd058bf84021a4d62d1365312c254fd4e2ead08b6d907c68791572f47dc3451d35fcda32fb777ff3c94c5785b270cc290187aef3ce3e3260
|
7
|
+
data.tar.gz: 10e3b47288658c8dd7068447339fa54981684e225542c4c05ac7440dc2fa3f2112c8461ac849b855e16c740e4357d0f7e673a49eabac946a762c21b028882d3a
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,14 +1,21 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-performance
|
3
|
+
|
1
4
|
AllCops:
|
2
|
-
TargetRubyVersion: 2.
|
5
|
+
TargetRubyVersion: 2.5
|
3
6
|
DisplayCopNames: true
|
4
7
|
Exclude:
|
5
|
-
-
|
8
|
+
- "*.gemspec"
|
9
|
+
- "Gemfile"
|
10
|
+
|
11
|
+
Performance:
|
12
|
+
Enabled: true
|
6
13
|
|
7
14
|
Style/StringLiterals:
|
8
15
|
EnforcedStyle: double_quotes
|
9
16
|
|
10
|
-
|
11
|
-
|
17
|
+
Layout/EmptyLineAfterGuardClause:
|
18
|
+
Enabled: false
|
12
19
|
|
13
20
|
Metrics/MethodLength:
|
14
21
|
Max: 15
|
@@ -23,3 +30,6 @@ Metrics/ModuleLength:
|
|
23
30
|
|
24
31
|
ClassAndModuleChildren:
|
25
32
|
Enabled: false
|
33
|
+
|
34
|
+
Naming/UncommunicativeMethodParamName:
|
35
|
+
MinNameLength: 2
|
data/.travis.yml
CHANGED
@@ -1,19 +1,29 @@
|
|
1
1
|
language: ruby
|
2
2
|
script: "bundle exec rake spec:coverage"
|
3
3
|
cache: bundler
|
4
|
+
env:
|
5
|
+
global:
|
6
|
+
- CC_TEST_REPORTER_ID=c1b17cd36cd4d025298f11b2a3ee678e4977c3969986f9ee497d6984a2c82c56
|
4
7
|
before_install:
|
5
8
|
- "gem update --system"
|
9
|
+
before_script:
|
10
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
11
|
+
- chmod +x ./cc-test-reporter
|
12
|
+
- ./cc-test-reporter before-build
|
6
13
|
after_script:
|
7
|
-
-
|
14
|
+
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
8
15
|
rvm:
|
16
|
+
- 2.6.0
|
9
17
|
- 2.5.0
|
10
|
-
- 2.
|
18
|
+
- jruby-9.2.0.0
|
11
19
|
- ruby-head
|
12
20
|
- jruby-head
|
21
|
+
- truffleruby
|
13
22
|
matrix:
|
14
23
|
allow_failures:
|
15
24
|
- rvm: jruby-head
|
16
25
|
- rvm: ruby-head
|
26
|
+
- rvm: truffleruby
|
17
27
|
branches:
|
18
28
|
only:
|
19
29
|
- master
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -4,8 +4,11 @@ HTTP router for Rack.
|
|
4
4
|
|
5
5
|
## Status
|
6
6
|
|
7
|
+
Under development, not ready for prime-time just yet.
|
8
|
+
|
7
9
|
[](https://travis-ci.org/sandelius/rutter)
|
8
10
|
[](https://codeclimate.com/github/sandelius/rutter/coverage)
|
11
|
+
[](http://inch-ci.org/github/sandelius/rutter)
|
9
12
|
|
10
13
|
## Installation
|
11
14
|
|
@@ -15,126 +18,38 @@ Add this line to your application's Gemfile:
|
|
15
18
|
gem "rutter"
|
16
19
|
```
|
17
20
|
|
18
|
-
And then execute:
|
19
|
-
|
20
|
-
$ bundle
|
21
|
-
|
22
|
-
Or install it yourself as:
|
23
|
-
|
24
|
-
$ gem install rutter
|
25
|
-
|
26
21
|
## Usage
|
27
22
|
|
28
|
-
|
23
|
+
The main purpose of a router is to map URL's to endpoints. An endpoint needs to
|
24
|
+
be either an object that responds to `call(env)` or a string that can be resolved
|
25
|
+
to one.
|
29
26
|
|
30
|
-
|
31
|
-
require "rutter"
|
32
|
-
|
33
|
-
router = Rutter.new do
|
34
|
-
get "/", to: ->(env) { [200, {}, ["Hello World"]] }
|
35
|
-
end
|
36
|
-
|
37
|
-
run router.freeze
|
38
|
-
```
|
39
|
-
|
40
|
-
### HTTP verbs
|
41
|
-
|
42
|
-
The router supports most of the verbs available.
|
27
|
+
Below are examples of both endpoint styles:
|
43
28
|
|
44
29
|
```ruby
|
45
|
-
require "rutter"
|
46
|
-
|
47
30
|
Rutter.new do
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
patch "/", to: ->(env) {}
|
52
|
-
delete "/", to: ->(env) {}
|
53
|
-
options "/", to: ->(env) {}
|
54
|
-
head "/", to: ->(env) {}
|
55
|
-
trace "/", to: ->(env) {}
|
56
|
-
end
|
31
|
+
# Endpoint that implements #call
|
32
|
+
get "/books", to: ->(env) { [200, {}, ["My Bookshelf"]] }
|
33
|
+
end.freeze
|
57
34
|
```
|
58
35
|
|
59
|
-
### Named parameters
|
60
|
-
|
61
|
-
In the example `:title` is a *named parameter*. The values are accessible via `env["rutter.params"]` and it contains a `Hash<String => String>`.
|
62
|
-
|
63
36
|
```ruby
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
37
|
+
class Books
|
38
|
+
def self.call(env)
|
39
|
+
[200, {}, ["My Bookshelf"]]
|
40
|
+
end
|
68
41
|
end
|
69
|
-
```
|
70
|
-
|
71
|
-
Named parameters only match a single path segment:
|
72
|
-
|
73
|
-
```
|
74
|
-
/books/eloquent-ruby match
|
75
|
-
/books/confident-ruby match
|
76
|
-
/books/confident-ruby.rb no match
|
77
|
-
/books/eloquent-ruby/reviews no match
|
78
|
-
/books/ no match
|
79
|
-
```
|
80
|
-
|
81
|
-
### Catch-All parameters
|
82
|
-
|
83
|
-
*catch-all* parameters have the form `*title`. Like the name suggests, they match everything, event new `/` segments. Therefore they must always be at the end of the pattern.
|
84
|
-
|
85
|
-
```ruby
|
86
|
-
require "rutter"
|
87
42
|
|
88
43
|
Rutter.new do
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
```
|
94
|
-
/books/eloquent-ruby match
|
95
|
-
/books/confident-ruby match
|
96
|
-
/books/confident-ruby.rb match
|
97
|
-
/books/eloquent-ruby/reviews match
|
98
|
-
/books/ no match
|
99
|
-
```
|
100
|
-
|
101
|
-
### Optional segments
|
102
|
-
|
103
|
-
Support for optional segments have the form `(i-am-optional)`.
|
104
|
-
|
105
|
-
```ruby
|
106
|
-
require "rutter"
|
107
|
-
|
108
|
-
Rutter.new do
|
109
|
-
get "/books(/:title)", to: ->(env) {}
|
110
|
-
end
|
111
|
-
```
|
112
|
-
|
113
|
-
```
|
114
|
-
/books/eloquent-ruby match
|
115
|
-
/books/confident-ruby match
|
116
|
-
/books/confident-ruby.rb no match
|
117
|
-
/books/eloquent-ruby/reviews no match
|
118
|
-
/books match
|
119
|
-
```
|
120
|
-
|
121
|
-
### Redirects
|
122
|
-
|
123
|
-
Make legacy paths point to a new destination.
|
124
|
-
|
125
|
-
```ruby
|
126
|
-
require "rutter"
|
127
|
-
|
128
|
-
Rutter.new do
|
129
|
-
get "/legacy-path", to: redirect("/new_path")
|
130
|
-
end
|
44
|
+
# String that resolves to endpoint
|
45
|
+
get "/books", to: "books" # This will be resolved to <Books>
|
46
|
+
end.freeze
|
131
47
|
```
|
132
48
|
|
133
49
|
## Contributing
|
134
50
|
|
135
51
|
Bug reports and pull requests are welcome on GitHub at https://github.com/sandelius/rutter. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
136
52
|
|
137
|
-
|
138
53
|
## License
|
139
54
|
|
140
55
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require "bundler/gem_tasks"
|
@@ -14,3 +13,10 @@ namespace :spec do
|
|
14
13
|
end
|
15
14
|
|
16
15
|
task default: :spec
|
16
|
+
|
17
|
+
task :clean do
|
18
|
+
FileUtils.rm_r ".yardoc" if Dir.exist?(".yardoc")
|
19
|
+
FileUtils.rm_r "doc" if Dir.exist?("doc")
|
20
|
+
FileUtils.rm_r "pkg" if Dir.exist?("pkg")
|
21
|
+
FileUtils.rm_r "coverage" if Dir.exist?("coverage")
|
22
|
+
end
|
data/bench/config.ru
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
# $ wrk -t 2 http://localhost:9292/
|
3
|
+
# puma -e production -t 16:16
|
5
4
|
|
6
|
-
require "
|
7
|
-
|
5
|
+
require "bundler/setup"
|
6
|
+
require "rutter"
|
8
7
|
|
9
8
|
router = Rutter.new do
|
10
|
-
|
11
|
-
|
12
|
-
}
|
13
|
-
end
|
9
|
+
# wrk -t 2 http://localhost:9292/
|
10
|
+
get "/", to: ->(_) { [200, {}, ["Hello World"]] }
|
14
11
|
|
15
|
-
|
12
|
+
# wrk -t 2 http://localhost:9292/ruby
|
13
|
+
get "/:lang", to: ->(env) { [200, {}, [env["rutter.params"]["lang"]]] }
|
14
|
+
end.freeze
|
15
|
+
|
16
|
+
run router
|
data/lib/rutter.rb
CHANGED
@@ -2,16 +2,19 @@
|
|
2
2
|
|
3
3
|
require_relative "rutter/version"
|
4
4
|
require_relative "rutter/builder"
|
5
|
-
require_relative "rutter/routes"
|
6
5
|
|
7
|
-
# HTTP router for
|
6
|
+
# HTTP router for Rack.
|
8
7
|
module Rutter
|
9
|
-
#
|
8
|
+
# Factory method for creating a new builder object.
|
10
9
|
#
|
11
|
-
# @
|
10
|
+
# @param base [String]
|
11
|
+
# Base URL, used for generating URLs.
|
12
12
|
#
|
13
|
-
# @
|
14
|
-
|
15
|
-
|
13
|
+
# @yield
|
14
|
+
# Executes the block inside the created builder context.
|
15
|
+
#
|
16
|
+
# @see Rutter::Builder#initialize
|
17
|
+
def self.new(base: "http://localhost:9292", &block)
|
18
|
+
Builder.new(base: base, &block)
|
16
19
|
end
|
17
20
|
end
|
data/lib/rutter/builder.rb
CHANGED
@@ -1,150 +1,75 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "uri"
|
4
4
|
|
5
|
+
require_relative "naming"
|
6
|
+
require_relative "verbs"
|
5
7
|
require_relative "route"
|
8
|
+
require_relative "mount"
|
6
9
|
require_relative "scope"
|
10
|
+
require_relative "routes"
|
7
11
|
|
8
12
|
module Rutter
|
9
|
-
# The
|
10
|
-
# it's to a controller and an action or a Rack endpoint.
|
13
|
+
# The builder map URL's to endpoints.
|
11
14
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# get "/", to: ->(env) {}
|
22
|
-
# post "/", to: ->(env) {}
|
23
|
-
# put "/", to: ->(env) {}
|
24
|
-
# patch "/", to: ->(env) {}
|
25
|
-
# delete "/", to: ->(env) {}
|
26
|
-
# options "/", to: ->(env) {}
|
27
|
-
# head "/", to: ->(env) {}
|
28
|
-
# trace "/", to: ->(env) {}
|
29
|
-
# end.freeze
|
15
|
+
# @!attribute [r] flat_map
|
16
|
+
# @return [Array]
|
17
|
+
# Defined routes in a flat map.
|
18
|
+
# @!attribute [r] verb_map
|
19
|
+
# @return [Hash]
|
20
|
+
# Defined routes grouped by verb.
|
21
|
+
# @!attribute [r] named_map
|
22
|
+
# @return [Hash]
|
23
|
+
# Defined routes grouped by route name.
|
30
24
|
class Builder
|
31
|
-
attr_reader :flat_map
|
25
|
+
attr_reader :flat_map
|
26
|
+
attr_reader :verb_map
|
27
|
+
attr_reader :named_map
|
32
28
|
|
33
|
-
# Initializes the
|
29
|
+
# Initializes the builder.
|
34
30
|
#
|
35
|
-
# @param
|
36
|
-
# URL
|
37
|
-
# @param host [String]
|
38
|
-
# URL host.
|
39
|
-
# @param port [Integer]
|
40
|
-
# URL port.
|
41
|
-
# @param controller_suffix [String]
|
42
|
-
# Suffix string for controllers (BooksController).
|
31
|
+
# @param base [String]
|
32
|
+
# Base URL, used for generating URLs.
|
43
33
|
#
|
44
34
|
# @yield
|
45
|
-
#
|
35
|
+
# Executes the block inside the created builder context.
|
46
36
|
#
|
47
|
-
# @return [
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
controller_suffix: nil,
|
53
|
-
&block
|
54
|
-
)
|
55
|
-
@scheme = scheme
|
56
|
-
@host = host
|
57
|
-
@port = port
|
58
|
-
@controller_suffix = controller_suffix
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @private
|
40
|
+
def initialize(base: "http://localhost:9292", &block)
|
41
|
+
@uri = URI(base).freeze
|
59
42
|
@flat_map = []
|
60
|
-
@verb_map = Hash.new { |
|
43
|
+
@verb_map = Hash.new { |h, k| h[k] = [] }
|
61
44
|
@named_map = {}
|
62
45
|
|
63
46
|
instance_eval(&block) if block_given?
|
64
47
|
end
|
65
48
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
# @param app [Class, Object]
|
69
|
-
# A class or an object that responds to `call`.
|
70
|
-
# @param at [String]
|
71
|
-
# Path prefix to match.
|
49
|
+
# Create a scoped set of routes.
|
72
50
|
#
|
73
|
-
# @
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
#
|
79
|
-
#
|
80
|
-
# @param destination [String]
|
81
|
-
# The destination.
|
82
|
-
# @param status [Integer]
|
83
|
-
# Response status code.
|
84
|
-
#
|
85
|
-
# @return [Proc]
|
86
|
-
# Redirect endpoint.
|
87
|
-
#
|
88
|
-
# @example basic usage
|
89
|
-
# Rutter.new do
|
90
|
-
# get "/legacy-path", to: redirect("/new_path")
|
91
|
-
# end
|
51
|
+
# @param path [String]
|
52
|
+
# Scope path prefix.
|
53
|
+
# @param namespace [String, Symbol]
|
54
|
+
# Scope namespace.
|
55
|
+
# @param as [Symbol]
|
56
|
+
# Scope name prefix.
|
92
57
|
#
|
93
|
-
# @
|
94
|
-
#
|
95
|
-
# get "/legacy-path", to: redirect("/new_path", status: 301)
|
96
|
-
# end
|
97
|
-
def redirect(destination, status: 302)
|
98
|
-
->(_env) { [status, { "Location" => destination.to_s }, []] }
|
99
|
-
end
|
100
|
-
|
101
|
-
# Defines a root route (a GET route for '/').
|
58
|
+
# @yield
|
59
|
+
# Block is evaluated inside the created scope context.
|
102
60
|
#
|
103
|
-
# @return [Rutter::
|
61
|
+
# @return [Rutter::Scope]
|
104
62
|
#
|
105
|
-
# @see Rutter::
|
106
|
-
def
|
107
|
-
|
63
|
+
# @see Rutter::Scope
|
64
|
+
def scope(path: nil, namespace: nil, as: nil, &block)
|
65
|
+
Scope.new(self, path: path, namespace: namespace, as: as, &block)
|
108
66
|
end
|
109
67
|
|
110
|
-
#
|
68
|
+
# Creates a scoped collection of routes with the given name as namespace.
|
111
69
|
#
|
112
|
-
# @param
|
113
|
-
#
|
114
|
-
# @param path [String]
|
115
|
-
# Path template.
|
116
|
-
# @param as [String, Symbol]
|
117
|
-
# Route identifier (name).
|
118
|
-
# @param to [String, Proc]
|
119
|
-
# Route endpoint.
|
70
|
+
# @param name [Symbol, String]
|
71
|
+
# Scope namespace.
|
120
72
|
#
|
121
|
-
# @return [Rutter::Route]
|
122
|
-
def add(method, path, to:, as: nil)
|
123
|
-
route = Route.new(method, path, to, controller_suffix: @controller_suffix)
|
124
|
-
|
125
|
-
flat_map << route
|
126
|
-
verb_map[route.method] << route
|
127
|
-
|
128
|
-
return route unless as
|
129
|
-
|
130
|
-
add_named_route!(as, route)
|
131
|
-
end
|
132
|
-
|
133
|
-
# @see Rutter::Builder#add
|
134
|
-
Route::VERBS.each do |verb|
|
135
|
-
define_method verb.downcase do |path, to:, **opts|
|
136
|
-
add verb, path, to: to, **opts
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Starts a scoped collection of routes.
|
141
|
-
#
|
142
|
-
# @option opts [String] :path (nil)
|
143
|
-
# Path prefix
|
144
|
-
# @option opts [String] :namespace (nil)
|
145
|
-
# Namespace prefix
|
146
|
-
# @option opts [String, Symbol] :as (nil)
|
147
|
-
# Name prefix
|
148
73
|
# @yield
|
149
74
|
# Scope context.
|
150
75
|
#
|
@@ -152,155 +77,189 @@ module Rutter
|
|
152
77
|
#
|
153
78
|
# @example
|
154
79
|
# Rutter.new do
|
155
|
-
#
|
156
|
-
#
|
157
|
-
# get "/cats", to: "Cats#index", as: :cats
|
158
|
-
# end
|
80
|
+
# namespace :admin do
|
81
|
+
# get "/login", to: "sessions#new", as: :login
|
159
82
|
# end
|
160
83
|
# end
|
84
|
+
def namespace(name, &block)
|
85
|
+
scope path: name, namespace: name, as: name, &block
|
86
|
+
end
|
87
|
+
|
88
|
+
# Mount a Rack compatible at the given path prefix.
|
161
89
|
#
|
162
|
-
# @
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
def
|
169
|
-
|
90
|
+
# @param app [#call]
|
91
|
+
# Application to mount.
|
92
|
+
# @param at [String]
|
93
|
+
# Path prefix to match.
|
94
|
+
#
|
95
|
+
# @return [Rutter::Mount]
|
96
|
+
def mount(app, at:)
|
97
|
+
route = Mount.new(at, app)
|
98
|
+
@flat_map << route
|
99
|
+
VERBS.each { |verb| @verb_map[verb] << route }
|
100
|
+
route
|
170
101
|
end
|
171
102
|
|
172
|
-
#
|
103
|
+
# Generates a path from the given arguments.
|
173
104
|
#
|
174
105
|
# @param name [Symbol]
|
175
|
-
# Name of the route.
|
176
|
-
#
|
177
|
-
#
|
106
|
+
# Name of the route to generate path from.
|
107
|
+
#
|
108
|
+
# @overload path(name, key: value)
|
109
|
+
# @param key [String, Integer, Array]
|
110
|
+
# Key value.
|
111
|
+
# @overload path(name, key: value, key2: value2)
|
112
|
+
# @param key2 [String, Integer, Array]
|
113
|
+
# Key value.
|
178
114
|
#
|
179
115
|
# @return [String]
|
116
|
+
# Generated path.
|
180
117
|
#
|
181
|
-
# @raise [
|
182
|
-
#
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
118
|
+
# @raise [RuntimeError]
|
119
|
+
# If the route cannot be found.
|
120
|
+
#
|
121
|
+
# @see Rutter::Route#expand
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# router = Rutter.new(base: "http://rutter.org")
|
125
|
+
# router.get "/login", to: "sessions#new", as: :login
|
126
|
+
# router.get "/books/:id", to: "books#show", as: :book
|
127
|
+
#
|
128
|
+
# router.path(:login)
|
129
|
+
# # => "/login"
|
130
|
+
# router.path(:login, return_to: "/")
|
131
|
+
# # => "/login?return_to=/"
|
132
|
+
# router.path(:book, id: 82)
|
133
|
+
# # => "/books/82"
|
134
|
+
def path(name, *args)
|
135
|
+
unless (route = @named_map[name])
|
136
|
+
raise "No route called '#{name}' was found"
|
137
|
+
end
|
187
138
|
|
188
|
-
|
189
|
-
path = path.gsub(%r{\A[\/]+|[\/]+\z}, "")
|
190
|
-
"/#{path}"
|
139
|
+
route.expand(*args)
|
191
140
|
end
|
192
141
|
|
193
|
-
#
|
142
|
+
# Generates a full URL from the given arguments.
|
194
143
|
#
|
195
144
|
# @param name [Symbol]
|
196
|
-
# Name of the route.
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
# @
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
145
|
+
# Name of the route to generate URL from.
|
146
|
+
#
|
147
|
+
# @overload expand(name, subdomain: value)
|
148
|
+
# @param subdomain [String, Symbol]
|
149
|
+
# Subdomain to be added to the host.
|
150
|
+
# @overload expand(name, key: value)
|
151
|
+
# @param key [String, Integer, Array]
|
152
|
+
# Key value.
|
153
|
+
# @overload expand(name, key: value, key2: value2)
|
154
|
+
# @param key2 [String, Integer, Array]
|
155
|
+
# Key value.
|
206
156
|
#
|
207
157
|
# @return [String]
|
158
|
+
# Generated URL.
|
208
159
|
#
|
209
|
-
# @raise [
|
210
|
-
# If route
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
160
|
+
# @raise [RuntimeError]
|
161
|
+
# If the route cannot be found.
|
162
|
+
#
|
163
|
+
# @see Rutter::Builder#path
|
164
|
+
#
|
165
|
+
# @example
|
166
|
+
# router = Rutter.new(base: "http://rutter.org")
|
167
|
+
# router.get "/login", to: "sessions#new", as: :login
|
168
|
+
# router.get "/books/:id", to: "books#show", as: :book
|
169
|
+
#
|
170
|
+
# router.url(:login)
|
171
|
+
# # => "http://rutter.org/login"
|
172
|
+
# router.url(:login, return_to: "/")
|
173
|
+
# # => "http://rutter.org/login?return_to=/"
|
174
|
+
# router.url(:book, id: 82)
|
175
|
+
# # => "http://rutter.org/books/82"
|
176
|
+
def url(name, **args)
|
177
|
+
host = @uri.scheme + "://"
|
178
|
+
host += "#{args.delete(:subdomain)}." if args.key?(:subdomain)
|
179
|
+
host += @uri.host
|
180
|
+
host += ":#{@uri.port}" if @uri.port != 80 && @uri.port != 443
|
181
|
+
host + path(name, args)
|
219
182
|
end
|
220
183
|
|
221
|
-
#
|
184
|
+
# Add a new, frozen, route to the map.
|
222
185
|
#
|
223
|
-
# @param
|
224
|
-
#
|
186
|
+
# @param verb [String]
|
187
|
+
# Request verb to match.
|
188
|
+
# @param path [String]
|
189
|
+
# Path template to match.
|
190
|
+
# @param to [#call]
|
191
|
+
# Rack endpoint.
|
192
|
+
# @param as [Symbol, String]
|
193
|
+
# Route name/identifier.
|
194
|
+
# @param constraints [Hash]
|
195
|
+
# Route segment constraints.
|
225
196
|
#
|
226
|
-
# @return [
|
227
|
-
# Serialized Rack response.
|
197
|
+
# @return [Rutter::Route]
|
228
198
|
#
|
229
|
-
# @
|
199
|
+
# @raise [ArgumentError]
|
200
|
+
# If verb is unsupported.
|
230
201
|
#
|
231
202
|
# @private
|
232
|
-
def
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
ctrl = Object.const_get(ctrl) if ctrl.is_a?(String)
|
238
|
-
return ctrl.call(env)
|
203
|
+
def add(verb, path, to:, as: nil, constraints: nil)
|
204
|
+
verb = verb.to_s.upcase
|
205
|
+
|
206
|
+
unless VERBS.include?(verb)
|
207
|
+
raise ArgumentError, "Unsupported verb '#{verb}'"
|
239
208
|
end
|
240
209
|
|
241
|
-
|
210
|
+
route = Route.new(path, to, constraints)
|
211
|
+
@flat_map << route
|
212
|
+
@verb_map[verb] << route
|
213
|
+
return route unless as
|
214
|
+
|
215
|
+
named_map[Naming.route_name(as)] = route
|
242
216
|
end
|
243
217
|
|
244
|
-
#
|
218
|
+
# Freeze the state of the router.
|
245
219
|
#
|
246
220
|
# @return [self]
|
247
221
|
def freeze
|
248
|
-
flat_map.freeze
|
249
|
-
verb_map.freeze
|
250
|
-
|
222
|
+
@flat_map.freeze
|
223
|
+
@verb_map.freeze
|
224
|
+
@verb_map.each_value(&:freeze)
|
225
|
+
@named_map.freeze
|
251
226
|
|
252
227
|
super
|
253
228
|
end
|
254
229
|
|
255
|
-
|
230
|
+
# @see #add
|
231
|
+
VERBS.each do |verb|
|
232
|
+
define_method verb.downcase do |path, to:, as: nil, constraints: nil|
|
233
|
+
add verb, path, to: to, as: as, constraints: constraints
|
234
|
+
end
|
235
|
+
end
|
256
236
|
|
257
|
-
#
|
237
|
+
# Process the request and is compatible with the Rack protocol.
|
238
|
+
#
|
239
|
+
# @param env [Hash]
|
240
|
+
# Rack environment hash.
|
258
241
|
#
|
259
|
-
# @
|
242
|
+
# @return [Array]
|
243
|
+
# Serialized Rack response.
|
260
244
|
#
|
261
|
-
# @
|
245
|
+
# @see http://rack.github.io
|
262
246
|
#
|
263
247
|
# @private
|
264
|
-
def
|
265
|
-
|
266
|
-
path = path.chomp("/") if path != "/" && path.end_with?("/")
|
267
|
-
|
268
|
-
routes = verb_map[env["REQUEST_METHOD"]]
|
269
|
-
routes.each do |route|
|
270
|
-
return route if route.match?(path)
|
271
|
-
end
|
272
|
-
|
273
|
-
false
|
274
|
-
end
|
248
|
+
def call(env)
|
249
|
+
request_method = env["REQUEST_METHOD"]
|
275
250
|
|
276
|
-
|
277
|
-
def add_named_route!(name, route)
|
278
|
-
name = normalize_route_name(name)
|
251
|
+
return NOT_FOUND_RESPONSE unless @verb_map.key?(request_method)
|
279
252
|
|
280
|
-
|
281
|
-
|
253
|
+
routes = @verb_map[request_method]
|
254
|
+
routes.each do |route|
|
255
|
+
next unless route.match?(env)
|
256
|
+
return route.call(env)
|
282
257
|
end
|
283
258
|
|
284
|
-
|
285
|
-
end
|
286
|
-
|
287
|
-
# @private
|
288
|
-
def normalize_route_name(name)
|
289
|
-
name.to_s
|
290
|
-
.tr("/", "_")
|
291
|
-
.gsub(/[_]{2,}/, "_")
|
292
|
-
.gsub(/\A_|_\z/, "")
|
293
|
-
.downcase
|
294
|
-
.to_sym
|
259
|
+
NOT_FOUND_RESPONSE
|
295
260
|
end
|
296
261
|
|
297
|
-
# Response returned when no route matched the request.
|
298
|
-
#
|
299
262
|
# @private
|
300
|
-
NOT_FOUND_RESPONSE = [
|
301
|
-
404,
|
302
|
-
{ "X-Cascade" => "pass" },
|
303
|
-
["Route Not Found"]
|
304
|
-
].freeze
|
263
|
+
NOT_FOUND_RESPONSE = [404, { "X-Cascade" => "pass" }, ["Not Found"]].freeze
|
305
264
|
end
|
306
265
|
end
|