described_routes 0.0.2 → 0.0.3
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/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: []
|