path-to 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
+ == 0.4.0
2
+
3
+ * Positional parameters for path-to/described_routes (only), e.g. delicious.posts('ruby'); app.users['dojo']
4
+
1
5
  == 0.3.1 2009-05-08
2
6
 
3
7
  * Add gem dependency on described_routes
data/README.rdoc CHANGED
@@ -18,10 +18,17 @@ Create a client application configured from a server that supports described_rou
18
18
 
19
19
  app = PathTo::DescribedRoutes::Application.new(:json => Net::HTTP.get(URI.parse("http://example.com/described_routes.json")))
20
20
 
21
- app.users["user_id" => "dojo"].articles.recent #=> http://example.com/users/dojo/articles/recent
22
- app.users["user_id" => "dojo"].articles.recent.get #=> "<html>...</html>"
21
+ app.users["dojo"].articles.recent
22
+ #=> http://example.com/users/dojo/articles/recent
23
+ app.users["dojo"].articles.recent.get
24
+ #=> "<html>...</html>"
25
+
26
+ app.users["dojo"].articles.recent["format" => "json"]
27
+ #=> http://example.com/users/dojo/articles/recent.json
28
+ app.users["dojo"].articles.recent.get
29
+ #=> [...]
23
30
 
24
- See examples/delicious.rb for an example based on a partial YAML-based description of the Delicious API held locally.
31
+ See examples/delicious.rb for an example based on a partial YAML-based description of the Delicious API.
25
32
 
26
33
  === Local configuration
27
34
 
data/Rakefile CHANGED
@@ -13,7 +13,7 @@ $hoe = Hoe.new('path-to', PathTo::VERSION) do |p|
13
13
  p.extra_deps = [
14
14
  ['httparty','>= 0.4.2'],
15
15
  ['addressable','>= 2.0.2'],
16
- ['described_routes','>= 0.3.5']
16
+ ['described_routes','>= 0.3.6']
17
17
  ]
18
18
  p.extra_dev_deps = [
19
19
  ['newgem', ">= #{::Newgem::VERSION}"]
@@ -1,5 +1,4 @@
1
- # Adapted from jnunemaker/httparty/examples/delicious.rb to demonstrate path-to's metadata-driven REST client API capability
2
- # For more information see http://positiveincline.com/?tag=path-to
1
+ # Adapted from jnunemaker/httparty/examples/delicious.rb to demonstrate path-to's metadata-driven client API capability
3
2
 
4
3
  require 'path-to/described_routes'
5
4
  require 'pp'
@@ -38,6 +37,6 @@ delicious = PathTo::DescribedRoutes::Application.new(
38
37
  :username => config['username'],
39
38
  :password => config['password']}})
40
39
 
41
- pp delicious.posts['tag' => 'ruby'].get
42
- pp delicious.posts['tag' => 'ruby'].recent['count' => '5'].get
40
+ pp delicious.posts('ruby').get
41
+ pp delicious.posts('ruby').recent('count' => '5').get
43
42
  delicious.recent_posts.get['posts']['post'].each { |post| puts post['href'] }
data/lib/path-to.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module PathTo
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
4
4
 
5
5
  $:.push File.dirname(__FILE__)
@@ -2,10 +2,26 @@ require "path-to"
2
2
  require "described_routes/resource_template"
3
3
 
4
4
  module PathTo
5
+ #
6
+ # Application and Path implementations for DescribedRoutes, each resource described by a ResourceTemplate
7
+ #
5
8
  module DescribedRoutes
9
+ #
10
+ # Implements PathTo::Path, represents a resource described by a ResourceTemplate
11
+ #
6
12
  class TemplatedPath < PathTo::Path
7
13
  attr_reader :resource_template
8
14
 
15
+ #
16
+ # Initialize a TemplatedPath. Raises ArgumentError if params doesn't include all mandatory params expected by the resource
17
+ # template.
18
+ #
19
+ # Parameters:
20
+ # [parent] parent object path or application
21
+ # [service] unused - resource_template.name is passed to super() instead. TODO: refactor
22
+ # [params] hash of params; will be merged with the parent's params and passed when required to the resource template's URI template
23
+ # [resource_template] metadata describing the web resource
24
+ #
9
25
  def initialize(parent, service, params, resource_template)
