described_routes 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/Manifest.txt +4 -0
- data/lib/described_routes/rails_routes.rb +153 -0
- data/lib/described_routes/resource_template.rb +66 -0
- data/lib/described_routes.rb +1 -1
- metadata +5 -1
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
path-to, Copyright (c) 2009 Mike Burrows
|
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/Manifest.txt
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
History.txt
|
2
|
+
LICENSE
|
2
3
|
Manifest.txt
|
3
4
|
PostInstall.txt
|
4
5
|
README.rdoc
|
5
6
|
Rakefile
|
6
7
|
lib/described_routes.rb
|
8
|
+
lib/described_routes/rails_routes.rb
|
9
|
+
lib/described_routes/resource_template.rb
|
7
10
|
lib/tasks/described_routes.rb
|
8
11
|
script/console
|
9
12
|
script/destroy
|
10
13
|
script/generate
|
11
14
|
test/test_described_routes.rb
|
12
15
|
test/test_helper.rb
|
16
|
+
test/test_resource_template.rb
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'described_routes'
|
2
|
+
|
3
|
+
module DescribedRoutes
|
4
|
+
module RailsRoutes
|
5
|
+
#
|
6
|
+
# Based on the implementation of "rake routes". Returns a hash of Rails path specifications (slightly normalized)
|
7
|
+
# mapped to hashes of the attributes we need.
|
8
|
+
#
|
9
|
+
def self.get_rails_resources
|
10
|
+
ActionController::Routing::Routes.routes.inject({}) do |resources, route|
|
11
|
+
name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s
|
12
|
+
controller = route.parameter_shell[:controller]
|
13
|
+
action = route.parameter_shell[:action]
|
14
|
+
options = [route.conditions[:method]].flatten.map{|option| option.to_s.upcase}
|
15
|
+
segs = route.segments.inject("") {|str,s| str << s.to_s }
|
16
|
+
segs.chop! if segs.length > 1
|
17
|
+
|
18
|
+
# prefix :id parameters consistently
|
19
|
+
# TODO - probably a better way to do this, just need a pattern that matches :id and not :id[a-zA-Z0-9_]+
|
20
|
+
segs.gsub!(/:[a-zA-Z0-9_]+/) do |match|
|
21
|
+
if match == ":id" && controller
|
22
|
+
":#{controller.singularize.sub(/.*\//, "")}_id"
|
23
|
+
else
|
24
|
+
match
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# ignore optional format parameter when comparing paths
|
29
|
+
key = segs.sub("(.:format)", "")
|
30
|
+
if resources[key]
|
31
|
+
# we've seen the (normalised) path before; add to options
|
32
|
+
resources[key]["options"] += options
|
33
|
+
else
|
34
|
+
template = segs
|
35
|
+
|
36
|
+
# collect & format mandatory parameters
|
37
|
+
params = []
|
38
|
+
template.gsub!(/:[a-zA-Z0-9_]+/) do |match|
|
39
|
+
param = match[1..-1]
|
40
|
+
param = controller.singularize.sub(/.*\//, "") + "_id" if param == "id" && controller
|
41
|
+
params << param
|
42
|
+
"{#{param}}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# collect & format optional format parameter
|
46
|
+
optional_params = []
|
47
|
+
template.sub!("(.{format})") do |match|
|
48
|
+
optional_params << "format"
|
49
|
+
"{-prefix|.|format}"
|
50
|
+
end
|
51
|
+
params -= optional_params
|
52
|
+
|
53
|
+
# so now we have (for example):
|
54
|
+
# segs #=> "/users/:user_id/edit(.:format)" (was "/users/:id")
|
55
|
+
# key #=> "/users/:user_id/edit"
|
56
|
+
# template #=> "/users/{user_id}/edit"
|
57
|
+
# params #=> ["user_id"]
|
58
|
+
# optional_params #=> ["format"]
|
59
|
+
# action #=> "edit"
|
60
|
+
# options #=> ["GET"]
|
61
|
+
# name #=> "edit_user"
|
62
|
+
|
63
|
+
# create a new route hash
|
64
|
+
resource = {
|
65
|
+
"path_template" => template,
|
66
|
+
"options" => options,
|
67
|
+
}
|
68
|
+
resource["params"] = params unless params.empty?
|
69
|
+
resource["optional_params"] = optional_params unless optional_params.empty?
|
70
|
+
|
71
|
+
resources[key] = resource
|
72
|
+
end
|
73
|
+
|
74
|
+
# this may be the first time we've seen a good name for this key
|
75
|
+
resources[key]["name"] ||= name unless name.blank? or name =~ /^formatted/
|
76
|
+
|
77
|
+
resources
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Takes the routes from Rails and produces the required tree structure.
|
83
|
+
#
|
84
|
+
def self.get_resources
|
85
|
+
resources = get_rails_resources
|
86
|
+
resources.delete_if{|k, v| v["name"].blank? or v["name"] =~ /^formatted/}
|
87
|
+
|
88
|
+
key_tree = make_key_tree(resources.keys.sort){|possible_prefix, key|
|
89
|
+
key[0...possible_prefix.length] == possible_prefix && possible_prefix != "/"
|
90
|
+
}
|
91
|
+
|
92
|
+
tree = map_key_tree(key_tree) do |key, children|
|
93
|
+
resource = resources[key]
|
94
|
+
resource["resource_templates"] = children unless children.empty?
|
95
|
+
resource.delete("options") if resource["options"] == [""]
|
96
|
+
|
97
|
+
# compare parent and child names, and populate "rel" with either
|
98
|
+
# 1) a prefix (probably an action name)
|
99
|
+
# 2) a suffix (probably a nested resource)
|
100
|
+
# If neither applies, let's hope the child is identified by parameter (i.e. the parent is a collection)
|
101
|
+
# TODO rewrite this so that it's done when the child is created
|
102
|
+
name = resource["name"]
|
103
|
+
prefix = /^(.*)_#{name}$/
|
104
|
+
suffix = /^#{name}_(.*)$/
|
105
|
+
children.each do |child|
|
106
|
+
child_name = child["name"]
|
107
|
+
if child_name =~ prefix
|
108
|
+
child["rel"] = $1
|
109
|
+
elsif child_name =~ suffix
|
110
|
+
child["rel"] = $1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
resource
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Depth-first tree traversal
|
120
|
+
#
|
121
|
+
# tree = [["/", []], ["/a", [["/a/b", [["/a/b/c", []]]], ["/a/d", []]]], ["/b", []]]
|
122
|
+
# map_key_tree(tree){|key, processed_children| {key => processed_children}}
|
123
|
+
# # => [{"/"=>[]}, {"/a"=>[{"/a/b"=>[{"/a/b/c"=>[]}]}, {"/a/d"=>[]}]}, {"/b"=>[]}]
|
124
|
+
#
|
125
|
+
def self.map_key_tree(tree, &blk) #:nodoc:
|
126
|
+
tree.map do |pair|
|
127
|
+
key, children = pair
|
128
|
+
blk.call(key, map_key_tree(children, &blk))
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Turns a sorted array of strings into a tree structure as follows:
|
134
|
+
#
|
135
|
+
# make_key_tree(["/", "/a", "/a/b", "/a/b/c", "/a/d", "/b"]){|possible_prefix, route|
|
136
|
+
# route[0...possible_prefix.length] == possible_prefix && possible_prefix != "/"
|
137
|
+
# }
|
138
|
+
# => [["/", []], ["/a", [["/a/b", [["/a/b/c", []]]], ["/a/d", []]]], ["/b", []]]
|
139
|
+
#
|
140
|
+
# Note that in the example (as in is actual usage in this module), we choose not to to have the root resource ("/") as
|
141
|
+
# the parent of all other resources.
|
142
|
+
#
|
143
|
+
def self.make_key_tree(sorted_keys, &is_prefix) #:nodoc:
|
144
|
+
head, *tail = sorted_keys
|
145
|
+
if head
|
146
|
+
children, siblings = tail.partition{|p| is_prefix.call(head, p)}
|
147
|
+
[[head, make_key_tree(children, &is_prefix)]] + make_key_tree(siblings, &is_prefix)
|
148
|
+
else
|
149
|
+
[]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module DescribedRoutes
|
4
|
+
class ResourceTemplate
|
5
|
+
# The template's name. Optional. Making these unique across the application is helpful for clients
|
6
|
+
# that may wish to pick out nested templates by name.
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# Optional attribute that describes a resource's relationship to its parent. For example:
|
10
|
+
# * a nested route to a resource's edit page would have rel of "edit"
|
11
|
+
# * a nested collection of articles under a "user" resource would have have a rel of "articles"
|
12
|
+
# Collection members generally don't need a rel as they are identified by their params
|
13
|
+
attr_reader :rel
|
14
|
+
|
15
|
+
# A template for generating paths relative to the application's base.
|
16
|
+
attr_reader :path_template
|
17
|
+
|
18
|
+
# The parameters required by the path template
|
19
|
+
attr_reader :params
|
20
|
+
|
21
|
+
# Optional paramaters that may be used by the path template
|
22
|
+
attr_reader :optional_params
|
23
|
+
|
24
|
+
# "options" in the sense of the HTTP option request - i.e. a list of HTTP methods. Optional.
|
25
|
+
attr_reader :options
|
26
|
+
|
27
|
+
# An optional list of nested resource templates
|
28
|
+
attr_reader :resource_templates
|
29
|
+
|
30
|
+
# Initialize a ResourceTemplate. See the attribute descriptions above for explanations of the parameters.
|
31
|
+
def initialize(name, rel, path_template, params, optional_params, options, resource_templates)
|
32
|
+
@name, @rel, @path_template = name, rel, path_template
|
33
|
+
@params = params || []
|
34
|
+
@optional_params = optional_params || []
|
35
|
+
@options = options || []
|
36
|
+
@resource_templates = resource_templates || []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create a ResourceTemplate from its Hash representation
|
40
|
+
def self.from_hash(hash)
|
41
|
+
attributes = %w(name rel path_template params optional_params options).map{|k| hash[k]}
|
42
|
+
if hash["resource_templates"]
|
43
|
+
attributes << hash["resource_templates"].map{|h| from_hash(h)} if hash["resource_templates"]
|
44
|
+
else
|
45
|
+
attributes << nil
|
46
|
+
end
|
47
|
+
self.new(*attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Convert to a hash (equivalent to its JSON or YAML representation)
|
51
|
+
def to_hash
|
52
|
+
hash = {}
|
53
|
+
hash["name"] = name if name && !name.empty?
|
54
|
+
hash["rel"] = rel if rel && !rel.empty?
|
55
|
+
hash["path_template"] = path_template if path_template && !path_template.empty?
|
56
|
+
|
57
|
+
hash["options"] = options if options && !options.empty?
|
58
|
+
|
59
|
+
hashes = DescribedRoutes.to_parsed(resource_templates)
|
60
|
+
hash["resource_templates"] = hashes if hashes && !hashes.empty?
|
61
|
+
|
62
|
+
hash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
data/lib/described_routes.rb
CHANGED
@@ -2,7 +2,7 @@ require 'described_routes/resource_template'
|
|
2
2
|
|
3
3
|
module DescribedRoutes
|
4
4
|
# rubygem version
|
5
|
-
VERSION = "0.0.
|
5
|
+
VERSION = "0.0.3"
|
6
6
|
|
7
7
|
# Convert an array of ResourceTemplate objects to array of hashes equivalent to their JSON or YAML representations
|
8
8
|
def self.to_parsed(resource_templates)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: described_routes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Burrows
|
@@ -46,17 +46,21 @@ extra_rdoc_files:
|
|
46
46
|
- README.rdoc
|
47
47
|
files:
|
48
48
|
- History.txt
|
49
|
+
- LICENSE
|
49
50
|
- Manifest.txt
|
50
51
|
- PostInstall.txt
|
51
52
|
- README.rdoc
|
52
53
|
- Rakefile
|
53
54
|
- lib/described_routes.rb
|
55
|
+
- lib/described_routes/rails_routes.rb
|
56
|
+
- lib/described_routes/resource_template.rb
|
54
57
|
- lib/tasks/described_routes.rb
|
55
58
|
- script/console
|
56
59
|
- script/destroy
|
57
60
|
- script/generate
|
58
61
|
- test/test_described_routes.rb
|
59
62
|
- test/test_helper.rb
|
63
|
+
- test/test_resource_template.rb
|
60
64
|
has_rdoc: true
|
61
65
|
homepage:
|
62
66
|
licenses: []
|