twitter4r 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,331 @@
1
+ # Contains Twitter4R Model API.
2
+
3
+ module Twitter
4
+ # Mixin module for model classes. Includes generic class methods like
5
+ # unmarshal.
6
+ #
7
+ # To create a new model that includes this mixin's features simply:
8
+ # class NewModel
9
+ # include Twitter::ModelMixin
10
+ # end
11
+ #
12
+ # This mixin module automatically includes <tt>Twitter::ClassUtilMixin</tt>
13
+ # features.
14
+ #
15
+ # The contract for models to use this mixin correctly is that the class
16
+ # including this mixin must provide an class method named <tt>attributes</tt>
17
+ # that will return an Array of attribute symbols that will be checked
18
+ # in #eql? override method. The following would be sufficient:
19
+ # def self.attributes; @@ATTRIBUTES; end
20
+ module ModelMixin #:nodoc:
21
+ def self.included(base) #:nodoc:
22
+ base.send(:include, Twitter::ClassUtilMixin)
23
+ base.send(:include, InstanceMethods)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ # Class methods defined for <tt>Twitter::ModelMixin</tt> module.
28
+ module ClassMethods #:nodoc:
29
+ # Unmarshal object singular or plural array of model objects
30
+ # from JSON serialization. Currently JSON is only supported
31
+ # since this is all <tt>Twitter4R</tt> needs.
32
+ def unmarshal(raw)
33
+ input = JSON.parse(raw)
34
+ def unmarshal_model(hash)
35
+ self.new(hash)
36
+ end
37
+ return unmarshal_model(input) if input.is_a?(Hash) # singular case
38
+ result = []
39
+ input.each do |hash|
40
+ model = unmarshal_model(hash) if hash.is_a?(Hash)
41
+ result << model
42
+ end if input.is_a?(Array)
43
+ result # plural case
44
+ end
45
+ end
46
+
47
+ # Instance methods defined for <tt>Twitter::ModelMixin</tt> module.
48
+ module InstanceMethods #:nodoc:
49
+ attr_accessor :client
50
+ # Equality method override of Object#eql? default.
51
+ #
52
+ # Relies on the class using this mixin to provide a <tt>attributes</tt>
53
+ # class method that will return an Array of attributes to check are
54
+ # equivalent in this #eql? override.
55
+ #
56
+ # It is by design that the #eql? method will raise a NoMethodError
57
+ # if no <tt>attributes</tt> class method exists, to alert you that
58
+ # you must provide it for a meaningful result from this #eql? override.
59
+ # Otherwise this will return a meaningless result.
60
+ def eql?(other)
61
+ attrs = self.class.attributes
62
+ attrs.each do |att|
63
+ return false unless self.send(att).eql?(other.send(att))
64
+ end
65
+ true
66
+ end
67
+
68
+ # Returns integer representation of model object instance.
69
+ #
70
+ # For example,
71
+ # status = Twitter::Status.new(:id => 234343)
72
+ # status.to_i #=> 234343
73
+ def to_i
74
+ @id
75
+ end
76
+
77
+ # Returns string representation of model object instance.
78
+ #
79
+ # For example,
80
+ # status = Twitter::Status.new(:text => 'my status message')
81
+ # status.to_s #=> 'my status message'
82
+ #
83
+ # If a model class doesn't have a @text attribute defined
84
+ # the default Object#to_s will be returned as the result.
85
+ def to_s
86
+ self.respond_to(:text) ? @text : super.to_s
87
+ end
88
+
89
+ # Returns hash representation of model object instance.
90
+ #
91
+ # For example,
92
+ # u = Twitter::User.new(:id => 2342342, :screen_name => 'tony_blair_is_the_devil')
93
+ # u.to_hash #=> {:id => 2342342, :screen_name => 'tony_blair_is_the_devil'}
94
+ #
95
+ # This method also requires that the class method <tt>attributes</tt> be
96
+ # defined to return an Array of attributes for the class.
97
+ def to_hash
98
+ attrs = self.class.attributes
99
+ result = {}
100
+ attrs.each do |att|
101
+ value = self.send(att)
102
+ value = value.to_hash if value.respond_to?(:to_hash)
103
+ result[att] = value if value
104
+ end
105
+ result
106
+ end
107
+
108
+ # "Blesses" model object.
109
+ #
110
+ # Should be overridden by model class if special behavior is expected
111
+ #
112
+ # Expected to return blessed object (usually <tt>self</tt>)
113
+ def bless(client)
114
+ self.basic_bless(client)
115
+ end
116
+
117
+ protected
118
+ # Basic "blessing" of model object
119
+ def basic_bless(client)
120
+ self.client = client
121
+ self
122
+ end
123
+ end
124
+ end
125
+
126
+ module AuthenticatedUserMixin
127
+ def self.included(base)
128
+ base.send(:include, InstanceMethods)
129
+ end
130
+
131
+ module InstanceMethods
132
+ # Returns an Array of user objects that represents the authenticated
133
+ # user's friends on Twitter.
134
+ def followers
135
+ @client.user(@id, :followers)
136
+ end
137
+
138
+ # Adds given user as a friend. Returns user object as given by
139
+ # <tt>Twitter</tt> REST server response.
140
+ #
141
+ # For <tt>user</tt> argument you may pass in the unique integer
142
+ # user ID, screen name or Twitter::User object representation.
143
+ def befriend(user)
144
+ @client.friend(:add, user)
145
+ end
146
+
147
+ # Removes given user as a friend. Returns user object as given by
148
+ # <tt>Twitter</tt> REST server response.
149
+ #
150
+ # For <tt>user</tt> argument you may pass in the unique integer
151
+ # user ID, screen name or Twitter::User object representation.
152
+ def defriend(user)
153
+ @client.friend(:remove, user)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Represents a <tt>Twitter</tt> user
159
+ class User
160
+ include ModelMixin
161
+ @@ATTRIBUTES = [:id, :name, :description, :location, :screen_name, :url]
162
+ attr_accessor *@@ATTRIBUTES
163
+
164
+ class << self
165
+ # Used as factory method callback
166
+ def attributes; @@ATTRIBUTES; end
167
+
168
+ # Returns user model object with given <tt>id</tt> using the configuration
169
+ # and credentials of the <tt>client</tt> object passed in.
170
+ #
171
+ # You can pass in either the user's unique integer ID or the user's
172
+ # screen name.
173
+ def find(id, client)
174
+ client.user(id)
175
+ end
176
+ end
177
+
178
+ # Override of ModelMixin#bless method.
179
+ #
180
+ # Adds #followers instance method when user object represents
181
+ # authenticated user. Otherwise just do basic bless.
182
+ #
183
+ # This permits applications using <tt>Twitter4R</tt> to write
184
+ # Rubyish code like this:
185
+ # followers = user.followers if user.is_me?
186
+ # Or:
187
+ # followers = user.followers if user.respond_to?(:followers)
188
+ def bless(client)
189
+ basic_bless(client)
190
+ self.instance_eval(%{
191
+ self.class.send(:include, Twitter::AuthenticatedUserMixin)
192
+ }) if self.is_me? and not self.respond_to?(:followers)
193
+ self
194
+ end
195
+
196
+ # Returns whether this <tt>Twitter::User</tt> model object
197
+ # represents the authenticated user of the <tt>client</tt>
198
+ # that blessed it.
199
+ def is_me?
200
+ # TODO: Determine whether we should cache this or not?
201
+ # Might be dangerous to do so, but do we want to support
202
+ # the edge case where this would cause a problem? i.e.
203
+ # changing authenticated user after initial use of
204
+ # authenticated API.
205
+ # TBD: To cache or not to cache. That is the question!
206
+ # Since this is an implementation detail we can leave this for
207
+ # subsequent 0.2.x releases. It doesn't have to be decided before
208
+ # the 0.2.0 launch.
209
+ @screen_name == @client.instance_eval("@login")
210
+ end
211
+
212
+ # Returns an Array of user objects that represents the authenticated
213
+ # user's friends on Twitter.
214
+ def friends
215
+ @client.user(@id, :friends)
216
+ end
217
+ end # User
218
+
219
+ # Represents a status posted to <tt>Twitter</tt> by a <tt>Twitter</tt> user.
220
+ class Status
221
+ include ModelMixin
222
+ @@ATTRIBUTES = [:id, :text, :created_at, :user]
223
+ attr_accessor *@@ATTRIBUTES
224
+
225
+ class << self
226
+ # Used as factory method callback
227
+ def attributes; @@ATTRIBUTES; end
228
+
229
+ # Returns status model object with given <tt>status</tt> using the
230
+ # configuration and credentials of the <tt>client</tt> object passed in.
231
+ def find(id, client)
232
+ client.status(:get, id)
233
+ end
234
+
235
+ # Creates a new status for the authenticated user of the given
236
+ # <tt>client</tt> context.
237
+ #
238
+ # You MUST include a valid/authenticated <tt>client</tt> context
239
+ # in the given <tt>params</tt> argument.
240
+ #
241
+ # For example:
242
+ # status = Twitter::Status.create(
243
+ # :text => 'I am shopping for flip flops',
244
+ # :client => client)
245
+ #
246
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
247
+ # is given in the <tt>params</tt> Hash. For example,
248
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
249
+ # The above line of code will raise an <tt>ArgumentError</tt>.
250
+ #
251
+ # The same is true when you do not provide a <tt>:text</tt> key-value
252
+ # pair in the <tt>params</tt> argument given.
253
+ #
254
+ # The Twitter::Status object returned after the status successfully
255
+ # updates on the Twitter server side is returned from this method.
256
+ def create(params)
257
+ client, text = params[:client], params[:text]
258
+ raise ArgumentError, 'Valid client context must be provided' unless client.is_a?(Twitter::Client)
259
+ raise ArgumentError, 'Must provide text for the status to update' unless text.is_a?(String)
260
+ client.status(:post, text)
261
+ end
262
+ end
263
+
264
+ protected
265
+ # Constructor callback
266
+ def init
267
+ @user = User.new(@user) if @user.is_a?(Hash)
268
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
269
+ end
270
+ end # Status
271
+
272
+ # Represents a direct message on <tt>Twitter</tt> between <tt>Twitter</tt> users.
273
+ class Message
274
+ include ModelMixin
275
+ @@ATTRIBUTES = [:id, :recipient, :sender, :text, :created_at]
276
+ attr_accessor *@@ATTRIBUTES
277
+
278
+ class << self
279
+ # Used as factory method callback
280
+ def attributes; @@ATTRIBUTES; end
281
+
282
+ # Raises <tt>NotImplementedError</tt> because currently
283
+ # <tt>Twitter</tt> doesn't provide a facility to retrieve
284
+ # one message by unique ID.
285
+ def find(id, client)
286
+ raise NotImplementedError, 'Twitter has yet to implement a REST API for this. This is not a Twitter4R library limitation.'
287
+ end
288
+
289
+ # Creates a new direct message from the authenticated user of the
290
+ # given <tt>client</tt> context.
291
+ #
292
+ # You MUST include a valid/authenticated <tt>client</tt> context
293
+ # in the given <tt>params</tt> argument.
294
+ #
295
+ # For example:
296
+ # status = Twitter::Message.create(
297
+ # :text => 'I am shopping for flip flops',
298
+ # :receipient => 'anotherlogin',
299
+ # :client => client)
300
+ #
301
+ # An <tt>ArgumentError</tt> will be raised if no valid client context
302
+ # is given in the <tt>params</tt> Hash. For example,
303
+ # status = Twitter::Status.create(:text => 'I am shopping for flip flops')
304
+ # The above line of code will raise an <tt>ArgumentError</tt>.
305
+ #
306
+ # The same is true when you do not provide any of the following
307
+ # key-value pairs in the <tt>params</tt> argument given:
308
+ # * <tt>text</tt> - the String that will be the message text to send to <tt>user</tt>
309
+ # * <tt>recipient</tt> - the user ID, screen_name or Twitter::User object representation of the recipient of the direct message
310
+ #
311
+ # The Twitter::Message object returned after the direct message is
312
+ # successfully sent on the Twitter server side is returned from
313
+ # this method.
314
+ def create(params)
315
+ client, text, recipient = params[:client], params[:text], params[:recipient]
316
+ raise ArgumentError, 'Valid client context must be given' unless client.is_a?(Twitter::Client)
317
+ raise ArgumentError, 'Message text must be supplied to send direct message' unless text.is_a?(String)
318
+ raise ArgumentError, 'Recipient user must be specified to send direct message' unless [Twitter::User, Integer, String].member?(recipient.class)
319
+ client.message(:post, text, recipient)
320
+ end
321
+ end
322
+
323
+ protected
324
+ # Constructor callback
325
+ def init
326
+ @sender = User.new(@sender) if @sender.is_a?(Hash)
327
+ @recipient = User.new(@recipient) if @recipient.is_a?(Hash)
328
+ @created_at = Time.parse(@created_at) if @created_at.is_a?(String)
329
+ end
330
+ end # Message
331
+ end # Twitter
@@ -1,12 +1,17 @@
1
+ # version.rb contains <tt>Twitter::Version</tt> that provides helper
2
+ # methods related to versioning of the <tt>Twitter4R</tt> project.
3
+
1
4
  module Twitter::Version #:nodoc:
2
5
  MAJOR = 0
3
- MINOR = 1
4
- REVISION = 1
6
+ MINOR = 2
7
+ REVISION = 0
5
8
  class << self
9
+ # Returns X.Y.Z formatted version string
6
10
  def to_version
7
11
  "#{MAJOR}.#{MINOR}.#{REVISION}"
8
12
  end
9
13
 
14
+ # Returns X-Y-Z formatted version name
10
15
  def to_name
11
16
  "#{MAJOR}_#{MINOR}_#{REVISION}"
12
17
  end
data/lib/twitter.rb CHANGED
@@ -1,14 +1,25 @@
1
+ #
2
+
1
3
  module Twitter; end
2
4
 
3
- def resolve_path(suffix)
4
- File.expand_path(File.join(File.dirname(__FILE__), suffix))
5
+ def require_local(suffix)
6
+ require(File.expand_path(File.join(File.dirname(__FILE__), suffix)))
5
7
  end
6
8
 
7
9
  # For better unicode support
8
10
  $KCODE = 'u'
9
11
  require 'jcode'
10
12
 
13
+ # External requires
14
+ require('net/https')
15
+ require('uri')
16
+ require('json')
17
+
11
18
  # Ordering matters...pay attention here!
12
- require(resolve_path('twitter/version'))
13
- require(resolve_path('twitter/meta'))
14
- require(resolve_path('twitter/core'))
19
+ require_local('twitter/ext')
20
+ require_local('twitter/version')
21
+ require_local('twitter/meta')
22
+ require_local('twitter/core')
23
+ require_local('twitter/model')
24
+ require_local('twitter/config')
25
+ require_local('twitter/client')
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,52 @@
1
1
  require 'spec'
