described_routes 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,8 @@
1
+ == 0.5.1 2009-05-22
2
+
3
+ * Add ResourceTemplate::ResourceTemplate.find_by_rel
4
+ * More low-level refactoring
5
+
1
6
  == 0.5.0 2009-05-22
2
7
 
3
8
  * API refactor, a possible prelude to new resource_template gem:
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:
@@ -2,5 +2,5 @@ require 'resource_template'
2
2
 
3
3
  module DescribedRoutes
4
4
  # rubygem version
5
- VERSION = "0.5.0"
5
+ VERSION = "0.5.1"
6
6
  end
@@ -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
- # Initialize a ResourceTemplate. See the attribute descriptions above for explanations of the parameters.
34
- def initialize(name, rel, uri_template, path_template, params, optional_params, options, resource_templates)
35
- @name, @rel, @uri_template, @path_template = name, rel, uri_template, path_template
36
- @params = params || []
37
- @optional_params = optional_params || []
38
- @options = options || []
39
- @resource_templates = resource_templates || Resources.new
40
- end
41
-
42
- # Create a ResourceTemplate from its Hash representation
43
- def self.from_hash(hash)
44
- attributes = %w(name rel uri_template path_template params optional_params options).map{|k| hash[k]}
45
- attributes << ResourceTemplates.new(hash["resource_templates"])
46
- self.new(*attributes)
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
- # returns params and any optional_params in order, removing the parent's params
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.from_hash(r))
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.parse_json(json)
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.0
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-22 00:00:00 +01:00
12
+ date: 2009-05-25 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency