weary 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,126 @@
1
+ module Weary
2
+ class Base
3
+ @@resources = {}
4
+
5
+ attr_accessor :defaults
6
+
7
+ # Assign credentials to be used when authenticating to a Resource.
8
+ # Can be a username/password combo, or an OAuth::AccessToken
9
+ def credentials(username,password='')
10
+ if username.is_a?(OAuth::AccessToken)
11
+ @credentials = username
12
+ else
13
+ @credentials = {:username => username, :password => password}
14
+ end
15
+ end
16
+
17
+ # Each Weary::Base object has its own set of Resources
18
+ def resources
19
+ @resources = Marshal.load(Marshal.dump(@@resources)) if !@resources
20
+ @resources
21
+ end
22
+
23
+ # Make changes to a Resource given and rebuild the Request method for this object
24
+ def modify_resource(name)
25
+ yield resources[name] if block_given?
26
+ rebuild_method(resources[name])
27
+ end
28
+
29
+ # Rebuild the Request Method for a given Resource. This only affects the current instance.
30
+ def rebuild_method(resource)
31
+ instance_eval %Q{
32
+ def #{resource.name}(params={})
33
+ resources[:#{resource.name}].build!(params, @defaults, @credentials)
34
+ end
35
+ }
36
+ end
37
+
38
+ class << self
39
+
40
+ # Getter for class-level resources
41
+ def resources
42
+ @@resources
43
+ end
44
+
45
+ # Declare a resource. Use it with a block to setup the resource
46
+ #
47
+ # Methods that are understood are:
48
+ # [<tt>via</tt>] Get, Post, etc. Defaults to a GET request
49
+ # [<tt>with</tt>] An array of parameters that will be passed to the body or query of the request. If you pass a hash, it will define default <tt>values</tt> for params <tt>keys</tt>
50
+ # [<tt>requires</tt>] Array of members of <tt>:with</tt> that are required by the resource.
51
+ # [<tt>authenticates</tt>] Boolean value; does the resource require authentication?
52
+ # [<tt>url</tt>] The url of the resource. You can use the same flags as #construct_url
53
+ # [<tt>follows</tt>] Boolean; Does this follow redirects? Defaults to true
54
+ # [<tt>headers</tt>] Set headers for the HTTP Request
55
+ def get(name,&block)
56
+ build_resource(name, :get, block)
57
+ end
58
+ alias declare get
59
+
60
+ # Declares a Resource to be requested via POST
61
+ def post(name,&block)
62
+ build_resource(name, :post, block)
63
+ end
64
+
65
+ # Declares a Resource to be requested via PUT
66
+ def put(name,&block)
67
+ build_resource(name, :put, block)
68
+ end
69
+
70
+ # Declares a Resource to be requested via DELETE
71
+ def delete(name,&block)
72
+ build_resource(name, :delete, block)
73
+ end
74
+
75
+ # Set custom default Headers for your Request
76
+ def headers(headers)
77
+ @headers = headers
78
+ end
79
+
80
+ # Sets the domain to be used to build default url's
81
+ def domain(dom)
82
+ domain = URI.extract(dom)
83
+ raise ArgumentError, 'The domain must be a URL.' if domain.blank?
84
+ @domain = URI.parse(domain[0]).normalize.to_s
85
+ end
86
+
87
+ # Sets a default format, used to build default Resource url's
88
+ def format(format)
89
+ @format = format
90
+ end
91
+
92
+ # Prepare and store the Resource
93
+ def build_resource(name,verb,block=nil)
94
+ resource = prepare_resource(name,verb)
95
+ block.call(resource) if block
96
+ store_resource(resource)
97
+ build_method(resource)
98
+ resource
99
+ end
100
+
101
+ # Prepare a Resource with set defaults
102
+ def prepare_resource(name,via = :get)
103
+ preparation = Weary::Resource.new(name)
104
+ preparation.via = via
105
+ preparation.headers = @headers unless @headers.blank?
106
+ preparation.url = "#{@domain}#{preparation.name}." + (@format || :json).to_s if @domain
107
+ preparation
108
+ end
109
+
110
+ # Store the resource for future use
111
+ def store_resource(resource)
112
+ @@resources[resource.name.to_sym] = resource
113
+ resource
114
+ end
115
+
116
+ # Build a method to form a Request for the given Resource
117
+ def build_method(resource)
118
+ define_method resource.name.to_sym do |*args|
119
+ args.blank? ? params = {} : params = args[0]
120
+ resource.build!(params, @defaults, @credentials)
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -30,15 +30,11 @@ module Weary
30
30
  def perform
31
31
  req = http.request(request)
32
32
  response = Response.new(req, @http_verb)
33
- unless options[:no_follow]
34
- if response.redirected?
35
- response.follow_redirect
36
- else
37
- response
38
- end
39
- else
40
- response
33
+ if response.redirected?
34
+ return response if options[:no_follow]
35
+ response.follow_redirect
41
36
  end
37
+ response
42
38
  end
43
39
 
44
40
  private
@@ -1,22 +1,21 @@
1
1
  module Weary
2
2
  class Resource
3
- attr_accessor :name, :domain, :with, :requires, :via, :format, :url, :authenticates, :follows, :headers, :oauth, :access_token
3
+ attr_reader :name, :via, :with, :requires, :url
4
+ attr_accessor :headers
4
5
 
5
6
  def initialize(name)
6
7
  self.name = name
7
8
  self.via = :get
8
9
  self.authenticates = false
9
10
  self.follows = true
10
- self.with = []
11
- self.requires = []
12
- self.oauth = false
13
11
  end
14
12
 
13
+ # The name of the Resource. Will be a lowercase string, whitespace replaced with underscores.
15
14
  def name=(resource_name)
16
- resource_name = resource_name.to_s unless resource_name.is_a?(String)
17
- @name = resource_name.downcase.strip.gsub(/\s/,'_')
15
+ @name = resource_name.to_s.downcase.strip.gsub(/\s/,'_')
18
16
  end
19
17
 
18
+ # The HTTP Method used to fetch the Resource
20
19
  def via=(http_verb)
21
20
  verb = HTTPVerb.new(http_verb).normalize
22
21
  @via = if Methods.include?(verb)
@@ -26,109 +25,114 @@ module Weary
26
25
  end
27
26
  end
28
27
 
29
- def format=(type)
30
- type = type.downcase if type.is_a?(String)
31
- @format = case type
32
- when *ContentTypes[:json]
33
- :json
34
- when *ContentTypes[:xml]
35
- :xml
36
- when *ContentTypes[:html]
37
- :html
38
- when *ContentTypes[:yaml]
39
- :yaml
40
- when *ContentTypes[:plain]
41
- :plain
42
- else
43
- raise ArgumentError, "#{type} is not a recognized format."
44
- end
45
- end
46
-
28
+ # Optional params. Should be an array. Merges with requires if that is set.
47
29
  def with=(params)
48
- if params.is_a?(Hash)
49
- @requires.each { |key| params[key] = nil unless params.has_key?(key) }
50
- @with = params
51
- else
52
- if @requires.nil?
53
- @with = params.collect {|x| x.to_sym}
54
- else
55
- @with = params.collect {|x| x.to_sym} | @requires
56
- end
57
- end
30
+ @with = [params].flatten.collect {|x| x.to_sym}
31
+ @with = (requires | @with) if requires
58
32
  end
59
33
 
34
+ # Required params. Should be an array. Merges with `with` or sets `with`.
60
35
  def requires=(params)
61
- if @with.is_a?(Hash)
62
- params.each { |key| @with[key] = nil unless @with.has_key?(key) }
63
- @requires = params.collect {|x| x.to_sym}
64
- else
65
- @with = @with | params.collect {|x| x.to_sym}
66
- @requires = params.collect {|x| x.to_sym}
67
- end
36
+ @requires = [params].flatten.collect {|x| x.to_sym}
37
+ with ? @with = (with | @requires) : (@with = @requires)
68
38
  end
69
39
 
70
- def url=(pattern)
71
- if pattern.index("<domain>")
72
- raise StandardError, "Domain flag found but the domain is not defined" if @domain.nil?
73
- pattern = pattern.gsub("<domain>", @domain)
74
- end
75
- pattern = pattern.gsub("<resource>", @name)
76
- pattern = pattern.gsub("<format>", @format.to_s)
77
- @url = pattern
40
+ # Sets whether the Resource requires authentication. Always sets to a boolean value.
41
+ def authenticates=(bool)
42
+ @authenticates = bool ? true : false
78
43
  end
79
44
 
80
- def oauth=(bool)
81
- @authenticates = false if bool
82
- @oauth = if bool
83
- true
84
- else
85
- false
86
- end
45
+ # Does the Resource require authentication?
46
+ def authenticates?
47
+ @authenticates
87
48
  end
88
49
 
89
- def authenticates=(bool)
90
- @oauth = false if bool
91
- @authenticates = if bool
92
- true
93
- else
94
- false
95
- end
50
+ # Sets whether the Resource should follow redirection. Always sets to a boolean value.
51
+ def follows=(bool)
52
+ @follows = (bool ? true : false)
96
53
  end
97
54
 
98
- def authenticates?
99
- @authenticates
55
+ # Should the resource follow redirection?
56
+ def follows?
57
+ @follows
100
58
  end
101
59
 
102
- def oauth?
103
- @oauth
60
+ # Set the Resource's URL as a URI
61
+ def url=(uri)
62
+ @url = URI.parse(uri)
104
63
  end
105
64
 
106
- def access_token=(token)
107
- raise ArgumentError, "Token needs to be an OAuth::AccessToken object" unless token.is_a?(OAuth::AccessToken)
108
- @oauth = true
109
- @access_token = token
65
+ # A hash representation of the Resource
66
+ def to_hash
67
+ {@name.to_sym => { :via => via,
68
+ :with => with,
69
+ :requires => requires,
70
+ :follows => follows?,
71
+ :authenticates => authenticates?,
72
+ :url => url,
73
+ :headers => headers}}
74
+ end
75
+
76
+ # Take parameters, default params, and credentials and build a Request object for this Resource
77
+ def build!(params={}, defaults=nil, credentials=nil)
78
+ uri = @url
79
+ parameters = setup_parameters(params, defaults)
80
+ request_opts = setup_options(parameters, credentials)
81
+ uri.query = request_opts[:query].to_params if request_opts[:query]
82
+ Weary::Request.new(uri.normalize.to_s, @via, request_opts)
83
+ end
84
+
85
+ # Setup the parameters to make the Request with
86
+ def setup_parameters(params={}, defaults=nil)
87
+ params = defaults ? defaults.merge(params) : params
88
+ find_missing_requirements(params)
89
+ remove_unnecessary_params(params)
110
90
  end
111
91
 
112
- def follows_redirects?
113
- if @follows
114
- true
92
+ # Search the given parameters to see if they are missing any required params
93
+ def find_missing_requirements(params)
94
+ if (@requires && !@requires.empty?)
95
+ missing_requirements = @requires - params.keys
96
+ raise ArgumentError, "This resource is missing required parameters: '#{missing_requirements.inspect}'" unless missing_requirements.empty?
97
+ end
98
+ end
99
+
100
+ # Remove params that have not been specified with #with
101
+ def remove_unnecessary_params(params)
102
+ params.delete_if {|k,v| !@with.include?(k) } if (@with && !@with.empty?)
103
+ end
104
+
105
+ # Setup the options to be passed into the Request
106
+ def setup_options(params={}, credentials=nil)
107
+ options = {}
108
+ prepare_request_body(params, options)
109
+ setup_authentication(options, credentials)
110
+ options[:no_follow] = true if !follows?
111
+ options[:headers] = @headers if !@headers.blank?
112
+ options
113
+ end
114
+
115
+ # Prepare the Request query or body depending on the HTTP method
116
+ def prepare_request_body(params, options={})
117
+ if (@via == :post || @via == :put)
118
+ options[:body] = params unless params.blank?
115
119
  else
116
- false
120
+ options[:query] = params unless params.blank?
117
121
  end
122
+ options
118
123
  end
119
-
120
- def to_hash
121
- {@name.to_sym => { :via => @via,
122
- :with => @with,
123
- :requires => @requires,
124
- :follows => follows_redirects?,
125
- :authenticates => authenticates?,
126
- :format => @format,
127
- :url => @url,
128
- :domain => @domain,
129
- :headers => @headers,
130
- :oauth => oauth?,
131
- :access_token => @access_token}}
124
+
125
+ # Prepare authentication credentials for the Request
126
+ def setup_authentication(options, credentials=nil)
127
+ if authenticates?
128
+ raise ArgumentError, "This resource requires authentication and no credentials were given." if credentials.blank?
129
+ if credentials.is_a?(OAuth::AccessToken)
130
+ options[:oauth] = credentials
131
+ else
132
+ options[:basic_auth] = credentials
133
+ end
134
+ end
135
+ options
132
136
  end
133
137
 
134
138
  end
@@ -75,17 +75,6 @@ module Weary
75
75
  parse[key]
76
76
  end
77
77
 
78
- # Search the body with a CSS/XPath selector with Nokogiri.
79
- # If the document is not XMLish, fall back to #parse and ditch the selector.
80
- def search(selector)
81
- if @format == (:xml || :html)
82
- doc = Nokogiri.parse(@body)
83
- doc.search(selector)
84
- else
85
- parse
86
- end
87
- end
88
-
89
78
  private
90
79
  def handle_errors
91
80
  case @code
@@ -1,7 +1,8 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
1
3
  require 'rubygems'
2
- gem 'rspec'
3
4
  require 'spec'
4
- require File.join(File.dirname(__FILE__), '..', 'lib', 'weary')
5
+ require 'weary'
5
6
 
6
7
  def get_fixture(filename)
7
8
  open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename.to_s}")).read