2
2
  require 'twitter'
3
+ require 'twitter/console'
4
+ require 'twitter/extras'
3
5
 
4
6
  # Add helper methods here if relevant to multiple _spec.rb files
5
7
 
8
+ # Spec helper that sets attribute <tt>att</tt> for given objects <tt>obj</tt>
9
+ # and <tt>other</tt> to given <tt>value</tt>.
10
+ def equalizer(obj, other, att, value)
11
+ setter = "#{att}="
12
+ obj.send(setter, value)
13
+ other.send(setter, value)
14
+ end
15
+
16
+ # Spec helper that nil-izes objects passed in
17
+ def nilize(*objects)
18
+ objects.each {|obj| obj = nil }
19
+ end
20
+
21
+ # Returns default <tt>client</tt> context object
22
+ def client_context(file = 'config/twitter.yml')
23
+ Twitter::Client.from_config(file)
24
+ end
25
+
26
+ # Spec helper that returns a mocked <tt>Twitter::Config</tt> object
27
+ # with stubbed attributes and <tt>attrs</tt> for overriding attribute
28
+ # values.
29
+ def stubbed_twitter_config(config, attrs = {})
30
+ opts = {
31
+ :protocol => :ssl,
32
+ :host => 'twitter.com',
33
+ :port => 443,
34
+ :proxy_host => 'proxy.host',
35
+ :proxy_port => 8080,
36
+ }.merge(attrs)
37
+ config.stub!(:protocol).and_return(opts[:protocol])
38
+ config.stub!(:host).and_return(opts[:host])
39
+ config.stub!(:port).and_return(opts[:port])
40
+ config.stub!(:proxy_host).and_return(opts[:proxy_host])
41
+ config.stub!(:proxy_port).and_return(opts[:proxy_port])
42
+ config
43
+ end
44
+
45
+ def mas_twitter_config(attrs = {})
46
+ config = mock(Twitter::Config)
47
+ stubbed_twitter_conf(config, attrs)
48
+ end
49
+
6
50
  # Spec helper that returns the project root directory as absolute path string
