contextio 1.5.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 028f4ed25415c6d3512c35374b324199b18131e6
4
- data.tar.gz: 4369526ee522539a85b89ea065523c1b7f0ce4a4
3
+ metadata.gz: f3726a18eda9fc6ebe4ca26a055cefc0ab8c21c6
4
+ data.tar.gz: 70f0f22c3c52460d4e81648f2839c05e5f88514f
5
5
  SHA512:
6
- metadata.gz: edce3fb7fe4a13c6e1388cc41e2f0369de581553cc3453ca84b2b239693ca4066fa06d8a7edb216cfee24d0614af509c1c47af22c9fe224b5406732e30776620
7
- data.tar.gz: 5a024a9a50af47dec49a6dd032e288032af1b1dcd06e5f28e9b2d863e2c15785550d148a2b7fbd87ebad856568ee065e5cb78acaedfe622ceebbbfc5bfdbe487
6
+ metadata.gz: 2651ef5e3d6ea6276eaaebf33761120b9e68b42be0a35e8a2cdbe5d8b04481bd1dd55b857b65ec265f601c48c07d3004b4d66e346401553b0b202c22fc57cee5
7
+ data.tar.gz: 6258631b9b65b5b624fbb545d8175b768997f84b10268fa05bc31617b2bba921f486934ae469bdb537874f273d27069a79a01c169379a403e1218671b894c0fa
data/CHANGES.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changes
2
2
 
3
+ ## 1.6.0
4
+
5
+ * Add `version` and `base_url` instance variables to API. - Dominik Gehl
6
+ * Don't try to JSON parse raw attachments. - Dominik Gehl
7
+ * Use symbols for options internally to avoid OAuth gem failure. - Ben Hamill
8
+ * Add `in_reply_to` to `Message`'s lazy attributes. - Asa Wilson
9
+ * Fix bug where `Hash`es with mixed `String`/`Symbol` keys would cause the OAuth
10
+ gem to explode. - Ben Hamill
11
+ * Add missing files association to `Message` class. - Ben Hamill
12
+ * Add a ton of examples to the README to help new users. - Johnny Goodman
13
+
3
14
  ## 1.5.0
4
15
 
