tms_client 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,2 +1,11 @@
1
1
  source "https://rubygems.org"
2
- gemspec
2
+ gemspec
3
+
4
+ group :development do
5
+ gem 'rspec'
6
+ gem 'rake'
7
+ gem 'redcarpet'
8
+ gem 'yard'
9
+ gem 'guard-rspec'
10
+ gem 'rubygems-tasks', :git => 'https://github.com/postmodern/rubygems-tasks.git'
11
+ end
data/README.md CHANGED
@@ -1,94 +1,135 @@
1
1
  TMS Client
2
2
  ===========
3
- This is a ruby client to interact with the GovDelivery TMS REST API.
3
+ This is a reference Ruby client to interact with the GovDelivery TMS REST API.
4
4
 
5
5
  Installation
6
6
  ------------
7
7
  ### Using Bundler
8
- ``` ruby
8
+
9
+ ```ruby
9
10
  gem 'tms_client'
10
11
  ```
11
12
 
12
13
  ### Standalone
14
+
13
15
  ```
14
16
  $ gem install tms_client
15
17
  ```
16
18
 
17
19
 
18
- Usage
19
- -----
20
- ### Connecting
21
-
22
- ``` ruby
23
- client = TMS::Client.new('username', 'password', :api_root => 'https://tms.govdelivery.com')
20
+ Connecting
21
+ ----------
22
+ Loading an instance of `TMS::Client` will automatically connect to the API to query the available resources for your account.
24
23
 
24
+ ```ruby
25
+ client = TMS::Client.new('username', 'password', :api_root => 'https://stage-tms.govdelivery.com')
25
26
  ```
26
27
 
27
- ### Getting messages
28
+ Messages
29
+ --------
28
30
 
29
- ``` ruby
30
- client.subresources #=> {"messages"=><TMS::Messages href=/messages collection=[]>}
31
- client.messages #=> <TMS::Messages href=/messages collection=[]>
32
- client.sms_messages.get #=> #lots of sms stuff
33
- client.sms_messages.next #=> <TMS::Messages href=/messages/page/2 collection=[]>
34
- # (if there is a second page)
35
- client.sms_messages.next.get #=> # more messages...
36
- client.voice_messages.get #=> #lots of voice stuff
37
- client.voice_messages.next #=> <TMS::Messages href=/messages/page/2 collection=[]>
38
- # (if there is a second page)
39
- client.voice_messages.next.get #=> # more messages...
31
+ ### Loading messages
32
+ Sms, Email, and voice messages can be retrieved with the `get` collection method. Messages are paged in groups of 50. To retrieve another page, used the `next` method. This method will not be defined if another page is not available.
33
+
34
+ ```ruby
35
+ client.sms_messages.get # get the first page of sms messages
36
+ client.sms_messages.next.get # get the next page of sms messages
40
37
  ```
41
38
 
42
39
 
43
40
  ### Sending an SMS Message
44
41
 
45
- ``` ruby
46
- message = client.sms_messages.build(:short_body=>'Test Message!')
42
+ ```ruby
43
+ message = client.sms_messages.build(:body=>'Test Message!')
47
44
  message.recipients.build(:phone=>'5551112222')
48
45
  message.recipients.build(:phone=>'5551112223')
49
46
  message.recipients.build # invalid - no phone
50
- message.post #=> true
51
- message.recipients.collection.detect{|r| r.errors } #=> {"phone"=>["is not a number"]}
47
+ message.post # true
48
+ message.recipients.collection.detect{|r| r.errors } # {"phone"=>["is not a number"]}
52
49
  # save succeeded, but we have one bad recipient
53
- message.href #=> "/messages/87"
54
- message.get #=> <TMS::Message href=/messages/87 attributes={...}>
50
+ message.href # "/messages/sms/87"
51
+ message.get # <TMS::SmsMessage href=/messages/sms/87 attributes={...}>
55
52
  ```
56
53
 
57
- ### Sending an Voice Message
54
+ ### Sending Email
58
55
 
59
- ``` ruby
60
- message = client.voice_messages.build(:url=>'www.testmessage.com')
56
+ ```ruby
57
+ message = client.email_messages.build(:body=>'<p><a href="http://example.com">Visit here</a>', :subject => 'Hey')
58
+ message.recipients.build(:email=>'example1@example.com')
59
+ message.recipients.build(:email=>'example2@example.com')
60
+ message.post # true
61
+ message.recipients.collection.detect{|r| r.errors } # {"phone"=>["is not a number"]}
62
+ # save succeeded, but we have one bad recipient
63
+ message.href # "/messages/email/87"
64
+ message.get # <TMS::EmailMessage href=/messages/email/88 attributes={...}>
65
+ ```
66
+
67
+ ### Sending a Voice Message
68
+
69
+ ```ruby
70
+ message = client.voice_messages.build(:play_url=>'www.testmessage.com')
61
71
  message.recipients.build(:phone=>'5551112222')
62
72
  message.recipients.build(:phone=>'5551112223')
63
73
  message.recipients.build # invalid - no phone
64
- message.post #=> true
65
- message.recipients.collection.detect{|r| r.errors } #=> {"phone"=>["is not a number"]}
74
+ message.post # true
75
+ message.recipients.collection.detect{|r| r.errors } # {"phone"=>["is not a number"]}
66
76
  # save succeeded, but we have one bad recipient