7
51
  def project_root_dir
8
52
  File.expand_path(File.join(File.dirname(__FILE__), '..'))
@@ -16,16 +60,14 @@ def stubbed_net_http(response, obj_stubs = {}, host = 'twitter.com', port = 80)
16
60
  http = Net::HTTP.new(host, port)
17
61
  Net::HTTP.stub!(:new).and_return(http)
18
62
  http.stub!(:request).and_return(response)
19
- http.stub!(:use_ssl=)
20
- http.stub!(:verify_mode=)
21
63
  http
22
64
  end
23
65
 
24
66
  # Spec helper that returns a mocked <tt>Net::HTTP</tt> object and
25
67
  # stubs out the <tt>request</tt> method to return the given
26
68
  # <tt>response</tt>
27
- def mas_net_http(response)
28
- http = mock(Net::HTTP)
69
+ def mas_net_http(response, obj_stubs = {})
70
+ http = mock(Net::HTTP, obj_stubs)
29
71
  Net::HTTP.stub!(:new).and_return(http)
30
72
  http.stub!(:request).and_return(response)
31
73
  http.stub!(:start).and_yield(http)
@@ -87,4 +129,5 @@ end
87
129
  def _create_http_response(mock_response, code, message)
88
130
  mock_response.stub!(:code).and_return(code)
