named-routes 0.1.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.
- data/CHANGES +0 -0
- data/MIT.LICENSE +20 -0
- data/README.md +111 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/named-routes/routes.rb +78 -0
- data/lib/named-routes/uri.rb +13 -0
- data/lib/named-routes.rb +20 -0
- data/spec/named-routes/routes_spec.rb +146 -0
- data/spec/spec_helper.rb +17 -0
- metadata +80 -0
data/CHANGES
ADDED
File without changes
|
data/MIT.LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Brian Takita
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# NamedRoutes
|
2
|
+
|
3
|
+
A simple and generic named routes api. It works really well with Sinatra.
|
4
|
+
|
5
|
+
## Installation/Usage
|
6
|
+
|
7
|
+
gem install named-routes
|
8
|
+
|
9
|
+
## Route Definitions
|
10
|
+
|
11
|
+
You can define a named route by providing the name of the method and the definition.
|
12
|
+
|
13
|
+
NamedRoutes.path(:user, "/users/:user_id") # => "/users/:user_id"
|
14
|
+
|
15
|
+
You can use this in conjunction with Sinatra routes like so:
|
16
|
+
|
17
|
+
include NamedRoutes
|
18
|
+
get path(:user, "/users/:user_id") do
|
19
|
+
# ...
|
20
|
+
end
|
21
|
+
|
22
|
+
If you have multiple handlers for the same route, you can use the block syntax:
|
23
|
+
|
24
|
+
include NamedRoutes
|
25
|
+
path(:user, "/users/:user_id") do |_|
|
26
|
+
get _ do
|
27
|
+
# ...
|
28
|
+
end
|
29
|
+
|
30
|
+
post _ do
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
You can also define prefixes on the route definitions:
|
36
|
+
|
37
|
+
include NamedRoutes
|
38
|
+
routes.prefix = "admin"
|
39
|
+
|
40
|
+
path(:user, "/users/:user_id") do |_| # => /admin/users/:user_id
|
41
|
+
get _ do
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
|
45
|
+
post _ do
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
## Route Helpers
|
51
|
+
|
52
|
+
You can access the routes by doing the following.
|
53
|
+
|
54
|
+
include NamedRoutes
|
55
|
+
routes.host = "example.com"
|
56
|
+
path(:user, "/users/:user_id")
|
57
|
+
routes.user(:user_id => 42) # => "/users/42"
|
58
|
+
routes.http.user(:user_id => 42) # => "http://example.com/users/42"
|
59
|
+
routes.https.user(:user_id => 42) # => "http://example.com/users/42"
|
60
|
+
|
61
|
+
It also works with prefixes:
|
62
|
+
|
63
|
+
include NamedRoutes
|
64
|
+
routes.host = "example.com"
|
65
|
+
routes.prefix = "admin"
|
66
|
+
path(:user, "/users/:user_id")
|
67
|
+
routes.user(:user_id => 42) # => "/users/42"
|
68
|
+
routes.http.user(:user_id => 42) # => "http://example.com/admin/users/42"
|
69
|
+
routes.https.user(:user_id => 42) # => "http://example.com/admin/users/42"
|
70
|
+
|
71
|
+
And with query params:
|
72
|
+
|
73
|
+
include NamedRoutes
|
74
|
+
routes.host = "example.com"
|
75
|
+
routes.prefix = "admin"
|
76
|
+
path(:user, "/users/:user_id")
|
77
|
+
routes.user(:user_id => 42, :foo => "bar of soap") # => "/users/42&foo=bar+of+soap"
|
78
|
+
|
79
|
+
## Advanced Usages
|
80
|
+
|
81
|
+
You can also inherit Routes to have different sets of Routes. This is useful if you want route sets with different prefixes.
|
82
|
+
|
83
|
+
class AdminRoutes < NamedRoutes::Routes
|
84
|
+
self.prefix = "admin"
|
85
|
+
end
|
86
|
+
|
87
|
+
class PartayRoutes < NamedRoutes::Routes
|
88
|
+
self.prefix = "partay"
|
89
|
+
end
|
90
|
+
|
91
|
+
def admin_routes
|
92
|
+
AdminRoutes
|
93
|
+
end
|
94
|
+
|
95
|
+
def partay_routes
|
96
|
+
PartayRoutes
|
97
|
+
end
|
98
|
+
|
99
|
+
get admin_routes.path(:user, "/users/:user_id") do # => /admin/users/:user_id
|
100
|
+
# ...
|
101
|
+
end
|
102
|
+
|
103
|
+
admin_routes.user(:user_id => 42) # => "/admin/users/42"
|
104
|
+
|
105
|
+
get partay_routes.path(:user, "/users/:user_id") do # => /partay/users/:user_id
|
106
|
+
# ...
|
107
|
+
end
|
108
|
+
|
109
|
+
partay_routes.user(:user_id => 42, :beer => "pabst") # => "/partay/users/42&beer=pabst"
|
110
|
+
|
111
|
+
Copyright (c) 2010 Brian Takita. This software is licensed under the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "rake"
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |s|
|
9
|
+
s.name = "named-routes"
|
10
|
+
s.summary = "A simple and generic named routes api. It works really well with Sinatra."
|
11
|
+
s.email = "brian.takita@gmail.com"
|
12
|
+
s.homepage = "http://github.com/btakita/named-routes"
|
13
|
+
s.summary = "A simple and generic named routes api. It works really well with Sinatra."
|
14
|
+
s.authors = ["Brian Takita"]
|
15
|
+
s.files = FileList[
|
16
|
+
'[A-Z]*',
|
17
|
+
'*.rb',
|
18
|
+
'lib/**/*.rb',
|
19
|
+
'spec/**/*.rb'
|
20
|
+
].to_a
|
21
|
+
s.test_files = Dir.glob('spec/*_spec.rb')
|
22
|
+
s.has_rdoc = true
|
23
|
+
s.extra_rdoc_files = [ "README.md", "CHANGES" ]
|
24
|
+
s.rdoc_options = ["--main", "README.md", "--inline-source", "--line-numbers"]
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
28
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module NamedRoutes
|
2
|
+
class Routes
|
3
|
+
class_inheritable_accessor :host, :prefix
|
4
|
+
|
5
|
+
extend(Module.new do
|
6
|
+
def instance
|
7
|
+
@instance ||= new
|
8
|
+
end
|
9
|
+
|
10
|
+
def http
|
11
|
+
Uri.new(self, "http")
|
12
|
+
end
|
13
|
+
|
14
|
+
def https
|
15
|
+
Uri.new(self, "https")
|
16
|
+
end
|
17
|
+
|
18
|
+
def path(name, definition, include_prefix=true)
|
19
|
+
full_definition = (include_prefix && prefix) ? File.join("", prefix, definition) : definition
|
20
|
+
define_method name do |*args|
|
21
|
+
self.class.eval(full_definition, [args.first].compact.first || {})
|
22
|
+
end
|
23
|
+
yield full_definition if block_given?
|
24
|
+
full_definition
|
25
|
+
end
|
26
|
+
|
27
|
+
def eval(definition, params_arg={})
|
28
|
+
params = ActiveSupport::HashWithIndifferentAccess.new(params_arg)
|
29
|
+
path_string = if params.empty?
|
30
|
+
definition
|
31
|
+
else
|
32
|
+
definition.split("/").map do |segment|
|
33
|
+
segment_value = segment[/^:(.*)/, 1]
|
34
|
+
segment_value_parts = segment_value.to_s.split(".")
|
35
|
+
segment_name = segment_value_parts[0]
|
36
|
+
if segment_name
|
37
|
+
param_name = params.delete(File.basename(segment_name, '.*').to_s)
|
38
|
+
URI.escape([param_name, *segment_value_parts[1..-1]].join("."))
|
39
|
+
else
|
40
|
+
segment
|
41
|
+
end
|
42
|
+
end.join("/")
|
43
|
+
end
|
44
|
+
unless params.empty?
|
45
|
+
path_string << "?#{params.to_param}"
|
46
|
+
end
|
47
|
+
path_string
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO: Create eval_without_prefix
|
51
|
+
|
52
|
+
def escape_params(params={})
|
53
|
+
params.inject({}) do |memo, kv|
|
54
|
+
memo[URI.escape(kv[0])] = if kv[1].is_a?(Hash)
|
55
|
+
escape_params(kv[1])
|
56
|
+
elsif kv[1].is_a?(Array)
|
57
|
+
kv[1].map { |v| URI.escape(v.to_s) }
|
58
|
+
else
|
59
|
+
URI.escape(kv[1].to_s)
|
60
|
+
end
|
61
|
+
memo
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def normalize(path)
|
66
|
+
path.gsub(Regexp.new("^#{File.join("", prefix.to_s)}"), "/").gsub("//", "/")
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(method_name, *args, &block)
|
70
|
+
if instance.respond_to?(method_name)
|
71
|
+
instance.send(method_name, *args, &block)
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end)
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module NamedRoutes
|
2
|
+
class Uri
|
3
|
+
attr_reader :routes, :scheme
|
4
|
+
|
5
|
+
def initialize(routes, scheme)
|
6
|
+
@routes, @scheme = routes, scheme
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method_name, *args, &block)
|
10
|
+
"#{scheme}://#{routes.host}#{routes.send(method_name, *args, &block)}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/named-routes.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_support/hash_with_indifferent_access"
|
3
|
+
require "active_support/core_ext"
|
4
|
+
require "active_support/core_ext"
|
5
|
+
require "uri"
|
6
|
+
|
7
|
+
module NamedRoutes
|
8
|
+
def path(*args, &block)
|
9
|
+
routes.path(*args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def routes
|
13
|
+
::NamedRoutes::Routes
|
14
|
+
end
|
15
|
+
extend self
|
16
|
+
end
|
17
|
+
|
18
|
+
dir = File.dirname(__FILE__)
|
19
|
+
require "#{dir}/named-routes/routes"
|
20
|
+
require "#{dir}/named-routes/uri"
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require File.expand_path("#{File.dirname(__FILE__)}/../spec_helper")
|
2
|
+
|
3
|
+
module NamedRoutes
|
4
|
+
describe Routes do
|
5
|
+
before do
|
6
|
+
NamedRoutes.routes.host = "example.com"
|
7
|
+
end
|
8
|
+
|
9
|
+
def routes
|
10
|
+
@routes ||= begin
|
11
|
+
paths_class = Class.new(NamedRoutes::Routes)
|
12
|
+
paths_class.path(:root, "/")
|
13
|
+
paths_class.path(:current_user_category_top_choices, "/current-user/:category/top-choices")
|
14
|
+
paths_class.path(:decision_stream, "/decision-streams/:stream_id")
|
15
|
+
paths_class
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "path definition" do
|
20
|
+
context "when params hash is not given" do
|
21
|
+
it "returns the definition", :focus => true do
|
22
|
+
routes.root.should == "/"
|
23
|
+
routes.current_user_category_top_choices.should == "/current-user/:category/top-choices"
|
24
|
+
routes.decision_stream.should == "/decision-streams/:stream_id"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when params hash is given" do
|
29
|
+
it "returns the path with the param replaced with the given param value with additional params added as url params" do
|
30
|
+
uri_1 = routes.current_user_category_top_choices(:category => "cars", :foo => "bar", :baz => {"one" => "two three"})
|
31
|
+
|
32
|
+
path, query = uri_1.split("?")
|
33
|
+
path.should == "/current-user/cars/top-choices"
|
34
|
+
query.should include("foo=bar")
|
35
|
+
query.should include("baz[one]=two+three")
|
36
|
+
routes.decision_stream(:stream_id => 99).should == "/decision-streams/99"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when a prefix is given" do
|
41
|
+
def routes
|
42
|
+
@routes ||= begin
|
43
|
+
paths_class = Class.new(NamedRoutes::Routes)
|
44
|
+
paths_class.prefix = "general"
|
45
|
+
paths_class.path(:root, "/")
|
46
|
+
paths_class.path(:current_user_category_top_choices, "/current-user/:category/top-choices")
|
47
|
+
paths_class.path(:decision_stream, "/decision-streams/:stream_id")
|
48
|
+
paths_class
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "when default and include_prefix argument is true" do
|
53
|
+
it "appends the prefix to the returned paths" do
|
54
|
+
routes.root.should == "/general/"
|
55
|
+
routes.current_user_category_top_choices.should == "/general/current-user/:category/top-choices"
|
56
|
+
routes.decision_stream.should == "/general/decision-streams/:stream_id"
|
57
|
+
routes.current_user_category_top_choices(:category => "cars").should == "/general/current-user/cars/top-choices"
|
58
|
+
routes.decision_stream(:stream_id => 99).should == "/general/decision-streams/99"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when include_prefix argument is false in the path definition" do
|
63
|
+
it "does not append the prefix to the returned paths" do
|
64
|
+
routes.path(:raw_path, "/raw/path", false).should == "/raw/path"
|
65
|
+
routes.raw_path.should == "/raw/path"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ".eval" do
|
72
|
+
context "when params hash is not given" do
|
73
|
+
it "returns the definition" do
|
74
|
+
routes.eval("/current-user/:category/top-choices").should == "/current-user/:category/top-choices"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when params hash is given" do
|
79
|
+
it "returns the path with the param replaced with the given param value with additional params added as url params" do
|
80
|
+
uri_1 = routes.current_user_category_top_choices(:category => "cars", :foo => "bar", :baz => {"one" => "two three"})
|
81
|
+
|
82
|
+
path, query = uri_1.split("?")
|
83
|
+
path.should == "/current-user/cars/top-choices"
|
84
|
+
query.should include("foo=bar")
|
85
|
+
query.should include("baz[one]=two+three")
|
86
|
+
routes.eval("/decision-streams/:stream_id", :stream_id => 99).should == "/decision-streams/99"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#normalize" do
|
92
|
+
def routes
|
93
|
+
@routes ||= begin
|
94
|
+
path_class = Class.new(NamedRoutes::Routes)
|
95
|
+
path_class
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when there is no prefix" do
|
100
|
+
before do
|
101
|
+
routes.prefix.should_not be_present
|
102
|
+
end
|
103
|
+
|
104
|
+
it "returns the given path" do
|
105
|
+
routes.normalize("/prefix/foo/bar").should == "/prefix/foo/bar"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "when there is a prefix" do
|
110
|
+
context "when the prefix begins with a /" do
|
111
|
+
before do
|
112
|
+
routes.prefix = "/prefix"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "strips out the prefix from the beginning" do
|
116
|
+
routes.normalize("/prefix/foo/bar").should == "/foo/bar"
|
117
|
+
routes.normalize("/prefix/foo/prefix/bar").should == "/foo/prefix/bar"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "when the prefix does not begin with a /" do
|
122
|
+
before do
|
123
|
+
routes.prefix = "prefix"
|
124
|
+
end
|
125
|
+
|
126
|
+
it "strips out the prefix from the beginning" do
|
127
|
+
routes.normalize("/prefix/foo/bar").should == "/foo/bar"
|
128
|
+
routes.normalize("/prefix/foo/prefix/bar").should == "/foo/prefix/bar"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe ".http" do
|
135
|
+
it "returns a full http uri (with ::NamedRoutes.host) for the given named route" do
|
136
|
+
routes.http.decision_stream(:stream_id => "11").should == "http://example.com/decision-streams/11"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe ".https" do
|
141
|
+
it "returns a full https uri (with ::NamedRoutes.host) for the given named route" do
|
142
|
+
routes.https.decision_stream(:stream_id => "11").should == "https://example.com/decision-streams/11"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path("#{File.dirname(__FILE__)}/../lib"))
|
4
|
+
require "named-routes"
|
5
|
+
require "rspec"
|
6
|
+
|
7
|
+
ARGV.push("-b")
|
8
|
+
unless ARGV.include?("--format") || ARGV.include?("-f")
|
9
|
+
ARGV.push("--format", "nested")
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rr'
|
13
|
+
|
14
|
+
RSpec.configure do |c|
|
15
|
+
c.mock_with :rr
|
16
|
+
# c.filter_run :focus => true
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: named-routes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Brian Takita
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-20 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: brian.takita@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- CHANGES
|
30
|
+
- README.md
|
31
|
+
files:
|
32
|
+
- CHANGES
|
33
|
+
- MIT.LICENSE
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- lib/named-routes.rb
|
38
|
+
- lib/named-routes/routes.rb
|
39
|
+
- lib/named-routes/uri.rb
|
40
|
+
- spec/named-routes/routes_spec.rb
|
41
|
+
- spec/spec_helper.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/btakita/named-routes
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options:
|
48
|
+
- --main
|
49
|
+
- README.md
|
50
|
+
- --inline-source
|
51
|
+
- --line-numbers
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
hash: 3
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.7
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A simple and generic named routes api. It works really well with Sinatra.
|
79
|
+
test_files: []
|
80
|
+
|