67
- message.href #=> "/messages/87"
68
- message.get #=> <TMS::Message href=/messages/87 attributes={...}>
77
+ message.href # "/messages/voice/87"
78
+ message.get # <TMS::VoiceMessage href=/messages/voice/87 attributes={...}>
79
+ ```
80
+
81
+ Metrics
82
+ -------
83
+ ### Viewing recipients that clicked on a link in an email
84
+
85
+ ```ruby
86
+ email_message.get
87
+ email_message.clicked.get
88
+ email_message.clicked.collection # => [<#EmailRecipient>,...]
69
89
  ```
70
90
 
91
+ ### Viewing recipients that opened an email
92
+
93
+ ```ruby
94
+ email_message.get
95
+ email_message.opened.get
96
+ email_message.opened.collection # => [<#EmailRecipient>,...]
97
+ ```
98
+
99
+ ### Viewing a list of statistics for a recipient
100
+
101
+ ```ruby
102
+ email_recipient.clicks.get.collection #=> [<#EmailRecipientClick>,...]
103
+
104
+ email_recipient.opens.get.collection #=> [<#EmailRecipientOpen>,...]
105
+ ```
106
+
107
+ Configuring 2-way SMS
108
+ ---------------------
109
+
71
110
  ### Listing Command Types
111
+ Command Types are the available commands that can be used to respond to an incoming SMS message.
72
112
 
73
- ``` ruby
113
+ ```ruby
74
114
  command_types = client.command_types.get
75
115
  command_types.collection.each do |at|
76
- puts at.name #=> "forward"
77
- puts at.fields #=> ["url", "http_method", ...]
116
+ puts at.name # "forward"
117
+ puts at.fields # ["url", "http_method", ...]
78
118
  end
79
- ````
119
+ ```
80
120
 
81
121
  ### Managing Keywords
122
+ Keywords are chunks of text that are used to match an incoming SMS message.
82
123
 
83
- ``` ruby
124
+ ```ruby
84
125
  # CRUD
85
126
  keyword = client.keywords.build(:name => "BUSRIDE")
86
- keyword.post #=> true
87
- keyword.name #=> 'busride'
127
+ keyword.post # true
128
+ keyword.name # 'busride'
88
129
  keyword.name = "TRAINRIDE"
89
- keyword.put #=> true
90
- keyword.name #=> 'trainride'
91
- keyword.delete #=> true
130
+ keyword.put # true
131
+ keyword.name # 'trainride'
132
+ keyword.delete # true
92
133
 
93
134
  # list
94
135
  keywords = client.keywords.get
@@ -98,11 +139,12 @@ end
98
139
  ```
99
140
 
100
141
  ### Managing Commands
142
+ Commands have a command type and one or more keywords. The example below configures the system to respond to an incoming SMS message containing the string "RIDE" (or "ride") by forwarding an http POST to `http://example.com/new_url`. The POST body variables are documented in GovDelivery's [TMS REST API documentation](https://govdelivery.atlassian.net/wiki/display/PM/TMS+Customer+API+Documentation#TMSCustomerAPIDocumentation-Configuring2-waySMS "GovDelivery TMS REST API").
101
143
 
102
144
  ```ruby
103
145
  # CRUD
104
- keywords = client.keywords.get
105
- keyword = keywords.collection.first.get
146
+ keyword = client.keywords.build(:name => "RIDE")
147
+ keyword.post
106
148
  command = keyword.commands.build(
107
149
  :name => "Forward to somewhere else",
108
150
  :params => {:url => "http://example.com", :http_method => "get"},
@@ -120,21 +162,33 @@ end
120
162
  ```
121
163
 
122
164
 
123
- ### Logging
124
- Any instance of a [Logger](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html "Ruby Logger")-like class can be passed in to the client; incoming and outgoing
125
- request information will then be logged to that instance.
165
+ Logging
166
+ -------
167
+
168
+ Any instance of a [Logger](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html "Ruby Logger")-like class can be passed in to the client; incoming and outgoing request information will then be logged to that instance.
126
169
 
127
- The example below configures `TMS::Client` to log to STDOUT:
170
+ The example below configures `TMS::Client` to log to `STDOUT`:
128
171
 
129
- ``` ruby
172
+ ```ruby
130
173
  logger = Logger.new(STDOUT)
131
174
  client = TMS::Client.new('username', 'password', :logger => logger)
