described_routes 0.5.0 → 0.5.1
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/History.txt +5 -0
- data/README.rdoc +1 -3
- data/lib/described_routes.rb +1 -1
- data/lib/resource_template.rb +74 -39
- data/test/test_resource_template.rb +42 -1
- metadata +2 -2
data/History.txt
CHANGED
data/README.rdoc
CHANGED
@@ -24,7 +24,7 @@ Then:
|
|
24
24
|
rake described_routes:yaml # Describe resource structure in YAML format
|
25
25
|
rake described_routes:text # Describe resource structure in text (comparable to "rake routes")
|
26
26
|
|
27
|
-
The text output looks like this:
|
27
|
+
The JSON, XML and YAML formats (of which the YAML is the most readable) are designed for program consumption. The more human-friendly text output looks like this:
|
28
28
|
|
29
29
|
$ rake --silent described_routes:text
|
30
30
|
root root /
|
@@ -58,8 +58,6 @@ The text output looks like this:
|
|
58
58
|
You can specify the base URL of your application by appending "BASE=http://..." to the rake command line. The output will then
|
59
59
|
include full URI templates.
|
60
60
|
|
61
|
-
The JSON, XML and YAML formats (of which the YAML is the most readable) are designed for program consumption.
|
62
|
-
|
63
61
|
=== Run time
|
64
62
|
|
65
63
|
Include the controller, perhaps in an initializer:
|
data/lib/described_routes.rb
CHANGED
data/lib/resource_template.rb
CHANGED
@@ -12,9 +12,6 @@ class ResourceTemplate
|
|
12
12
|
# Collection members generally don't need a rel as they are identified by their params
|
13
13
|
attr_reader :rel
|
14
14
|
|
15
|
-
# A template for generating URIs.
|
16
|
-
attr_reader :uri_template
|
17
|
-
|
18
15
|
# A template for generating paths relative to the application's base.
|
19
16
|
attr_reader :path_template
|
20
17
|
|
@@ -30,25 +27,35 @@ class ResourceTemplate
|
|
30
27
|
# Nested resource templates, a Resources object
|
31
28
|
attr_reader :resource_templates
|
32
29
|
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
30
|
+
#
|
31
|
+
# Initialize a ResourceTemplate from a hash. For example:
|
32
|
+
#
|
33
|
+
# user_articles = ResourceTemplate.new(
|
34
|
+
# "name" => "user_articles",
|
35
|
+
# "rel" => "articles",
|
36
|
+
# "uri_template" => "http://example.com/users/{user_id}/articles{-prefix|.|format}",
|
37
|
+
# "path_template" => "/users/{user_id}/articles{-prefix|.|format}",
|
38
|
+
# "params" => ["user_id"],
|
39
|
+
# "optional_params" => ["format"],
|
40
|
+
# "resource_templates" => [user_article, new_user_article])
|
41
|
+
#
|
42
|
+
# <code>resource_templates</code> (if provided) can be a ResourceTemplates object, an array of ResourceTemplate objects
|
43
|
+
# or an array of hashes. This last option makes it easy to initialize a whole hierarchy directly from deserialised JSON or YAML
|
44
|
+
# objects, e.g.:
|
45
|
+
#
|
46
|
+
# user_articles = ResourceTemplate.new(JSON.parse(json))
|
47
|
+
# user_articles = ResourceTemplate.new(YAML.load(yaml))
|
48
|
+
#
|
49
|
+
def initialize(hash={})
|
50
|
+
@name, @rel, @uri_template, @path_template = %w(name rel uri_template path_template).map{|attr| hash[attr]}
|
51
|
+
@params, @optional_params, @options = %w(params optional_params options).map{|attr| hash[attr] || []}
|
52
|
+
@resource_templates = ResourceTemplates.new(hash["resource_templates"])
|
47
53
|
end
|
48
54
|
|
49
55
|
# Convert to a hash (equivalent to its JSON or YAML representation)
|
50
56
|
def to_hash
|
51
57
|
hash = {}
|
58
|
+
|
52
59
|
hash["name"] = name if name && !name.empty?
|
53
60
|
hash["rel"] = rel if rel && !rel.empty?
|
54
61
|
hash["uri_template"] = uri_template if uri_template && !uri_template.empty?
|
@@ -56,7 +63,6 @@ class ResourceTemplate
|
|
56
63
|
|
57
64
|
hash["params"] = params if params && !params.empty?
|
58
65
|
hash["optional_params"] = optional_params if optional_params && !optional_params.empty?
|
59
|
-
|
60
66
|
hash["options"] = options if options && !options.empty?
|
61
67
|
|
62
68
|
hash["resource_templates"] = resource_templates.to_parsed if !resource_templates.empty?
|
@@ -115,7 +121,7 @@ class ResourceTemplate
|
|
115
121
|
end
|
116
122
|
end
|
117
123
|
|
118
|
-
#
|
124
|
+
# Returns params and any optional_params in order, removing the parent's params
|
119
125
|
def positional_params(parent)
|
120
126
|
all_params = params + optional_params
|
121
127
|
if parent
|
@@ -125,17 +131,51 @@ class ResourceTemplate
|
|
125
131
|
end
|
126
132
|
end
|
127
133
|
|
134
|
+
# Returns this template's URI template, or one constructed from the given base and path template.
|
135
|
+
def uri_template(base=nil)
|
136
|
+
if @uri_template
|
137
|
+
@uri_template
|
138
|
+
elsif base && path_template
|
139
|
+
base + path_template
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns an expanded URI template with template variables filled from the given params hash.
|
144
|
+
# Raises ArgumentError if params doesn't contain all mandatory params.
|
145
|
+
def uri_for(params_hash, base=nil)
|
146
|
+
missing_params = params - params_hash.keys
|
147
|
+
unless missing_params.empty?
|
148
|
+
raise ArgumentError.new("missing params #{missing_params.join(', ')}")
|
149
|
+
end
|
150
|
+
|
151
|
+
t = uri_template(base)
|
152
|
+
unless t
|
153
|
+
raise RuntimeError.new("uri_template(#{base.inspect})=nil; path_template=#{path_template.inspect}")
|
154
|
+
end
|
155
|
+
|
156
|
+
Addressable::Template.new(t).expand(params_hash).to_s
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns an expanded path template with template variables filled from the given params hash.
|
160
|
+
# Raises ArgumentError if params doesn't contain all mandatory params, and a RuntimeError if there is no path_template.
|
161
|
+
def path_for(params_hash)
|
162
|
+
missing_params = params - params_hash.keys
|
163
|
+
raise ArgumentError.new("missing params #{missing_params.join(', ')}") unless missing_params.empty?
|
164
|
+
raise RuntimeError.new("#path_for called on resource template #{name.inspect} that has no path_template") if path_template.nil?
|
165
|
+
Addressable::Template.new(path_template).expand(params_hash).to_s
|
166
|
+
end
|
167
|
+
|
128
168
|
# Return a new resource template with the path_template or uri_template partially expanded with the given params
|
129
169
|
def partial_expand(actual_params)
|
130
170
|
self.class.new(
|
131
|
-
name,
|
132
|
-
rel,
|
133
|
-
partial_expand_uri_template(uri_template, actual_params),
|
134
|
-
partial_expand_uri_template(path_template, actual_params),
|
135
|
-
params - actual_params.keys,
|
136
|
-
optional_params - actual_params.keys,
|
137
|
-
options,
|
138
|
-
resource_templates.partial_expand(actual_params))
|
171
|
+
"name" => name,
|
172
|
+
"rel" => rel,
|
173
|
+
"uri_template" => partial_expand_uri_template(uri_template, actual_params),
|
174
|
+
"path_template" => partial_expand_uri_template(path_template, actual_params),
|
175
|
+
"params" => params - actual_params.keys,
|
176
|
+
"optional_params" => optional_params - actual_params.keys,
|
177
|
+
"options" => options,
|
178
|
+
"resource_templates" => resource_templates.partial_expand(actual_params))
|
139
179
|
end
|
140
180
|
|
141
181
|
# Partially expand a URI template
|
@@ -143,6 +183,11 @@ class ResourceTemplate
|
|
143
183
|
template && Addressable::Template.new(template).partial_expand(params).pattern
|
144
184
|
end
|
145
185
|
|
186
|
+
# Find member ResourceTemplate objects with the given rel
|
187
|
+
def find_by_rel(rel)
|
188
|
+
resource_templates.select{|resource_template| resource_template.rel == rel}
|
189
|
+
end
|
190
|
+
|
146
191
|
class ResourceTemplates < Array
|
147
192
|
# Initialize Resources (i.e. a new collection of ResourceTemplate objects) from given collection of ResourceTemplates or hashes
|
148
193
|
def initialize(collection=[])
|
@@ -153,7 +198,7 @@ class ResourceTemplate
|
|
153
198
|
if r.kind_of?(ResourceTemplate)
|
154
199
|
push(r)
|
155
200
|
elsif r.kind_of?(Hash)
|
156
|
-
push(ResourceTemplate.
|
201
|
+
push(ResourceTemplate.new(r))
|
157
202
|
else
|
158
203
|
raise ArgumentError.new("#{r.inspect} is neither a ResourceTemplate nor a Hash")
|
159
204
|
end
|
@@ -161,16 +206,6 @@ class ResourceTemplate
|
|
161
206
|
end
|
162
207
|
end
|
163
208
|
|
164
|
-
# Create Resources from a YAML string
|
165
|
-
def self.parse_yaml(yaml)
|
166
|
-
new(YAML::load(yaml))
|
167
|
-
end
|
168
|
-
|
169
|
-
# Create Resources from a JSON string
|
170
|
-
def self.parse_json(json)
|
171
|
-
new(JSON.parse(json))
|
172
|
-
end
|
173
|
-
|
174
209
|
# Create Resources from an XML string
|
175
210
|
def self.parse_xml
|
176
211
|
raise NotImplementedError.new
|
@@ -202,7 +237,7 @@ class ResourceTemplate
|
|
202
237
|
end
|
203
238
|
xm
|
204
239
|
end
|
205
|
-
|
240
|
+
|
206
241
|
# Get a hash of all named ResourceTemplate objects contained in the supplied collection, keyed by name
|
207
242
|
def all_by_name(h = {})
|
208
243
|
inject(h) do |hash, resource_template|
|
@@ -6,7 +6,7 @@ class TestResourceTemplate < Test::Unit::TestCase
|
|
6
6
|
|
7
7
|
def setup
|
8
8
|
@json ||= File.read(File.dirname(__FILE__) + '/fixtures/described_routes_test.json')
|
9
|
-
@resource_templates = ResourceTemplate::ResourceTemplates.
|
9
|
+
@resource_templates = ResourceTemplate::ResourceTemplates.new(JSON.parse(@json))
|
10
10
|
@resource_templates_by_name = @resource_templates.all_by_name
|
11
11
|
@user_articles = @resource_templates_by_name['user_articles']
|
12
12
|
@user_article = @resource_templates_by_name['user_article']
|
@@ -45,6 +45,11 @@ class TestResourceTemplate < Test::Unit::TestCase
|
|
45
45
|
YAML.load(resource_templates.to_yaml))
|
46
46
|
end
|
47
47
|
|
48
|
+
def test_find_by_rel
|
49
|
+
assert_equal([user_article], user_articles.find_by_rel(nil))
|
50
|
+
assert_equal([edit_user_article], user_article.find_by_rel('edit'))
|
51
|
+
end
|
52
|
+
|
48
53
|
def test_positional_params
|
49
54
|
assert_equal(['user_id', 'article_id', 'format'], user_article.positional_params(nil))
|
50
55
|
assert_equal(['article_id', 'format'], user_article.positional_params(user_articles))
|
@@ -57,5 +62,41 @@ class TestResourceTemplate < Test::Unit::TestCase
|
|
57
62
|
assert_equal(['article_id'], expanded_edit_user_article.params)
|
58
63
|
assert(expanded_edit_user_article.optional_params.empty?)
|
59
64
|
assert_equal('/users/dojo/articles/{article_id}/edit.json', expanded_edit_user_article.path_template)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_uri_for
|
68
|
+
assert_equal('http://localhost:3000/users/dojo/articles', user_articles.uri_for('user_id' => 'dojo'))
|
69
|
+
assert_equal('http://localhost:3000/users/dojo/articles.json', user_articles.uri_for('user_id' => 'dojo', 'format' => 'json'))
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_uri_for_with_missing_params
|
73
|
+
assert_raises(ArgumentError) do
|
74
|
+
user_articles.uri_for('format' => 'json') # no user_id param
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_uri_for_with_no_uri_template
|
79
|
+
users = ResourceTemplate.new('path_template' => '/users')
|
80
|
+
assert_raises(RuntimeError) do
|
81
|
+
users.uri_for({})
|
82
|
+
end
|
83
|
+
assert_equal('http://localhost:3000/users', users.uri_for({}, 'http://localhost:3000'))
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_path_for
|
87
|
+
assert_equal('/users/dojo/articles', user_articles.path_for('user_id' => 'dojo'))
|
88
|
+
assert_equal('/users/dojo/articles.json', user_articles.path_for('user_id' => 'dojo', 'format' => 'json'))
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_path_for_with_missing_params
|
92
|
+
assert_raises(ArgumentError) do
|
93
|
+
user_articles.path_for('format' => 'json') # no user_id param
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_path_for_with_no_path_template
|
98
|
+
assert_raises(RuntimeError) do
|
99
|
+
ResourceTemplate.new.path_for({}) # no path_template
|
100
|
+
end
|
60
101
|
end
|
61
102
|
end
|
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.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Burrows
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-25 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|