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 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