10
26
  super(parent, resource_template.name, params)
11
27
  @resource_template = resource_template
@@ -20,10 +36,16 @@ module PathTo
20
36
  end
21
37
  end
22
38
 
39
+ #
40
+ # Get and cache the uri template from the resource tamplte
41
+ #
23
42
  def uri_template
24
43
  @uri_template ||= resource_template.uri_template || (application.base + resource_template.path_template)
25
44
  end
26
45
 
46
+ #
47
+ # Create and cache the URI by filling in the URI template with params
48
+ #
27
49
  def uri
28
50
  @uri ||= begin
29
51
  Addressable::Template.new(uri_template).expand(params).to_s
@@ -43,15 +65,38 @@ module PathTo
43
65
  end
44
66
 
45
67
  #
46
- # Creates a child instance with new params, potentially finding a nested resource template that takes the additional params
68
+ # Creates a child instance with new params, potentially finding a nested resource template that takes the additional params.
69
+ # May take a combination of positional and named parameters, e.g.
70
+ #
71
+ # users["dojo", {"format" => "json"}]
47
72
  #
48
- def [](params = {})
49
- keys = self.params.merge(params).keys
50
- child_resource_template = resource_template.resource_templates.detect{ |t|
51
- t.rel.nil? && (t.params - keys).empty?
52
- } || resource_template
53
- child_class = child_class_for(self, nil, params, child_resource_template)
54
- child(child_class, nil, params, child_resource_template)
73
+ # Positional parameters are unsupported however if a new child template is not identified.
74
+ #
75
+ def [](*args)
76
+ positional_params, params_hash = extract_params(args, params)
77
+ known_keys = params_hash.keys
78
+
79
+ child_resource_template = resource_template.resource_templates.detect do |t|
80
+ if t.rel.nil?
81
+ (t.positional_params(resource_template)[positional_params.length..-1] - t.optional_params - known_keys).empty?
82
+ end
83
+ end
84
+
85
+ if child_resource_template
86
+ # we have a new child resource template; apply any positional params to the hash
87
+ complete_params_hash!(params_hash, child_resource_template.positional_params(resource_template), positional_params)
88
+ else
89
+ # we're just adding optional params, no new template identified
90
+ unless positional_params.empty?
91
+ raise ArgumentError.new(
92
+ "No matching child template; only named parameters can be used here. " +
93
+ "positional_params=#{positional_params.inspect}, params_hash=#{params_hash.inspect}")
94
+ end
95
+ child_resource_template = resource_template
96
+ end
97
+
98
+ child_class = child_class_for(self, nil, params_hash, child_resource_template)
99
+ child(child_class, nil, params_hash, child_resource_template)
55
100
  end
56
101
 
57
102
  #
@@ -62,11 +107,16 @@ module PathTo
62
107
  #
63
108
  # Otherwise we invoke super in the hope of avoiding any hard-to-debug behaviour!
64
109
  #
110
+ # May take a combination of positional and named parameters, e.g.
111
+ #
112
+ # users("dojo", "format" => "json")
113
+ #
65
114
  def method_missing(method, *args)
66
115
  child_resource_template = resource_template.resource_templates.detect{|t| t.rel == method.to_s}
67
116
  if child_resource_template && (child_class = child_class_for(self, method, params, child_resource_template))
68
- params = args.inject(Hash.new){|h, arg| h.merge(arg)}
69
- child(child_class, method, params, child_resource_template)
117
+ positional_params, params_hash = extract_params(args, params)
118
+ complete_params_hash!(params_hash, child_resource_template.positional_params(resource_template), positional_params)
119
+ child(child_class, method, params_hash, child_resource_template)
70
120
  else
71
121
  super
72
122
  end
@@ -74,6 +124,9 @@ module PathTo
74
124
 
75
125
  end
76
126
 
127
+ #
128
+ # DescribedRoutes implementation of PathTo::Application.
129
+ #
77
130
  class Application < WithParams