@@ -0,0 +1,321 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Weary::Base do
4
+ describe 'Class' do
5
+
6
+ describe 'Resource Defaults' do
7
+ before do
8
+ @headers = {"User-Agent" => Weary::UserAgents["Safari 4.0.2 - Mac"]}
9
+ end
10
+
11
+ it 'sets default headers' do
12
+ test = Class.new(Weary::Base)
13
+ test.headers @headers
14
+ test.instance_variable_get(:@headers).should == @headers
15
+ end
16
+
17
+ it "sets a domain to be used in default url's" do
18
+ test = Class.new(Weary::Base)
19
+ test.domain 'http://github.com'
20
+ test.instance_variable_get(:@domain).should == 'http://github.com/'
21
+ end
22
+
23
+ it 'panics when a domain that is not a url is given' do
24
+ test = Class.new(Weary::Base)
25
+ lambda { test.domain 'foobar' }.should raise_error
26
+ end
27
+
28
+ it "sets a format to use in default url's" do
29
+ test = Class.new(Weary::Base)
30
+ test.format(:json)
31
+ test.instance_variable_get(:@format).should == :json
32
+ end
33
+ end
34
+
35
+ describe 'Resource Preparation' do
36
+ before do
37
+ @headers = {"User-Agent" => Weary::UserAgents["Safari 4.0.2 - Mac"]}
38
+ prepTest = Class.new(Weary::Base)
39
+ prepTest.headers @headers
40
+ prepTest.domain 'http://foobar.com'
41
+ prepTest.format :xml
42
+ @t = prepTest.prepare_resource("test",:get)
43
+ end
44
+
45
+ it 'prepares a Resource' do
46
+ @t.class.should == Weary::Resource
47
+ end
48
+
49
+ it 'has a name' do
50
+ @t.name.should == "test"
51
+ end
52
+
53
+ it 'has an http method' do
54
+ @t.via.should == :get
55
+ end
56
+
57
+ it 'has headers' do
58
+ @t.headers.should == @headers
59
+ end
60
+
61
+ it 'builds a default url if a domain is provided' do
62
+ @t.url.normalize.to_s.should == 'http://foobar.com/test.xml'
63
+ end
64
+
65
+ it 'builds a default url with a json extension if no format is explicitly named' do
66
+ t = Class.new(Weary::Base)
67
+ t.domain 'http://foobar.com'
68
+ p = t.prepare_resource("test",:get)
69
+ p.url.normalize.to_s.should == 'http://foobar.com/test.json'
70
+ end
71
+
72
+ it 'ignores the url if no domain is provided' do
73
+ t = Class.new(Weary::Base).prepare_resource("test",:get)
74
+ t.url.should == nil
75
+ end
76
+
77
+ it 'ignores headers if no headers are defined' do
78
+ t = Class.new(Weary::Base).prepare_resource("test",:get)
79
+ t.headers.should == nil
80
+ end
81
+ end
82
+
83
+ describe 'Resource Storage' do
84
+ before do
85
+ @headers = {"User-Agent" => Weary::UserAgents["Safari 4.0.2 - Mac"]}
86
+ @restest = Class.new(Weary::Base)
87
+ @restest.headers @headers
88
+ @restest.domain 'http://foobar.com'
89
+ @r = @restest.prepare_resource("test",:get)
90
+ end
91
+
92
+ it 'has a store for resources' do
93
+ @restest.class_variable_defined?(:@@resources).should == true
94
+ end
95
+
96
+ it 'stores the resource for future use' do
97
+ @restest.store_resource(@r).should == @r
98
+ @restest.resources.include?(:test).should == true
99
+ end
100
+ end
101
+
102
+ describe 'Resource Construction' do
103
+ before do
104
+ @contest = Class.new(Weary::Base)
105
+ @contest.domain 'http://foobar.com'
106
+ end
107
+
108
+ it 'prepares a resource to be used' do
109
+ r = @contest.build_resource "test", :post
110
+ r.name.should == "test"
111
+ r.via.should == :post
112
+ end
113
+
114
+ it 'passes the resource into a block for further refinement' do
115
+ r = @contest.build_resource("test", :post, Proc.new {|res| res.via = :put })
116
+ r.name.should == "test"
117
+ r.via.should == :put
118
+ end
119
+
120
+ it 'stores the resource' do
121
+ r = @contest.build_resource("test 2", :get)
122
+ @contest.resources.include?(:test_2).should == true
123
+ end
124
+
125
+ it 'builds the method for the resource' do
126
+ r = @contest.build_resource("test 3", :get)
127
+ @contest.public_method_defined?(:test_3).should == true
128
+ end
129
+ end
130
+
131
+ describe 'Resource Declaration' do
132
+ before do
133
+ @dectest = Class.new(Weary::Base)
134
+ @dectest.domain 'http://foobar.com'
135
+ end
136
+
137
+ it 'gets a resource' do
138
+ r = @dectest.get "get test"
139
+ r.via.should == :get
140
+ r.name.should == "get_test"
141
+ end
142
+
143
+ it 'posts a resource' do
144
+ r = @dectest.post "post test"
145
+ r.via.should == :post
146
+ r.name.should == "post_test"
147
+ end
148
+
149
+ it 'puts a resource' do
150
+ r = @dectest.put "put test"
151
+ r.via.should == :put
152
+ r.name.should == "put_test"
153
+ end
154
+
155
+ it 'deletes a resource' do
156
+ r = @dectest.delete "del test"
157
+ r.via.should == :delete
158
+ r.name.should == "del_test"
159
+ end
160
+
161
+ it 'declares a resource' do
162
+ r = @dectest.declare "generic test"
163
+ r.via.should == :get
164
+ r.name.should == "generic_test"
165
+ end
166
+
167
+ it 'stores the resource' do
168
+ @dectest.get "storage test"
169
+ @dectest.resources.include?(:storage_test).should == true
170
+ end
171
+ end
172
+
173
+ describe 'Method Building' do
174
+ before do
175
+ @methtest = Class.new(Weary::Base)
176
+ @methtest.domain 'http://foobar.com'
177
+
178
+ r = @methtest.prepare_resource("method_test",:get)
179
+ @methtest.build_method(r)
180
+
181
+ a = @methtest.prepare_resource("authentication_test",:post)
182
+ a.authenticates = true
183
+ @methtest.build_method(a)
184
+
185
+ d = @methtest.prepare_resource("params_test",:post)
186
+ d.requires = :id
187
+ d.with = [:message, :user]
188
+ @methtest.build_method(d)
189
+ end
190
+
191
+ it 'builds a method with the name of the resource' do
192
+ n = @methtest.new
193
+ n.respond_to?(:method_test).should == true
194
+ end
195
+
196
+ it 'forms a Request according to the guidelines of the Resource' do
197
+ n = @methtest.new
198
+ n.method_test.class.should == Weary::Request
199
+ end
200
+
201
+ it 'passes in authentication credentials if defined' do
202
+ n = @methtest.new
203
+ cred = {:username => 'mwunsch', :password => 'secret'}
204
+ lambda { n.authentication_test }.should raise_error
205
+ n.credentials cred[:username], cred[:password]
206
+ n.authentication_test.options[:basic_auth].should == cred
207
+ end
208
+
209
+ it 'passes in default parameters if defined' do
210
+ n = @methtest.new
211
+ defaults = {:id => 1234, :message => "Hello world"}
212
+ lambda { n.params_test }.should raise_error
213
+ n.defaults = defaults
214
+ n.params_test.options[:body].should == defaults
215
+ end
216
+
217
+ it 'accepts parameters when given' do
218
+ n = @methtest.new
219
+ req = n.params_test :id => 1234, :message => "Hello world", :foo => "Bar"
220
+ req.options[:body].should == {:id => 1234, :message => "Hello world"}
221
+ req.options[:body].has_key?(:foo).should == false
222
+ end
223
+
224
+
225
+ end
226
+
227
+ end
228
+
229
+ describe 'Object' do
230
+ before do
231
+ @klass = Class.new(Weary::Base)
232
+ @klass.domain 'http://foobar.com'
233
+ @klass.format :xml
234
+ @klass.headers({"User-Agent" => Weary::UserAgents["Safari 4.0.2 - Mac"]})
235
+ @klass.get "thing" do |r|
236
+ r.requires = :id
237
+ r.with = [:message, :user]
238
+ end
239
+ @klass.post "important" do |r|
240
+ r.requires = :id
241
+ r.authenticates = true
242
+ end
243
+ end
244
+
245
+ it 'has methods defined by the class' do
246
+ obj = @klass.new
247
+ obj.respond_to?(:thing).should == true
248
+ obj.respond_to?(:important).should == true
249
+ end
250
+
251
+ it 'can set authentication credentials' do
252
+ obj = @klass.new
253
+
254
+ obj.credentials "username", "password"
255
+ obj.instance_variable_get(:@credentials).should == {:username => "username", :password => "password"}
256
+ obj.important(:id => 1234).options[:basic_auth].should == obj.instance_variable_get(:@credentials)
257
+ end
258
+
259
+ it 'credentials can be an OAuth access token' do
260
+ oauth_consumer = OAuth::Consumer.new("consumer_token","consumer_secret",{:site => 'http://foo.bar'})
261
+ oauth_token = OAuth::AccessToken.new(oauth_consumer, "token", "secret")
262
+ obj = @klass.new
263
+
264
+ obj.credentials oauth_token
265
+ obj.instance_variable_get(:@credentials).class.should == OAuth::AccessToken
266
+ obj.important(:id => 1234).options.has_key?(:oauth).should == true
267
+ obj.important(:id => 1234).options.has_key?(:basic_auth).should == false
268
+ end
269
+
270
+ it 'can set defaults to pass into requests' do
271
+ obj = @klass.new
272
+
273
+ obj.defaults = {:user => "mwunsch", :message => "hello world"}
274
+ obj.defaults.should == {:user => "mwunsch", :message => "hello world"}
275
+ obj.thing(:id => 1234).options[:query].should == {:user => "mwunsch", :message => "hello world", :id => 1234}
276
+ end
277
+
278
+ it 'has a list of resources' do
279
+ obj = @klass.new
280
+
281
+ obj.resources == @klass.resources
282
+ end
283
+
284
+ it 'should keep its resources separate' do
285
+ obj = @klass.new
286
+
287
+ obj.resources[:foo] = 'bar'
288
+ obj.resources.has_key?(:foo).should == true
289
+ @klass.resources.has_key?(:foo).should == false
290
+ obj.resources.delete(:foo)
291
+ end
292
+
293
+ it 'is able to rebuild its request method, like a singleton' do
294
+ obj1 = @klass.new
295
+ require 'pp'
296
+
297
+ obj1.resources[:thing].follows = false
298
+ obj1.rebuild_method(obj1.resources[:thing])
299
+
300
+ obj1.thing(:id => 1234).options[:no_follow].should == true
301
+ @klass.new.thing(:id => 1234).options[:no_follow].should == nil
302
+ end
303
+
304
+ it 'can modify a resource without modifying the resources of its class' do
305
+ obj = @klass.new
306
+
307
+ obj.modify_resource(:thing) {|r| r.url = "http://bar.foo" }
308
+ obj.resources[:thing].url.normalize.to_s.should == "http://bar.foo/"
309
+ obj.class.resources[:thing].url.normalize.to_s.should_not == "http://bar.foo/"
310
+ end
311
+
312
+ it 'modifying a resource rebuilds the method' do
313
+ obj = @klass.new
314
+
315
+ obj.credentials "username", "password"
316
+ obj.modify_resource(:important) {|r| r.follows = false }
317
+ obj.important(:id => 1234).options[:no_follow].should == true
318
+ end
319
+
320
+ end
321
+ end