weary 0.5.1 → 0.6.0

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