89
131
  mock_response.stub!(:message).and_return(message)
132
+ mock_response.stub!(:is_a?).and_return(true) if ["200", "201"].member?(code)
90
133
  end
@@ -0,0 +1,232 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+
3
+ describe "Twitter::Client" do
4
+ before(:each) do
5
+ @init_hash = { :login => 'user', :password => 'pass' }
6
+ end
7
+
8
+ it ".new should accept login and password as initializer hash keys and set the values to instance values" do
9
+ client = nil
10
+ lambda do
11
+ client = Twitter::Client.new(@init_hash)
12
+ end.should_not raise_error
13
+ client.send(:login).should eql(@init_hash[:login])
14
+ client.send(:password).should eql(@init_hash[:password])
15
+ end
16
+ end
17
+
18
+ describe Twitter::Client, "#http_header" do
19
+ before(:each) do
20
+ @user_agent = 'myapp'
21
+ @application_name = @user_agent
22
+ @application_version = '1.2.3'
23
+ @application_url = 'http://myapp.url'
24
+ Twitter::Client.configure do |conf|
25
+ conf.user_agent = @user_agent
26
+ conf.application_name = @application_name
27
+ conf.application_version = @application_version
28
+ conf.application_url = @application_url
29
+ end
30
+ @expected_headers = {
31
+ 'Accept' => 'text/x-json',
32
+ 'X-Twitter-Client' => @application_name,
33
+ 'X-Twitter-Client-Version' => @application_version,
34
+ 'X-Twitter-Client-URL' => @application_url,
35
+ 'User-Agent' => "Twitter4R v#{Twitter::Version.to_version} [#{@user_agent}]",
36
+ }
37
+ @twitter = client_context
38
+ # resent @@http_header class variable in Twitter::Client class
39
+ Twitter::Client.class_eval("@@http_header = nil")
40
+ end
41
+
42
+ it "should always return expected HTTP headers" do
43
+ headers = @twitter.send(:http_header)
44
+ headers.should eql(@expected_headers)
45
+ end
46
+
47
+ it "should cache HTTP headers Hash in class variable after first invocation" do
48
+ cache = Twitter::Client.class_eval("@@http_header")
49
+ cache.should be_nil
50
+ @twitter.send(:http_header)
51
+ cache = Twitter::Client.class_eval("@@http_header")
52
+ cache.should_not be_nil
53
+ cache.should eql(@expected_headers)
54
+ end
55
+
56
+ after(:each) do
57
+ nilize(@user_agent, @application_name, @application_version, @application_url, @twitter, @expected_headers)
58
+ end
59
+ end
60
+
61
+ describe Twitter::Client, "#create_http_get_request" do
62
+ before(:each) do
63
+ @uri = '/some/path'
64
+ @expected_get_request = mock(Net::HTTP::Get)
65
+ @twitter = client_context
66
+ @default_header = @twitter.send(:http_header)
67
+ end
68
+
69
+ it "should create new Net::HTTP::Get object with expected initialization arguments" do
70
+ Net::HTTP::Get.should_receive(:new).with(@uri, @default_header).and_return(@expected_get_request)
71
+ @twitter.send(:create_http_get_request, @uri)
72
+ end
73
+
74
+ after(:each) do
75
+ nilize(@twitter, @uri, @expected_get_request, @default_header)
76
+ end
77
+ end
78
+
79
+ describe Twitter::Client, "#create_http_post_request" do
80
+ before(:each) do
81
+ @uri = '/some/path'
82
+ @expected_post_request = mock(Net::HTTP::Post)
83
+ @twitter = client_context
84
+ @default_header = @twitter.send(:http_header)
85
+ end
86
+
87
+ it "should create new Net::HTTP::Post object with expected initialization arguments" do
88
+ Net::HTTP::Post.should_receive(:new).with(@uri, @default_header).and_return(@expected_post_request)
89
+ @twitter.send(:create_http_post_request, @uri)
90
+ end
91
+
92
+ after(:each) do
93
+ nilize(@twitter, @uri, @expected_post_request, @default_header)
94
+ end
95
+ end
96
+
97
+ describe Twitter::Client, "#create_http_delete_request" do
98
+ before(:each) do
99
+ @uri = '/a/stupid/path/that/is/not/restful/since/twitter.com/cannot/do/consistent/restful/apis'
100
+ @expected_delete_request = mock(Net::HTTP::Delete)
101
+ @twitter = client_context
102
+ @default_header = @twitter.send(:http_header)
103
+ end
104
+
105
+ it "should create new Net::HTTP::Delete object with expected initialization arguments" do
106
+ Net::HTTP::Delete.should_receive(:new).with(@uri, @default_header).and_return(@expected_delete_request)
107
+ @twitter.send(:create_http_delete_request, @uri)
108
+ end
109
+
110
+ after(:each) do
111
+ nilize(@twitter, @uri, @expected_delete_request, @default_header)
112
+ end
113
+ end
114
+
115
+ describe Twitter::Client, "#http_connect" do
116
+ before(:each) do
117
+ @request = mas_net_http_get(:basic_auth => nil)
118
+ @good_response = mas_net_http_response(:success)
119
+ @bad_response = mas_net_http_response(:server_error)
120
+ @http_stubs = {:is_a? => true}
121
+ @block = Proc.new do |conn|
122
+ conn.is_a?(Net::HTTP).should be(true)
123
+ @has_yielded = true
124
+ @request
125
+ end
126
+ @twitter = client_context
127
+ @has_yielded = false
128
+ end
129
+
130
+ def generate_bad_response
131
+ @http = mas_net_http(@bad_response, @http_stubs)
132
+ Net::HTTP.stub!(:new).and_return(@http)
133
+ end
134
+
135
+ def generate_good_response
136
+ @http = mas_net_http(@good_response, @http_stubs)
137
+ Net::HTTP.stub!(:new).and_return(@http)
138
+ end
139
+
140
+ it "should yield HTTP connection when response is good" do
141
+ generate_good_response
142
+ @http.should_receive(:is_a?).with(Net::HTTP).and_return(true)
143
+ lambda do
144
+ @twitter.send(:http_connect, &@block)
145
+ end.should_not raise_error
146
+ @has_yielded.should be(true)
147
+ end
148
+
149
+ it "should yield HTTP connection when response is bad" do
150
+ generate_bad_response
151
+ @http.should_receive(:is_a?).with(Net::HTTP).and_return(true)
152
+ lambda {
153
+ @twitter.send(:http_connect, &@block)
154
+ }.should raise_error(Twitter::RESTError)
155
+ @has_yielded.should be(true)
156
+ end
157
+
158
+ after(:each) do
159
+ nilize(@good_response, @bad_response, @http)
160
+ end
161
+ end
162
+
163
+ describe Twitter::Client, "#bless_model" do
164
+ before(:each) do
165
+ @twitter = client_context
166
+ @model = Twitter::User.new
167
+ end
168
+
169
+ it "should recieve #client= message on given model to self" do
170
+ @model.should_receive(:client=).with(@twitter)
171
+ model = @twitter.send(:bless_model, @model)
172
+ end
173
+
174
+ it "should set client attribute on given model to self" do
175
+ model = @twitter.send(:bless_model, @model)
176
+ model.client.should eql(@twitter)
177
+ end
178
+
179
+ # if model is nil, it doesn't not necessarily signify an exceptional case for this method's usage.
180
+ it "should return nil when receiving nil and not raise any exceptions" do
181
+ model = @twitter.send(:bless_model, nil)
182
+ model.should be_nil
183
+ end
184
+
185
+ # needed to alert developer that the model needs to respond to #client= messages appropriately.
186
+ it "should raise an error if passing in a non-nil object that doesn't not respond to the :client= message" do
187
+ lambda {
188
+ @twitter.send(:bless_model, Object.new)
189
+ }.should raise_error(NoMethodError)
190
+ end
191
+
192
+ after(:each) do
193
+ nilize(@twitter)
194
+ end
195
+ end
196
+
197
+ describe Twitter::Client, "#bless_models" do
198
+ before(:each) do
199
+ @twitter = client_context
200
+ @models = [
201
+ Twitter::Status.new(:text => 'message #1'),
202
+ Twitter::Status.new(:text => 'message #2'),
203
+ ]
204
+ end
205
+
206
+ it "should set client attributes for each model in given Array to self" do
207
+ models = @twitter.send(:bless_models, @models)
208
+ models.each {|model| model.client.should eql(@twitter) }
209
+ end
210
+
211
+ it "should set client attribute for singular model given to self" do
212
+ model = @twitter.send(:bless_models, @models[0])
213
+ model.client.should eql(@twitter)
214
+ end
215
+
216
+ it "should delegate to bless_model for singular model case" do
217
+ model = @models[0]
218
+ @twitter.should_receive(:bless_model).with(model).and_return(model)
219
+ @twitter.send(:bless_models, model)
220
+ end
221
+
222
+ it "should return nil when receiving nil and not raise any exceptions" do
223
+ lambda {
224
+ value = @twitter.send(:bless_models, nil)
225
+ value.should be_nil
226
+ }.should_not raise_error
227
+ end
228
+
229
+ after(:each) do
230
+ nilize(@twitter, @models)
231
+ end
232
+ end