5
16
  * Make `Source#sync!` and `Account#sync!` take an options hash that will be
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  * [Homepage](https://github.com/contextio/contextio-ruby#readme)
4
4
  * [API Documentation](http://context.io/docs/2.0/)
5
5
  * [Gem Documentation](http://rubydoc.info/gems/contextio/frames)
6
+ * [API Explorer](https://console.context.io/#explore)
6
7
  * [Sign Up](http://context.io)
7
8
 
8
9
 
@@ -11,8 +12,17 @@
11
12
  Provides a Ruby interface to [Context.IO](http://context.io). The general design
12
13
  was inspired by the wonderful [aws-sdk](https://github.com/aws/aws-sdk-ruby)
13
14
  gem. You start with an object that represents your account with Context.IO and
14
- then you deal with collections within that going forward (see the [Usage
15
- section](#usage)).
15
+ then you deal with collections within that going forward (see the [Quick Start
16
+ section](#quick start)).
17
+
18
+
19
+ ## Install
20
+
21
+ $ gem install contextio
22
+
23
+ Or, of course, put this in your Gemfile:
24
+
25
+ gem 'contextio'
16
26
 
17
27
 
18
28
  ## A Note On Method Names
@@ -28,15 +38,6 @@ docs](http://rubydoc.info/gems/contextio/frames) for a specific class when in
28
38
  doubt.
29
39
 
30
40
 
31
- ## Install
32
-
33
- $ gem install contextio
34
-
35
- Or, of course, put this in your Gemfile:
36
-
37
- gem 'contextio'
38
-
39
-
40
41
  ## Version Numbers
41
42
 
42
43
  This gem adheres to [SemVer](http://semver.org/). So you should be pretty safe
@@ -45,7 +46,11 @@ bump. When the major version bumps, be warned; upgrading will take some kind of
45
46
  effort.
46
47
 
47
48
 
48
- ## Usage
49
+ ## Examples
50
+
51
+ ### Quick Start
52
+
53
+ Print the subject of the first five messages in the some@email.com account.
49
54
 
50
55
  ```ruby
51
56
  require 'contextio'
@@ -54,29 +59,254 @@ contextio = ContextIO.new('your_api_key', 'your_api_secret')
54
59
 
55
60
  account = contextio.accounts.where(email: 'some@email.com').first
56
61
 
57
- account.email_addresses # ['some@email.com', 'another@email.com']
58
- account.first_name # 'Bruno'
59
- account.suspended? # False
60
-
61
- account.messages.where(folder: '\Drafts').each do |m|
62
- puts m.subject
62
+ account.messages.where(limit: 5).each do |message|
63
+ puts message.subject
63
64
  end
64
65
  ```
65
66
 
67
+
68
+ ### Primary Key Queries
69
+
66
70
  To grab some object you already know the primary key for, you'll use the `[]`
67
- method like you would for a `Hash` or `Array`. This is most helpful for
68
- accounts, but works for any resource collection.
71
+ method like you would for a `Hash` or `Array`.
72
+
73
+ This is most helpful for accounts, but works for any resource collection.
69
74
 
70
75
  ```ruby
71
76
  require 'contextio'
72
77
 
73
78
  contextio = ContextIO.new('your_api_key', 'your_api_secret')
74
79
 
80
+ some_account_id = "exampleaccountid12345678"
81
+
75
82
  account = contextio.accounts[some_account_id]
76
83
  message = account.messages[some_message_id]
77
84
  ```
78
85
 
79
86
 
87
+ #### Message By Key
88
+
89
+ ```ruby
90
+ message = account.messages[some_message_id]
91
+
92
+ message.message_id #=> "examplemessageid12345678"
93
+ message.subject #=> "My Email's Subject"
94
+ message.from.class #=> Hash
95
+ message.from['email'] #=> "some@email.com"
96
+ message.from['name'] #=> "John Doe"
97
+
98
+ message.delete #=> true
99
+ ```
100
+
101
+ Body content must be accessed through each body part (eg: html, text, etc.).
102
+ Context.io does not store body content and so each call will source it
103
+ directly from the mail box associated with the account. This will be slow
104
+ relative to the context.io API.
105
+
106
+ You can specify and receive a single body content type.
107
+
108
+ ```ruby
109
+ message = account.messages[some_message_id]
110
+
111
+ message_part = message.body_parts.where(type: 'text/plain').first
112
+
113
+ message_part.class #=> ContextIO::BodyPart
114
+ message_part.type #=> "text/plain"
115
+ message_part.content #=> "body content of text/plain body_part"
116
+ ```
117
+
118
+ You can determine how many body parts are available and iterate over each one.
119
+
120
+ ```ruby
121
+ message = account.messages[some_message_id]
122
+
123
+ message.body_parts.class #=> ContextIO::BodyPartCollection
124
+ message.body_parts.count #=> 2
125
+
126
+ message.body_parts.each do |part|
127
+ puts part.class #=> ContextIO::BodyPart
128
+ puts part.type #=> "text/html"
129
+ puts part.content #=> "body content of text/html body_part"
130
+ end
131
+ ```
132
+
133
+
134
+ #### Account By Key
135
+
136
+ You can specify an account id and get back information about that account.
137
+
138
+ ```ruby
139
+ account = contextio.accounts[some_account_id]
140
+
141
+ account.email_addresses #=> ['some@email.com', 'another@email.com']
142
+ account.id #=> "exampleaccountid12345678"
143
+ account.first_name #=> "Bruno"
144
+ account.suspended? #=> False
145
+ ```
146
+
147
+
148
+ ### Message Collections
149
+
150
+ #### Query Basics
151
+
152
+ Queries to Messages return ContextIO::MessageCollection objects which can be
153
+ iterated on.
154
+
155
+ The `where` method allows you to refine search results based on
156
+ [available filters](http://context.io/docs/2.0/accounts/messages#get).
157
+
158
+ ```ruby
159
+ #the 25 most recent messages by default, you can specify a higher limit
160
+ account.messages
161
+
162
+ #the 50 most recent messages
163
+ account.messages.where(limit: 50)
164
+
165
+ #recent messages sent to the account by some@email.com
166
+ account.messages.where(from: 'some@email.com')
167
+
168
+ #multiple parameters accepted in hash format
169
+ account.messages.where(from: 'some@email.com', subject: 'hello')
170
+
171
+ #regexp accepted as a string like '/regexp/'
172
+ account.messages.where(from: 'some@email.com', subject: '/h.llo/')
173
+
174
+ #regexp options are supported, the /i case insensitive is often useful
175
+ account.messages.where(from: 'some@email.com', subject: '/h.llo/i')
176
+ ```
177
+
178
+
179
+ #### Querying Dates
180
+
181
+ Pass dates in to message queries as Unix Epoch integers.
182
+
183
+ ```ruby
184
+ require 'active_support/all'
185
+
186
+ account.messages.where(date_before: 3.hours.ago.to_i, date_after: 5.hours.ago.to_i).each do |message|
187
+ puts "(#{message.date}) #{message.subject}"
188
+ end
189
+ ```
190
+
191
+ You can mix date and non-date parameters.
192
+
193
+ ```ruby
194
+ account.messages.where(
195
+ date_before: 3.days.ago.to_i,
196
+ date_after: 4.weeks.ago.to_i,
197
+ subject: 'subject of email',
198
+ from: 'foo@email.com'
199
+ )
200
+ ```
201
+
202
+
203
+ ### Individual Messages
204
+
205
+ #### Message Basics
206
+
207
+ ```ruby
208
+ account.messages.where(limit: 1).class # ContextIO::MessageCollection
209
+
210
+ message = account.messages.where(limit: 1).first
211
+
212
+ message.class #=> ContextIO::Message
213
+ message.subject #=> "subject of message"
214
+ message.from #=> {"email"=>"some@email.com", "name"=>"John Doe"}
215
+ message.to #=> [{"email"=>"some@email.com", "name"=>"'John Doe'"}, {"email"=>"another@email.com", "name"=>"Jeff Mangum"}]
216
+ ```
217
+
218
+ #### Message Dates
219
+
220
+ ##### received_at
221
+
222
+ `received_at` is the time when your IMAP account records the message
223
+ having arrived. It is returned as a Time object.
224
+
225
+ ```ruby
226
+ m.received_at #=> 2013-04-19 08:12:04 -0500
227
+ m.received_at.class #=> Time
228
+ ```
229
+
230
+ ##### indexed_at
231
+
232
+ `indexed_at` is the time when the message was processed and indexed
233
+ by the Context.io sync process.
234
+
235
+ ```ruby
236
+ m.indexed_at #=> 2013-04-29 01:14:39 -0500
237
+ m.received_at.class #=> Time
238
+ ```
239
+
240
+ ##### date
241
+
242
+ A message's date is set by the sender, extracted from the message's Date
243
+ header and is returned as a FixNum which can be converted into a Time
244
+ object.
245
+
246
+ ```ruby
247
+ message.date #=> 1361828599
248
+ Time.at(message.date) #=> 2013-04-19 08:11:33 -0500
249
+ Time.at(message.date).class #=> Time
250
+ ```
251
+
252
+ `message.date` is not reliable as it is easily spoofed. While it is made
253
+ available, `received_at` and `indexed_at` are better choices.
254
+
255
+
256
+ #### Messages with Body Data
257
+
258
+ By default, Context.io's API does not return message queries with body data.
259
+
260
+ You can include the body attribute in each individual message returned by
261
+ adding `include_body: 1` to your `messages.where` query options.
262
+
263
+ ```ruby
264
+ account.messages.where(include_body: 1, limit: 1).each do |message|
265
+ puts "#{message.subject} #{message.date} #{message.body[0]['content']}"
266
+ end
267
+ ```
268
+
269
+ Emails that contain two or more body parts are called [multipart messages](https://en.wikipedia.org/wiki/MIME#Alternative).
270
+
271
+ 'text/plain' and 'text/html' are common body part types for multipart
272
+ messages.
273
+
274
+ In the case a user is viewing email in a client that does not support HTML
275
+ markup, the 'text/plain' body part type will render instead.
276
+
277
+ If you are working with multipart messages, you may want to check each
278
+ body part's content in turn.
279
+
280
+ ```ruby
281
+ account.messages.where(include_body: 1, limit: 1).each do |message|
282
+ message.body_parts.each do |body_part|
283
+ puts body_part.content
284
+ end
285
+ end
286
+ ```
287
+
288
+ The `include_body` method queries the source IMAP box directly, which results
289
+ in slower return times.
290
+
291
+
292
+ ### Files
293
+
294
+ #### Files Per Message ID
295
+
296
+ ```ruby
297
+ message = account.messages[message_id]
298
+
299
+ message.files.class #=> ContextIO::FileCollection
300
+ message.files.count #=> 2
301
+ message.files.map { |f| f.file_name } #=> ["at_icon.png", "argyle_slides.png"]
302
+ message.files.first.resource_url #=> https://contextio_to_s3_redirect_url.io
303
+ ```
304
+
305
+ The file['resource_url'] url is a S3 backed temporary link. It is intended
306
+ to be used promptly after being called. Do not store off this link. Instead,
307
+ store off the message_id and request on demand.
308
+
309
+
80
310
  ### On Laziness
81
311
 
82
312
  This gem is architected to be as lazy as possible. It won't make an HTTP request
@@ -88,7 +318,7 @@ require 'contextio'
88
318
 
89
319
  contextio = ContextIO.new('your_api_key', 'your_api_secret')
90
320
 
91
- account = contextio.accounts['1234'] # No request made here.
321
+ account = contextio.accounts['exampleaccountid12345678'] # No request made here.
92
322
  account.last_name # Request made here.
93
323
  account.first_name # No request made here.
94
324
  ```
@@ -101,8 +331,8 @@ docs](http://rubydoc.info/gems/contextio/frames) will trigger the request.
101
331
 
102
332
  ### On Requests and Methods
103
333
 
104
- There are some consistent mappings between the requests documented in the [API
105
- docs](http://context.io/docs/2.0/) and the methods implemented in the gem.
334
+ There are some consistent mappings between the requests documented in the
335
+ [API docs](http://context.io/docs/2.0/) and the methods implemented in the gem.
106
336
 
107
337
  **For collections of resources:**
108
338
 
@@ -143,6 +373,6 @@ failing test can be helpful. If in doubt, make an Issue to discuss.
143
373
 
144
374
  ## Copyright
145
375
 
146
- Copyright (c) 2012 Context.IO
376
+ Copyright (c) 2012-2013 Context.IO
147
377
 
148
378
  This gem is distributed under the MIT License. See LICENSE.md for details.
@@ -21,7 +21,7 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_development_dependency 'bundler'
23
23
  gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
24
- gem.add_development_dependency 'rspec', '~> 2.4'
24
+ gem.add_development_dependency 'rspec', '~> 2.14'
25
25
  gem.add_development_dependency 'rake'
26
26
  gem.add_development_dependency 'yard'
27
27
  gem.add_development_dependency 'redcarpet'
@@ -52,6 +52,8 @@ class ContextIO
52
52
  self.class.user_agent_string
53
53
  end
54
54
 
55
+ attr_accessor :base_url, :version
56
+
55
57
  # @!attribute [r] key
56
58
  # @return [String] The OAuth key for the user's Context.IO account.
57
59
  # @!attribute [r] secret
@@ -67,6 +69,8 @@ class ContextIO
67
69
  @key = key
68
70
  @secret = secret
69
71
  @opts = opts || {}
72
+ @base_url = self.class.base_url
73
+ @version = self.class.version
70
74
  end
71
75
 
72
76
  # Generates the path for a resource_path and params hash for use with the API.
@@ -76,7 +80,7 @@ class ContextIO
76
80
  # @param [{String, Symbol => String, Symbol, Array<String, Symbol>}] params
77
81
  # A Hash of the query parameters for the action represented by this path.
78
82
  def path(resource_path, params = {})
79
- "/#{API.version}/#{API.strip_resource_path(resource_path)}#{API.hash_to_url_params(params)}"
83
+ "/#{version}/#{strip_resource_path(resource_path)}#{API.hash_to_url_params(params)}"
80
84
  end
81
85
 
82
86
  # Makes a request against the Context.IO API.
@@ -129,15 +133,19 @@ class ContextIO
129
133
  # @return [Net::HTTP*] The response object from the request.
130
134
  def oauth_request(method, resource_path, params, headers=nil)
131
135
  headers ||= { 'Accept' => 'application/json', 'User-Agent' => user_agent_string }
136
+ normalized_params = params.inject({}) do |normalized_params, (key, value)|
137
+ normalized_params[key.to_sym] = value
138
+ normalized_params
139
+ end
132
140
 
133
141
  # The below array used to include put, too, but there is a weirdness in
134
142
  # the oauth gem with PUT and signing requests. See
135
143
  # https://github.com/oauth/oauth-ruby/pull/34#issuecomment-5862199 for
136
144
  # some discussion on the matter. This is a work-around.
137
145
  if %w(post).include? method.to_s.downcase
138
- token.request(method, path(resource_path), params, headers)
146
+ token.request(method, path(resource_path), normalized_params, headers)
139
147
  else # GET, DELETE, HEAD, etc.
140
- token.request(method, path(resource_path, params), nil, headers)
148
+ token.request(method, path(resource_path, normalized_params), nil, headers)
141
149
  end
142
150
  end
143
151
 
@@ -147,7 +155,7 @@ class ContextIO
147
155
  # @param [#to_s] resource_path The full URL or path for a resource.
148
156
  #
149
157
  # @return [String] The resource path.
150
- def self.strip_resource_path(resource_path)
158
+ def strip_resource_path(resource_path)
151
159
  resource_path.to_s.gsub("#{base_url}/#{version}/", '')
152
160
  end
153
161
 
@@ -175,7 +183,7 @@ class ContextIO
175
183
  # @return [OAuth::Consumer] An Oauth consumer object for credentials
176
184
  # purposes.
177
185
  def consumer
178
- @consumer ||= OAuth::Consumer.new(key, secret, @opts.merge(site: API.base_url))
186
+ @consumer ||= OAuth::Consumer.new(key, secret, @opts.merge(site: base_url))
179
187
  end
180
188
 
181
189
  # @!attribute [r] token
@@ -48,7 +48,7 @@ class ContextIO
48
48
  end
49
49
 
50
50
  def content
51
- @content ||= api.request(:get, "#{resource_url}/content")
51
+ @content ||= api.raw_request(:get, "#{resource_url}/content")
52
52
  end
53
53
 
54
54
  def content_link