78
131
  # An Array of DescribedRoutes::Resource objects
79
132
  attr_reader :resource_templates
@@ -122,7 +175,7 @@ module PathTo
122
175
  #
123
176
  # Creates a copy of self with additional params
124
177
  #
125
- def [](params = {})
178
+ def [](params)
126
179
  self.class.new(:parent => self, :params => params)
127
180
  end
128
181
 
@@ -135,10 +188,11 @@ module PathTo
135
188
  # Otherwise we invoke super in the hope of avoiding any hard-to-debug behaviour!
136
189
  #
137
190
  def method_missing(method, *args)
138
- resource_template = resource_templates_by_name[method.to_s]
139
- if resource_template && (child_class = child_class_for(self, method, params, resource_template))
140
- params = args.inject(Hash.new){|h, arg| h.merge(arg)}
141
- child(child_class, method, params, resource_template)
191
+ child_resource_template = resource_templates_by_name[method.to_s]
192
+ if child_resource_template && (child_class = child_class_for(self, method, params, child_resource_template))
193
+ positional_params, params_hash = extract_params(args, params)
194
+ complete_params_hash!(params_hash, child_resource_template.positional_params(nil), positional_params)
195
+ child(child_class, method, params_hash, child_resource_template)
142
196
  else
143
197
  super
144
198
  end
@@ -87,5 +87,32 @@ module PathTo
87
87
  super
88
88
  end
89
89
  end
90
+
91
+ #
92
+ # Separates positional params from hash params
93
+ # TODO: this is initially just for the DescribedRoutes implementation but there will be some refactoring to do
94
+ #
95
+ def extract_params(args, params_hash={})#:nodoc:
96
+ positional_params = []
97
+ params_hash = params_hash.clone
98
+ args.each do |arg|
99
+ if arg.kind_of?(Hash)
100
+ params_hash.merge!(arg)
101
+ else
102
+ positional_params << arg
103
+ end
104
+ end
105
+ [positional_params, params_hash]
106
+ end
107
+
108
+ #
109
+ # Updates params_hash with positional parameters
110
+ # TODO: this is initially just for the DescribedRoutes implementation but there will be some refactoring to do
111
+ #
112
+ def complete_params_hash!(params_hash, names, values)#:nodoc:
113
+ names[0...values.length].each_with_index do |k, i|
114
+ params_hash[k] = values[i]
115
+ end
116
+ end
90
117
  end
91
118
  end
@@ -102,13 +102,22 @@ class TestDescribedRoutes < Test::Unit::TestCase
102
102
  end
103
103
 
104
104
  def test_path_optional_params
105
- users_json = app.users["format" => "json"]
106
- user_json = app.users["user_id" => "dojo"]["format" => "json"]
105
+ # more complicated than would be ideal, but the app has a different #method_missing d
106
+ user_articles = app.users["user_id" => "dojo"].articles("json")
107
107
 
108
- assert_equal("users", users_json.service)
109
- assert_equal("user", user_json.service)
110
- assert_equal("http://localhost:3000/users.json", users_json.uri)
111
- assert_equal("http://localhost:3000/users/dojo.json", user_json.uri)
108
+ assert_equal("user_articles", user_articles.service)
109
+ assert_equal({"user_id" => "dojo", "format" => "json"}, user_articles.params)
110
+ end
111
+
112
+ def test_path_collection_positional_params
113
+ article_json = app.users["dojo"].articles["article-1"]["format" => "json"]
114
+ assert_equal("user_article", article_json.service)
115
+ assert_equal({"user_id" => "dojo", "article_id" => "article-1", "format" => "json"}, article_json.params)
116
+ assert_equal("http://localhost:3000/users/dojo/articles/article-1.json", article_json.uri)
117
+
118
+ assert_raises(ArgumentError) do
119
+ article_json = app.users["dojo"]["json"]
120
+ end
112
121
  end
113
122
 
114
123
  def test_app_params
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: path-to
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Burrows (asplake)
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-08 00:00:00 +01:00
12
+ date: 2009-05-12 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: 0.3.5
43
+ version: 0.3.6
44
44
  version:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: newgem