175
+ ```
132
176
 
177
+ Generating Documentation
178
+ ------------------------
179
+ This project uses [yard](https://github.com/lsegal/yard) to generate documentation. To generate API documentation yourself, use the following series of commands from the project root:
180
+
181
+ ```ruby
182
+ # install development gems
183
+ bundle install
184
+ # generate documentation
185
+ rake yard
133
186
  ```
187
+ The generated documentation will be placed in the `doc` folder.
134
188
 
135
189
  Compatibility
136
190
  -------------
137
- This project is tested and compatible with REE 1.8.7 and MRI 1.9.3.
191
+ This project is tested and compatible with REE 1.8.7, MRI 1.9.3, and MRI 2.0.0.
138
192
 
139
193
  License
140
194
  -------
data/Rakefile CHANGED
@@ -1,6 +1,11 @@
1
1
  require 'rspec/core/rake_task'
2
2
  require 'rubygems/tasks'
3
3
  require 'rubygems/tasks/scm'
4
+ require 'yard'
5
+
6
+ YARD::Rake::YardocTask.new do |t|
7
+ t.files = ['lib/**/*.rb']
8
+ end
4
9
 
5
10
  RSpec::Core::RakeTask.new(:spec)
6
11
  Gem::Tasks.new
@@ -8,7 +8,7 @@ module TMS #:nodoc:
8
8
  base.send(:extend, TMS::CoreExt)
9
9
  end
10
10
 
11
- attr_accessor :client, :href, :errors
11
+ attr_accessor :client, :href, :errors, :new_record
12
12
 
13
13
  module ClassMethods
14
14
  def to_param
@@ -21,6 +21,15 @@ module TMS #:nodoc:
21
21
  self.client = client
22
22
  self.href = href
23
23
  end
24
+
25
+ def new_record?
26
+ !!self.new_record
27
+ end
28
+
29
+ def href=(href)
30
+ self.new_record=false
31
+ @href=href
32
+ end
24
33
  end
25
34
 
26
35
  end
@@ -1,11 +1,11 @@
1
- # The client class to connect and talk to the TMS REST API.
1
+ # The client class to connect and talk to the TMS REST API.
2
2
  class TMS::Client
3
3
  include TMS::Util::HalLinkParser
4
4
  include TMS::CoreExt
5
5
 
6
6
  attr_accessor :connection, :href
7
7
 
8
- # Create a new client and issue a request for the available resources for a given account.
8
+ # Create a new client and issue a request for the available resources for a given account.
9
9
  #
10
10
  # === Options
11
11
  # * +:api_root+ - The root URL of the TMS api. Defaults to localhost:3000
@@ -13,9 +13,9 @@ class TMS::Client
13
13
  #
14
14
  # === Examples
15
15
  # client = TMS::Client.new("foo@example.com", "onetwothree", {
16
- # :api_root => "https://tms.govdelivery.com",
16
+ # :api_root => "https://tms.govdelivery.com",
17
17
  # :logger => Logger.new(STDOUT)})
18
- #
18
+ #
19
19
  def initialize(username, password, options = {:api_root => 'http://localhost:3000', :logger => nil})
20
20
  @api_root = options[:api_root]
21
21
  connect!(username, password, options[:logger])
@@ -34,12 +34,12 @@ class TMS::Client
34
34
  def get(href)
35
35
  response = raw_connection.get(href)
36
36
  case response.status
37
- when 401..499
38
- raise TMS::Request::Error.new(response.status)
39
- when 202
40
- raise TMS::Request::InProgress.new(response.body['message'])
41
- else
42
- return response
37
+ when 401..499
38
+ raise TMS::Request::Error.new(response.status)
39
+ when 202
40
+ raise TMS::Request::InProgress.new(response.body['message'])
41
+ else
42
+ return response
43
43
  end
44
44
  end
45
45
 
@@ -62,10 +62,10 @@ class TMS::Client
62
62
  def delete(href)
63
63
  response = raw_connection.delete(href)
64
64
  case response.status
65
- when 200
66
- return response
67
- else
68
- raise TMS::Request::Error.new(response.status)
65
+ when 200
66
+ return response
67
+ else
68
+ raise TMS::Request::Error.new(response.status)
69
69
  end
70
70
  end
71
71
 
@@ -77,4 +77,4 @@ class TMS::Client
77
77
  self
78
78
  end
79
79
 
80
- end
80
+ end
@@ -30,6 +30,7 @@ module TMS::CollectionResource
30
30
 
31
31
  def build(attributes=nil)
32
32
  thing = instance_class(self.class).new(client, self.href, attributes || {})
33
+ thing.new_record = true
33
34
  self.collection << thing
34
35
  thing
35
36
  end
@@ -50,4 +51,4 @@ module TMS::CollectionResource
50
51
  end
51
52
  end
52
53
  end
53
- end
54
+ end
@@ -0,0 +1,9 @@
1
+ module TMS
2
+ module Errors
3
+ class InvalidGet < StandardError
4
+ def initialize(message=nil)
5
+ super(message || "Can't GET a resource after an invalid POST; either create a new object or fix errors")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -88,15 +88,18 @@ module TMS::InstanceResource
88
88
  end
89
89
 
90
90
  def get
91
+ raise TMS::Errors::InvalidGet if self.new_record?
91
92
  set_attributes_from_hash(self.client.get(href).body)
92
93
  self
93
94
  end
94
95
 
95
96
  def post
96
97
  response = client.post(self)
98
+
97
99
  case response.status
98
- when 201
100
+ when 200..299
99
101
  set_attributes_from_hash(response.body)
102
+ self.new_record=false
100
103
  return true
101
104
  when 401
102
105
  raise Exception.new("401 Not Authorized")
@@ -114,6 +117,7 @@ module TMS::InstanceResource
114
117
  response = client.put(self)
115
118
  case response.status
116
119
  when 200
120
+ self.new_record=false
117
121
  set_attributes_from_hash(response.body)
118
122
  return true
119
123
  when 401
@@ -156,6 +160,12 @@ module TMS::InstanceResource
156
160
  json_hash
157
161
  end
158
162
 
163
+ protected
164
+
165
+ def relation_class(rel)
166
+ self.class.custom_class_names[rel.to_sym] || super
167
+ end
168
+
159
169
  private
160
170
 
161
171
  def set_attributes_from_hash(hash)
@@ -0,0 +1,223 @@
1
+ # link_header, Copyright (c) 2009 Mike Burrows
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require "strscan"
23
+
24
+ #
25
+ # Represents an HTTP link header of the form described in the draft spec http://tools.ietf.org/id/draft-nottingham-http-link-header-06.txt.
26
+ # It is simply a list of LinkHeader::Link objects and some conversion functions.
27
+ #
28
+ class LinkHeader
29
+
30
+ # An array of Link objects
31
+ attr_reader :links
32
+
33
+ #
34
+ # Initialize from a collection of either LinkHeader::Link objects or data from which Link objects can be created.
35
+ #
36
+ # From a list of LinkHeader::Link objects:
37
+ #
38
+ # LinkHeader.new([
39
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]),
40
+ # LinkHeader::Link.new("http://example.com/", [["rel", "up"]])])
41
+ #
42
+ # From the equivalent JSON-friendly raw data:
43
+ #
44
+ # LinkHeader.new([
45
+ # ["http://example.com/foo", [["rel", "self"]]],
46
+ # ["http://example.com/", [["rel", "up"]]]]).to_s
47
+ #
48
+ # See also LinkHeader.parse
49
+ #
50
+ def initialize(links=[])
51
+ if links
52
+ @links = links.map{|l| l.kind_of?(Link) ? l : Link.new(*l)}
53
+ else
54
+ @links = []
55
+ end
56
+ end
57
+
58
+ #
59
+ # Convert to a JSON-friendly array
60
+ #
61
+ # LinkHeader.parse('<http://example.com/foo>; rel="self", <http://example.com/>; rel = "up"').to_a
62
+ # #=> [["http://example.com/foo", [["rel", "self"]]],
63
+ # ["http://example.com/", [["rel", "up"]]]]
64
+ #
65
+ def to_a
66
+ links.map{|l| l.to_a}
67
+ end
68
+
69
+ #
70
+ # Convert to string representation as per the link header spec
71
+ #
72
+ # LinkHeader.new([
73
+ # ["http://example.com/foo", [["rel", "self"]]],
74
+ # ["http://example.com/", [["rel", "up"]]]]).to_s
75
+ # #=> '<http://example.com/foo>; rel="self", <http://example.com/>; rel = "up"'
76
+ #
77
+ def to_s
78
+ links.join(', ')
79
+ end
80
+
81
+ #
82
+ # Regexes for link header parsing. TOKEN and QUOTED in particular should conform to RFC2616.
83
+ #
84
+ # Acknowledgement: The QUOTED regexp is based on
85
+ # http://stackoverflow.com/questions/249791/regexp-for-quoted-string-with-escaping-quotes/249937#249937
86
+ #
87
+ HREF = / *< *([^>]*) *> *;? */ #:nodoc: note: no attempt to check URI validity
88
+ TOKEN = /([^()<>@,;:\"\[\]?={}\s]+)/ #:nodoc: non-empty sequence of non-separator characters
89
+ QUOTED = /"((?:[^"\\]|\\.)*)"/ #:nodoc: double-quoted strings with backslash-escaped double quotes
90
+ ATTR = /#{TOKEN} *= *(#{TOKEN}|#{QUOTED}) */ #:nodoc:
91
+ SEMI = /; */ #:nodoc:
92
+ COMMA = /, */ #:nodoc:
93
+
94
+ #
95
+ # Parse a link header, returning a new LinkHeader object
96
+ #
97
+ # LinkHeader.parse('<http://example.com/foo>; rel="self", <http://example.com/>; rel = "up"').to_a
98
+ # #=> [["http://example.com/foo", [["rel", "self"]]],
99
+ # ["http://example.com/", [["rel", "up"]]]]
100
+ #
101
+ def self.parse(link_header)
102
+ return new unless link_header
103
+
104
+ scanner = StringScanner.new(link_header)
105
+ links = []
106
+ while scanner.scan(HREF)
107
+ href = scanner[1]
108
+ attrs = []
109
+ while scanner.scan(ATTR)
110
+ attr_name, token, quoted = scanner[1], scanner[3], scanner[4].gsub(/\\"/, '"')
111
+ attrs.push([attr_name, token || quoted])
112
+ break unless scanner.scan(SEMI)
113
+ end
114
+ links.push(Link.new(href, attrs))
115
+ break unless scanner.scan(COMMA)
116
+ end
117
+
118
+ new(links)
119
+ end
120
+
121
+ #
122
+ # Find a member link that has the given attributes
123
+ #
124
+ def find_link(*attr_pairs)
125
+ links.detect do |link|
126
+ !attr_pairs.detect do |pair|
127
+ !link.attr_pairs.include?(pair)
128
+ end
129
+ end
130
+ end
131
+
132
+ #
133
+ # Render as a list of HTML link elements
134
+ #
135
+ def to_html(separator="\n")
136
+ links.map{|link| link.to_html}.join(separator)
137
+ end
138
+
139
+ #
140
+ # Represents a link - an href and a list of attributes (key value pairs)
141
+ #
142
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]).to_s
143
+ # => '<http://example.com/foo>; rel="self"'
144
+ #
145
+ class Link
146
+ #
147
+ # The link's URI string
148
+ #
149
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]).href
150
+ # => 'http://example.com/foo>'
151
+ #
152
+ attr_reader :href
153
+
154
+ #
155
+ # The link's attributes, an array of key-value pairs
156
+ #
157
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]).attr_pairs
158
+ # => [["rel", "self"], ["rel", "canonical"]]
159
+ #
160
+ attr_reader :attr_pairs
161
+
162
+ #
163
+ # Initialize a Link from an href and attribute list
164
+ #
165
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]).to_s
166
+ # => '<http://example.com/foo>; rel="self"'
167
+ #
168
+ def initialize(href, attr_pairs)
169
+ @href, @attr_pairs = href, attr_pairs
170
+ end
171
+
172
+ #
173
+ # Lazily convert the attribute list to a Hash
174
+ #
175
+ # Beware repeated attribute names (it's safer to use #attr_pairs if this is risk):
176
+ #
177
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]).attrs
178
+ # => {"rel" =>"canonical"}
179
+ #
180
+ def attrs
181
+ @attrs ||= Hash[*attr_pairs.flatten]
182
+ end
183
+
184
+ #
185
+ # Access #attrs by key
186
+ #
187
+ def [](key)
188
+ attrs[key]
189
+ end
190
+
191
+ #
192
+ # Convert to a JSON-friendly Array
193
+ #
194
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]).to_a
195
+ # => ["http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]]
196
+ #
197
+ def to_a
198
+ [href, attr_pairs]
199
+ end
200
+
201
+ #
202
+ # Convert to string representation as per the link header spec. This includes backspace-escaping doublequote characters in
203
+ # quoted attribute values.
204
+ #
205
+ # Convert to string representation as per the link header spec
206
+ #
207
+ # LinkHeader::Link.new(["http://example.com/foo", [["rel", "self"]]]).to_s
208
+ # #=> '<http://example.com/foo>; rel="self"'
209
+ #
210
+ def to_s
211
+ (["<#{href}>"] + attr_pairs.map{|k, v| "#{k}=\"#{v.gsub(/"/, '\"')}\""}).join('; ')
212
+ end
213
+
214
+ #
215
+ # Bonus! Render as an HTML link element
216
+ #
217
+ # LinkHeader::Link.new(["http://example.com/foo", [["rel", "self"]]]).to_html
218
+ # #=> '<link href="http://example.com/foo" rel="self">'
219
+ def to_html
220
+ ([%Q(<link href="#{href}")] + attr_pairs.map{|k, v| "#{k}=\"#{v.gsub(/"/, '\"')}\""}).join(' ')
221
+ end
222
+ end
223
+ end
@@ -10,7 +10,7 @@ class TMS::SmsMessages
10
10
  include TMS::CollectionResource
11
11
  end
12
12
 
13
- class TMS::Emails
13
+ class TMS::EmailMessages
14
14
  include TMS::CollectionResource
15
15
  end
16
16
 
@@ -22,6 +22,14 @@ class TMS::EmailRecipients
22
22
  include TMS::CollectionResource
23
23
  end
24
24
 
25
+ class TMS::EmailRecipientOpens
26
+ include TMS::CollectionResource
27
+ end
28
+
29
+ class TMS::EmailRecipientClicks
30
+ include TMS::CollectionResource
31
+ end
32
+
25
33
  # A collection of Keyword objects.
26
34
  #
27
35
  # === Example
@@ -47,3 +55,7 @@ end
47
55
  class TMS::CommandTypes
48
56
  include TMS::CollectionResource
49
57
  end
58
+
59
+ class TMS::Commands
60
+ include TMS::CollectionResource
61
+ end
@@ -17,7 +17,10 @@ module TMS #:nodoc:
17
17
  class Command
18
18
  include InstanceResource
19
19
 
20
+ # @!parse attr_accessor :name, :command_type, :params
20
21
  writeable_attributes :name, :command_type, :params
22
+
23
+ # @!parse attr_reader :created_at, :updated_at
21
24
  readonly_attributes :created_at, :updated_at
22
25
 
23
26
  end
@@ -14,6 +14,7 @@ module TMS #:nodoc:
14
14
 
15
15
  include InstanceResource
16
16
 
17
+ # @!parse attr_reader :fields, :name
17
18
  readonly_attributes :name, :fields
18
19
  end
19
20
  end
@@ -0,0 +1,52 @@
1
+ module TMS #:nodoc:
2
+ # An EmailMessage is used to create and send a email to a collection of EmailRecipient
3
+ # objects. Certain metrics are available after the email is sent, including
4
+ # the collection of recipients who clicked or opened the email.
5
+ #
6
+ #
7
+ # ==== Attributes
8
+ #
9
+ # * +from_name+ - The name of the person or entity sending the email.
10
+ # * +subject+ - The subject of the email
11
+ # * +body+ - The body of the email
12
+ #
13
+ #
14
+ # === Example
15
+ # Sending a message
16
+ # email_message = client.email_messages.build(:subject => "Great news!", :body => "You win! <a href='http://example.com/'>click here</a>.")
17
+ # email_message.recipients.build(:email => "john@example.com")
18
+ # email_message.recipients.build(:email => "jeff@example.com")
19
+ # email_message.post
20
+ # email_message.get
21
+ #
22
+ # Viewing recipients that clicked on a link in the email
23
+ # email_message.get
24
+ # email_message.clicked.get
25
+ # email_message.clicked.collection # => [<#EmailRecipient>,...]
26
+ #
27
+ # Viewing recipients that opened the email
28
+ # email_message.get
29
+ # email_message.opened.get
30
+ # email_message.opened.collection # => [<#EmailRecipient>,...]
31
+ class EmailMessage
32
+ include InstanceResource
33
+
34
+ # @!parse attr_accessor :body, :from_name, :subject
35
+ writeable_attributes :body, :from_name, :subject
36
+
37
+ # @!parse attr_reader :created_at, :status
38
+ readonly_attributes :created_at, :status
39
+
40
+ ##
41
+ # A CollectionResource of EmailRecipients on this email
42
+ collection_attribute :recipients, 'EmailRecipients'
43
+
44
+ ##
45
+ # A CollectionResource of EmailRecipients that opened this email
46
+ collection_attribute :opened, 'EmailRecipients'
47
+
48
+ ##
49
+ # A CollectionResource of EmailRecipients that clicked on at least one link in this email
50
+ collection_attribute :clicked, 'EmailRecipients'
51
+ end
52
+ end
@@ -2,6 +2,18 @@ module TMS #:nodoc:
2
2
  class EmailRecipient
3
3
  include InstanceResource
4
4
 
5
+ # @!parse attr_accessor :email
5
6
  writeable_attributes :email
7
+
8
+ # @!parse attr_reader :completed_at
9
+ readonly_attributes :completed_at
10
+
11
+ ##
12
+ # A CollectionResource of EmailRecipientOpens for this EmailRecipient
13
+ collection_attribute :opens, 'EmailRecipientOpens'
14
+
15
+ ##
16
+ # A CollectionResource of EmailRecipientClicks for this EmailRecipient
17
+ collection_attribute :clicks, 'EmailRecipientClicks'
6
18
  end
7
- end
19
+ end
@@ -0,0 +1,8 @@
1
+ module TMS #:nodoc:
2
+ class EmailRecipientClick
3
+ include InstanceResource
4
+
5
+ # @!parse attr_reader :event_at, :url
6
+ readonly_attributes :event_at, :url
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module TMS #:nodoc:
2
+ class EmailRecipientOpen
3
+ include InstanceResource
4
+
5
+ # @!parse attr_reader :event_at
6
+ readonly_attributes :event_at
7
+ end
8
+ end
@@ -1,7 +1,8 @@
1
1
  module TMS #:nodoc:
2
2
  class InboundSmsMessage
3
3
  include InstanceResource
4
-
4
+
5
+ # @!parse attr_reader :created_at, :completed_at, :from, :body, :to
5
6
  readonly_attributes :created_at, :completed_at, :from, :body, :to
6
7
  end
7
8
  end
@@ -15,7 +15,11 @@ module TMS #:nodoc:
15
15
  class Keyword
16
16
  include InstanceResource
17
17
 
18
+ # @!parse attr_accessor :name
18
19
  writeable_attributes :name
20
+
21
+ ##
22
+ # A CollectionResource of Command objects
19
23
  collection_attributes :commands
20
24
 
21
25
  end
@@ -1,8 +1,10 @@
1
1
  module TMS #:nodoc:
2
2
  class Recipient
3
3
  include InstanceResource
4
-
4
+ # @!parse attr_accessor :phone
5
5
  writeable_attributes :phone
6
- readonly_attributes :formatted_phone, :error_message, :status
6
+
7
+ # @!parse attr_reader :formatted_phone, :error_message, :status, :completed_at
8
+ readonly_attributes :formatted_phone, :error_message, :status, :completed_at
7
9
  end
8
10
  end
@@ -1,9 +1,28 @@
1
1
  module TMS #:nodoc:
2
+ # An SMSMessage is used to create and send a text message to a collection of Recipient
3
+ # objects.
4
+ #
5
+ #
6
+ # ==== Attributes
7
+ #
8
+ # * +body+ - The content of the SMS. This field will be truncated to 160 characters.
9
+ #
10
+ # === Example
11
+ # sms = client.sms_messages.build(:body => "Hello")
12
+ # sms.recipients.build(:phone => "+18001002000")
13
+ # sms.post
14
+ # sms.get
2
15
  class SmsMessage
3
16
  include InstanceResource
17
+
18
+ # @!parse attr_accessor :body
19
+ writeable_attributes :body
4
20
 
5
- writeable_attributes :from, :body, :subject
6
- readonly_attributes :created_at, :completed_at
21
+ # @!parse attr_reader :created_at, :completed_at, :status
22
+ readonly_attributes :created_at, :completed_at, :status
23
+
24
+ ##
25
+ # A CollectionResource of Recipient objects
7
26
  collection_attributes :recipients
8
27
  end
9
28
  end
@@ -1,9 +1,29 @@
1
1
  module TMS #:nodoc:
2
+ # A VoiceMessage is used to create and send a voice message to a collection of Recipient
3
+ # objects. The recipients are called and the provided +play_url+ is
4
+ # played to them. Accepted sound formats include +wav+, +mp3+, and +aiff+.
5
+ #
6
+ #
7
+ # ==== Attributes
8
+ #
9
+ # * +play_url+ - The url to the sound file to be played back to the call recipients
10
+ #
11
+ # === Example
12
+ # voice_message = client.voice_messages.build(:play_url => "http://example.com/emergency_weather.mp3")
13
+ # voice_message.recipients.build(:phone => "+18001002000")
14
+ # voice_message.post
15
+ # voice_message.get
2
16
  class VoiceMessage
3
17
  include InstanceResource
4
18
 
19
+ # @!parse attr_accessor :play_url
5
20
  writeable_attributes :play_url
6
- readonly_attributes :created_at, :completed_at
21
+
22
+ # @!parse attr_reader :created_at, :completed_at, :status
23
+ readonly_attributes :created_at, :completed_at, :status
24
+
25
+ ##
26
+ # A CollectionResource of Recipient objects
7
27
  collection_attributes :recipients
8
28
 
9
29
  def self.to_s
@@ -29,7 +29,7 @@ module TMS::Util
29
29
  if rel == 'self'
30
30
  self.href = href
31
31
  else
32
- klass = ::TMS.const_get(classify(rel)) rescue nil
32
+ klass = relation_class(rel)
33
33
  klass = self.class if ['first', 'prev', 'next', 'last'].include?(rel)
34
34
  if klass
35
35
  subresources[rel] = klass.new(self.client, href)
@@ -42,6 +42,10 @@ module TMS::Util
42
42
  end
43
43
  end
44
44
 
45
+ def relation_class(rel)
46
+ ::TMS.const_get(classify(rel)) rescue nil
47
+ end
48
+
45
49
  def setup_subresource(link)
46
50
  return unless link
47
51
  link.each { |rel, href| self.metaclass.send(:define_method, rel.to_sym, &lambda { subresources[rel] }) unless rel == 'self' }
@@ -1,3 +1,3 @@
1
1
  module TMS #:nodoc:
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/tms_client.rb CHANGED
@@ -4,9 +4,9 @@ end
4
4
  require 'active_support'
5
5
  require 'tms_client/version'
6
6
  require 'faraday'
7
- require 'link_header'
8
7
  require 'faraday_middleware'
9
8
 
9
+ require 'tms_client/link_header'
10
10
  require 'tms_client/util/hal_link_parser'
11
11
  require 'tms_client/util/core_ext'
12
12
  require 'tms_client/connection'
@@ -16,15 +16,18 @@ require 'tms_client/base'
16
16
  require 'tms_client/instance_resource'
17
17
  require 'tms_client/collection_resource'
18
18
  require 'tms_client/request'
19
+ require 'tms_client/errors'
19
20
 
20
21
  require 'tms_client/resource/collections'
21
22
  require 'tms_client/resource/recipient'
22
23
  require 'tms_client/resource/email_recipient'
24
+ require 'tms_client/resource/email_recipient_open'
25
+ require 'tms_client/resource/email_recipient_click'
23
26
  require 'tms_client/resource/sms_message'
24
27
  require 'tms_client/resource/voice_message'
28
+ require 'tms_client/resource/email_message'
25
29
  require 'tms_client/resource/inbound_sms_message'
26
30
  require 'tms_client/resource/command_type'
27
31
  require 'tms_client/resource/command'
28
- require 'tms_client/resource/commands'
29
32
  require 'tms_client/resource/keyword'
30
- require 'tms_client/resource/email'
33
+
@@ -6,7 +6,7 @@ describe TMS::InboundSmsMessages do
6
6
  double('client')
7
7
  end
8
8
  before do
9
- @messages = TMS::InboundSmsMessages.new(client, '/inbound_sms_messages')
9
+ @messages = TMS::InboundSmsMessages.new(client, '/inbound_messages')
10
10
  end
11
11
  it 'should GET itself' do
12
12
  body = [{:body=>"HELP", :from=>"+16125551212", :created_at=>"a while ago", :to=>"(651) 433-6258"}, {:body=>"STOP", :from=>"+16125551212", :created_at=>"a while ago", :to=>"(651) 433-6258"}]
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ class Foo
3
+ include TMS::InstanceResource
4
+ writeable_attributes :bar
5
+ collection_attribute :blah, 'EmailMessage'
6
+ end
7
+
8
+ describe TMS::InstanceResource do
9
+ context "creating a new inbound messages list" do
10
+ let(:happy_response) do
11
+ double(:status => 201, :body => {})
12
+ end
13
+
14
+ let(:client) do
15
+ double('client', :post => happy_response)
16
+ end
17
+
18
+
19
+ before do
20
+ @instance_resource = Foo.new(client)
21
+ end
22
+
23
+ it 'should POST' do
24
+ @instance_resource.bar = "OMG"
25
+ @instance_resource.post.should be_true
26
+ end
27
+
28
+ it 'should correctly reflect on collection resources' do
29
+ @instance_resource.blah.class.should == TMS::EmailMessage
30
+ end
31
+ end
32
+ end
data/tms_client.gemspec CHANGED
@@ -19,10 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.add_runtime_dependency "faraday"
20
20
  s.add_runtime_dependency "faraday_middleware"
21
21
  s.add_runtime_dependency "activesupport"
22
- s.add_runtime_dependency "link_header"
23
- s.add_development_dependency "rspec"
24
- s.add_development_dependency "rake"
25
- s.add_development_dependency "rubygems-tasks"
26
22
 
27
23
  s.files = %w{
28
24
  Gemfile
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tms_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-23 00:00:00.000000000 Z
12
+ date: 2013-02-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -59,70 +59,6 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- - !ruby/object:Gem::Dependency
63
- name: link_header
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ! '>='
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- type: :runtime
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ! '>='
76
- - !ruby/object:Gem::Version
77
- version: '0'
78
- - !ruby/object:Gem::Dependency
79
- name: rspec
80
- requirement: !ruby/object:Gem::Requirement
81
- none: false
82
- requirements:
83
- - - ! '>='
84
- - !ruby/object:Gem::Version
85
- version: '0'
86
- type: :development
87
- prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ! '>='
92
- - !ruby/object:Gem::Version
93
- version: '0'
94
- - !ruby/object:Gem::Dependency
95
- name: rake
96
- requirement: !ruby/object:Gem::Requirement
97
- none: false
98
- requirements:
99
- - - ! '>='
100
- - !ruby/object:Gem::Version
101
- version: '0'
102
- type: :development
103
- prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
- requirements:
107
- - - ! '>='
108
- - !ruby/object:Gem::Version
109
- version: '0'
110
- - !ruby/object:Gem::Dependency
111
- name: rubygems-tasks
112
- requirement: !ruby/object:Gem::Requirement
113
- none: false
114
- requirements:
115
- - - ! '>='
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
- requirements:
123
- - - ! '>='
124
- - !ruby/object:Gem::Version
125
- version: '0'
126
62
  description: ! 'A reference implementation, written in Ruby, to interact with GovDelivery''s
127
63
  TMS API. The client is compatible with Ruby versions 1.8.7 and 1.9.3. '
128
64
  email:
@@ -139,15 +75,18 @@ files:
139
75
  - lib/tms_client/client.rb
140
76
  - lib/tms_client/collection_resource.rb
141
77
  - lib/tms_client/connection.rb
78
+ - lib/tms_client/errors.rb
142
79
  - lib/tms_client/instance_resource.rb
80
+ - lib/tms_client/link_header.rb
143
81
  - lib/tms_client/logger.rb
144
82
  - lib/tms_client/request.rb
145
83
  - lib/tms_client/resource/collections.rb
146
84
  - lib/tms_client/resource/command.rb
147
85
  - lib/tms_client/resource/command_type.rb
148
- - lib/tms_client/resource/commands.rb
149
- - lib/tms_client/resource/email.rb
86
+ - lib/tms_client/resource/email_message.rb
150
87
  - lib/tms_client/resource/email_recipient.rb
88
+ - lib/tms_client/resource/email_recipient_click.rb
89
+ - lib/tms_client/resource/email_recipient_open.rb
151
90
  - lib/tms_client/resource/inbound_sms_message.rb
152
91
  - lib/tms_client/resource/keyword.rb
153
92
  - lib/tms_client/resource/recipient.rb
@@ -160,6 +99,7 @@ files:
160
99
  - spec/client_spec.rb
161
100
  - spec/command_types_spec.rb
162
101
  - spec/inbound_sms_messages_spec.rb
102
+ - spec/instance_resource_spec.rb
163
103
  - spec/keyword_spec.rb
164
104
  - spec/keywords_spec.rb
165
105
  - spec/message_spec.rb
@@ -186,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
126
  version: '0'
187
127
  requirements: []
188
128
  rubyforge_project:
189
- rubygems_version: 1.8.24
129
+ rubygems_version: 1.8.25
190
130
  signing_key:
191
131
  specification_version: 3
192
132
  summary: A ruby client to interact with the GovDelivery TMS REST API.
@@ -194,6 +134,7 @@ test_files:
194
134
  - spec/client_spec.rb
195
135
  - spec/command_types_spec.rb
196
136
  - spec/inbound_sms_messages_spec.rb
137
+ - spec/instance_resource_spec.rb
197
138
  - spec/keyword_spec.rb
198
139
  - spec/keywords_spec.rb
199
140
  - spec/message_spec.rb
@@ -1,3 +0,0 @@
1
- class TMS::Commands
2
- include TMS::CollectionResource
3
- end
@@ -1,9 +0,0 @@
1
- module TMS #:nodoc:
2
- class Email
3
- include InstanceResource
4
-
5
- writeable_attributes :body, :from, :subject
6
- readonly_attributes :created_at, :completed_at
7
- collection_attribute :recipients, 'EmailRecipients'
8
- end
9
- end