dotmailer 0.0.1 → 0.0.2
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.
- data/README.md +162 -11
- data/TODO.md +6 -0
- data/dotmailer.gemspec +4 -3
- data/lib/dot_mailer.rb +11 -0
- data/lib/dot_mailer/account.rb +66 -0
- data/lib/dot_mailer/client.rb +108 -0
- data/lib/dot_mailer/contact.rb +179 -0
- data/lib/dot_mailer/contact_import.rb +94 -0
- data/lib/{dotmailer → dot_mailer}/data_field.rb +24 -1
- data/lib/dot_mailer/exceptions.rb +16 -0
- data/lib/dot_mailer/opt_in_type.rb +16 -0
- data/lib/dot_mailer/suppression.rb +29 -0
- data/lib/dot_mailer/version.rb +3 -0
- data/lib/dotmailer.rb +1 -3
- data/spec/dot_mailer/account_spec.rb +113 -0
- data/spec/dot_mailer/client_spec.rb +160 -0
- data/spec/dot_mailer/contact_import_spec.rb +173 -0
- data/spec/dot_mailer/contact_spec.rb +303 -0
- data/spec/dot_mailer/data_field_spec.rb +109 -0
- data/spec/dot_mailer/opt_in_type_spec.rb +21 -0
- data/spec/dot_mailer/suppression_spec.rb +67 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/assignable_attributes_helper.rb +20 -0
- metadata +37 -8
- data/lib/dotmailer/client.rb +0 -78
- data/lib/dotmailer/exceptions.rb +0 -4
- data/lib/dotmailer/version.rb +0 -3
- data/spec/dotmailer/client_spec.rb +0 -159
- data/spec/dotmailer/data_field_spec.rb +0 -32
data/README.md
CHANGED
@@ -21,31 +21,182 @@ To install as part of a project managed by bundler, add to your Gemfile:
|
|
21
21
|
Usage
|
22
22
|
-----
|
23
23
|
|
24
|
-
|
24
|
+
Interaction with the dotMailer API is done via a `DotMailer::Account` object, which is initialized with an API username and password:
|
25
25
|
|
26
|
-
|
26
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
27
27
|
|
28
|
-
|
28
|
+
All interaction via this object will be for the dotMailer account associated with the API credentials.
|
29
|
+
|
30
|
+
For instructions on how to obtain your API username and password, see [here](http://www.dotmailer.co.uk/api/more_about_api/getting_started_with_the_api.aspx).
|
29
31
|
|
30
32
|
Data Fields
|
31
33
|
-----------
|
32
34
|
|
33
35
|
### List
|
34
36
|
|
35
|
-
`
|
37
|
+
`DotMailer::Account#data_fields` will return an Array of `DotMailer::DataField` objects representing the data fields for the account's global address book:
|
38
|
+
|
39
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
36
40
|
|
37
|
-
|
41
|
+
account.data_fields
|
38
42
|
=> [
|
39
|
-
|
40
|
-
|
43
|
+
DotMailer::DataField name: "FIELD1", type: "String", visibility: "Public", default: "",
|
44
|
+
DotMailer::DataField name: "FIELD2", type: "Numeric", visibility: "Private", default: 0
|
41
45
|
]
|
42
46
|
|
47
|
+
NOTE: The returned data fields are cached in memory for `DotMailer::Account::CACHE_LIFETIME` seconds to avoid unnecessarily hitting the API.
|
48
|
+
|
43
49
|
### Create
|
44
50
|
|
45
|
-
`
|
51
|
+
`DotMailer::Account#create_data_field` will attempt to create a new data field. On failure it raises an exception:
|
52
|
+
|
53
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
54
|
+
|
55
|
+
account.create_data_field 'FIELD3', :type => 'String'
|
56
|
+
|
57
|
+
account.create_data_field 'FIELD3', :type => 'String'
|
58
|
+
=> DotMailer::InvalidRequest: Field already exists. ERROR_NON_UNIQUE_DATAFIELD
|
59
|
+
|
60
|
+
NOTE: successfully creating a data field via this method will invalidate any cached data fields.
|
61
|
+
|
62
|
+
Contacts
|
63
|
+
--------
|
64
|
+
|
65
|
+
### Finding A Contact
|
66
|
+
|
67
|
+
There are two ways to find contacts via the API, using a contact's email address or id.
|
68
|
+
|
69
|
+
The gem provides two methods for doing so: `DotMailer::Account#find_contact_by_email` and `DotMailer::Account#find_contact_by_id`.
|
70
|
+
|
71
|
+
Suppose you have one contact with email john@example.com and id 12345, then:
|
72
|
+
|
73
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
74
|
+
|
75
|
+
account.find_contact_by_email 'john@example.com'
|
76
|
+
=> DotMailer::Contact id: 12345, email: john@example.com
|
77
|
+
|
78
|
+
account.find_contact_by_email 'sue@example.com'
|
79
|
+
=> nil
|
80
|
+
|
81
|
+
account.find_contact_by_id 12345
|
82
|
+
=> DotMailer::Contact id: 12345, email: john@example.com
|
83
|
+
|
84
|
+
account.find_contact_by_id 54321
|
85
|
+
=> nil
|
86
|
+
|
87
|
+
### Finding contacts modified since a particular time
|
88
|
+
|
89
|
+
Contacts modified since a particular time can be retrieved by passing a Time object to `DotMailer::Account#find_contacts_modified_since`:
|
90
|
+
|
91
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
92
|
+
|
93
|
+
time = Time.parse('1st March 2013 15:30')
|
94
|
+
|
95
|
+
account.find_contacts_modified_since(time)
|
96
|
+
=> [
|
97
|
+
DotMailer::Contact id: 123, email: bob@example.com,
|
98
|
+
DotMailer::Contact id: 345, email: sue@example.com
|
99
|
+
]
|
100
|
+
|
101
|
+
### Updating a contact
|
102
|
+
|
103
|
+
Contacts can be updated by assigning new values and calling `DotMailer::Contact#save`:
|
104
|
+
|
105
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
106
|
+
|
107
|
+
contact = account.find_contact_by_email 'john@example.com'
|
108
|
+
=> DotMailer::Contact id: 12345, email: john@example.com, email_type: Html
|
109
|
+
|
110
|
+
contact.email_type
|
111
|
+
=> 'Html'
|
112
|
+
contact.email_type = 'PlainText'
|
113
|
+
=> 'PlainText
|
114
|
+
|
115
|
+
contact.save
|
46
116
|
|
47
|
-
|
117
|
+
contact = DotMailer.find_contact_by_email 'john@example.com'
|
118
|
+
=> DotMailer::Contact id: 12345, email: john@example.com, email_type: PlainText
|
119
|
+
|
120
|
+
### Resubscribing a contact
|
121
|
+
|
122
|
+
The dotMailer API provides a specific endpoint for resubscribing contacts which will initiate the resubscribe process via email, then redirect the contact to a specified URL.
|
123
|
+
|
124
|
+
This can be accessed through the `DotMailer::Contact#resubscribe` method:
|
125
|
+
|
126
|
+
contact = DotMailer.find_contact_by_email 'john@example.com'
|
127
|
+
=> DotMailer::Contact id: 12345, email: john@example.com, status: Unsubscribed
|
128
|
+
|
129
|
+
contact.subscribed?
|
130
|
+
=> false
|
131
|
+
contact.resubscribe 'http://www.example.com/resubscribed'
|
132
|
+
|
133
|
+
Then, once the contact has gone through the resubscribe process and been redirected to the specified URL:
|
134
|
+
|
135
|
+
contact = DotMailer.find_contact_by_email 'john@example.com'
|
136
|
+
=> DotMailer::Contact id: 12345, email: john@example.com, status: Subscribed
|
137
|
+
|
138
|
+
contact.subscribed?
|
48
139
|
=> true
|
49
140
|
|
50
|
-
|
51
|
-
|
141
|
+
### Bulk Import
|
142
|
+
|
143
|
+
`DotMailer::Account#import_contacts` will start a batch import of contacts into the global address book, and return a `DotMailer::ContactImport` object which has a `status`:
|
144
|
+
|
145
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
146
|
+
|
147
|
+
import = account.import_contacts [
|
148
|
+
{ 'Email' => 'joe@example.com' },
|
149
|
+
{ 'Email' => 'sue@example.com' },
|
150
|
+
{ 'Email' => 'bob@example.com' },
|
151
|
+
{ 'Email' => 'invalid@email' }
|
152
|
+
]
|
153
|
+
=> DotMailer::ContactImport contacts: [{"Email"=>"joe@example.com" }, {"Email"=>"sue@example.com" }, {"Email"=>"bob@example.com"}]
|
154
|
+
|
155
|
+
import.finished?
|
156
|
+
=> false
|
157
|
+
import.status
|
158
|
+
=> "NotFinished"
|
159
|
+
|
160
|
+
Then, once the import has finished, you can query the status and get any errors (as a CSV::Table object):
|
161
|
+
|
162
|
+
import.finished?
|
163
|
+
=> true
|
164
|
+
import.status
|
165
|
+
=> "Finished"
|
166
|
+
|
167
|
+
errors = import.errors
|
168
|
+
=> #<CSV::Table>
|
169
|
+
errors.count
|
170
|
+
=> 1
|
171
|
+
errors.first
|
172
|
+
=> #<CSV::Row "Reason":"Invalid Email" "Email":"invalid@email">
|
173
|
+
|
174
|
+
**NOTE** The specified contacts can only have the following keys (case insensitive):
|
175
|
+
|
176
|
+
* `id`
|
177
|
+
* `email`
|
178
|
+
* `optInType`
|
179
|
+
* `emailType`
|
180
|
+
* Any data field name for the account (i.e. any value in `account.data_fields.map(&:name)`)
|
181
|
+
|
182
|
+
If any other key is present in any of the contacts, a `DotMailer::UnknownDataField` error will be raised
|
183
|
+
|
184
|
+
Suppressions
|
185
|
+
------------
|
186
|
+
|
187
|
+
The dotMailer API provides an endpoint for retrieving suppressions since a particular point in time, where a "suppression" is the combination of a contact, a removal date, and a reason for the suppression.
|
188
|
+
|
189
|
+
To fetch these suppressions, pass a Time object to `DotMailer::Account#find_suppressions_since`:
|
190
|
+
|
191
|
+
account = DotMailer::Account.new('your-api-username', 'your-api-password')
|
192
|
+
|
193
|
+
time = Time.parse('1st March 2013 15:30')
|
194
|
+
|
195
|
+
suppressions = account.find_suppressions_since(time)
|
196
|
+
=> [
|
197
|
+
DotMailer::Suppression reason: Unsubscribed, date_removed: 2013-03-02 14:00:00 UTC,
|
198
|
+
DotMailer::Suppression reason: Unsubscribed, date_removed: 2013-03-04 16:00:00 UTC
|
199
|
+
]
|
200
|
+
|
201
|
+
suppressions.first.contact
|
202
|
+
=> DotMailer::Contact id: 12345, email: john@example.com
|
data/TODO.md
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Test against live API
|
2
|
+
---------------------
|
3
|
+
|
4
|
+
The dotMailer does not have a sandbox mode so we will need to be careful that we do not send mail to real email addresses whilst testing.
|
5
|
+
|
6
|
+
We should consider using [VCR](https://github.com/vcr/vcr) to cache HTTP requests from the API.
|
data/dotmailer.gemspec
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "
|
3
|
+
require "dot_mailer/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "dotmailer"
|
7
|
-
s.version =
|
7
|
+
s.version = DotMailer::VERSION
|
8
8
|
s.authors = ["Econsultancy"]
|
9
9
|
s.email = ["tech@econsultancy.com"]
|
10
10
|
s.homepage = "https://github.com/econsultancy/dotmailer"
|
@@ -13,10 +13,11 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.rubyforge_project = "dotmailer"
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
16
|
-
s.test_files = `git ls-files -- {
|
16
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
|
20
|
+
s.add_runtime_dependency 'activesupport'
|
20
21
|
s.add_runtime_dependency 'rest-client'
|
21
22
|
|
22
23
|
s.add_development_dependency 'rspec'
|
data/lib/dot_mailer.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'dot_mailer/exceptions'
|
2
|
+
require 'dot_mailer/data_field'
|
3
|
+
require 'dot_mailer/contact_import'
|
4
|
+
require 'dot_mailer/contact'
|
5
|
+
require 'dot_mailer/suppression'
|
6
|
+
require 'dot_mailer/account'
|
7
|
+
require 'dot_mailer/client'
|
8
|
+
|
9
|
+
module DotMailer
|
10
|
+
SUBSCRIBED_STATUS = 'Subscribed'
|
11
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_support/core_ext/numeric/time'
|
2
|
+
require 'active_support/cache'
|
3
|
+
|
4
|
+
module DotMailer
|
5
|
+
class Account
|
6
|
+
CACHE_LIFETIME = 1.minute
|
7
|
+
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(api_user, api_pass)
|
11
|
+
self.client = Client.new(api_user, api_pass)
|
12
|
+
end
|
13
|
+
|
14
|
+
def data_fields
|
15
|
+
cache.fetch 'data_fields' do
|
16
|
+
DataField.all self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_data_field(name, options = {})
|
21
|
+
DataField.create self, name, options
|
22
|
+
|
23
|
+
cache.delete 'data_fields'
|
24
|
+
end
|
25
|
+
|
26
|
+
def import_contacts(contacts)
|
27
|
+
ContactImport.import self, contacts
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_contact_by_email(email)
|
31
|
+
Contact.find_by_email self, email
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_contact_by_id(id)
|
35
|
+
Contact.find_by_id self, id
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_contacts_modified_since(time)
|
39
|
+
Contact.modified_since(self, time)
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_suppressions_since(time)
|
43
|
+
Suppression.suppressed_since(self, time)
|
44
|
+
end
|
45
|
+
|
46
|
+
def suppress(email)
|
47
|
+
client.post_json '/contacts/unsubscribe', 'Email' => email
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"#{self.class.name} client: #{client}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
attr_writer :client
|
60
|
+
|
61
|
+
def cache
|
62
|
+
# Auto expire content after CACHE_LIFETIME seconds
|
63
|
+
@cache ||= ActiveSupport::Cache::MemoryStore.new :expires_in => CACHE_LIFETIME
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'tempfile'
|
3
|
+
require 'cgi'
|
4
|
+
require 'json'
|
5
|
+
require 'restclient'
|
6
|
+
|
7
|
+
module DotMailer
|
8
|
+
class Client
|
9
|
+
def initialize(api_user, api_pass)
|
10
|
+
self.api_user = api_user
|
11
|
+
self.api_pass = api_pass
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(path)
|
15
|
+
rescue_api_errors do
|
16
|
+
endpoint = endpoint_for(path)
|
17
|
+
response = RestClient.get endpoint, :accept => :json
|
18
|
+
|
19
|
+
JSON.parse response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_csv(path)
|
24
|
+
rescue_api_errors do
|
25
|
+
endpoint = endpoint_for(path)
|
26
|
+
response = RestClient.get endpoint, :accept => :csv
|
27
|
+
|
28
|
+
# Force the encoding to UTF-8 as that is what the
|
29
|
+
# API returns (otherwise it will be ASCII-8BIT, see
|
30
|
+
# http://bugs.ruby-lang.org/issues/2567)
|
31
|
+
response.force_encoding('UTF-8')
|
32
|
+
|
33
|
+
# Remove the UTF-8 BOM if present
|
34
|
+
response.sub!(/\A\xEF\xBB\xBF/, '')
|
35
|
+
|
36
|
+
CSV.parse response, :headers => true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_json(path, params)
|
41
|
+
post path, params.to_json, :content_type => :json
|
42
|
+
end
|
43
|
+
|
44
|
+
# Need to use a Tempfile as the API will not accept CSVs
|
45
|
+
# without filenames
|
46
|
+
def post_csv(path, csv)
|
47
|
+
file = Tempfile.new(['dotmailer-contacts', '.csv'])
|
48
|
+
file.write csv
|
49
|
+
file.rewind
|
50
|
+
|
51
|
+
post path, :csv => file
|
52
|
+
end
|
53
|
+
|
54
|
+
def post(path, data, options = {})
|
55
|
+
rescue_api_errors do
|
56
|
+
endpoint = endpoint_for(path)
|
57
|
+
response = RestClient.post endpoint, data, options.merge(:accept => :json)
|
58
|
+
|
59
|
+
JSON.parse response
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def put_json(path, params)
|
64
|
+
put path, params.to_json, :content_type => :json
|
65
|
+
end
|
66
|
+
|
67
|
+
def put(path, data, options = {})
|
68
|
+
rescue_api_errors do
|
69
|
+
endpoint = endpoint_for(path)
|
70
|
+
response = RestClient.put endpoint, data, options.merge(:accept => :json)
|
71
|
+
|
72
|
+
JSON.parse response
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
"#{self.class.name} api_user: #{api_user}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def inspect
|
81
|
+
to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
attr_accessor :api_user, :api_pass
|
86
|
+
|
87
|
+
def rescue_api_errors
|
88
|
+
yield
|
89
|
+
rescue RestClient::BadRequest => e
|
90
|
+
raise InvalidRequest, extract_message_from_exception(e)
|
91
|
+
rescue RestClient::ResourceNotFound => e
|
92
|
+
raise NotFound, extract_message_from_exception(e)
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_message_from_exception(exception)
|
96
|
+
JSON.parse(exception.http_body)['message']
|
97
|
+
end
|
98
|
+
|
99
|
+
def endpoint_for(path)
|
100
|
+
URI::Generic.build(
|
101
|
+
:scheme => 'https',
|
102
|
+
:userinfo => "#{CGI.escape(api_user)}:#{CGI.escape(api_pass)}",
|
103
|
+
:host => 'api.dotmailer.com',
|
104
|
+
:path => "/v2#{path}"
|
105
|
+
).to_s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'active_support/core_ext/object/try'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require 'dot_mailer/opt_in_type'
|
6
|
+
|
7
|
+
module DotMailer
|
8
|
+
class Contact
|
9
|
+
def self.find_by_email(account, email)
|
10
|
+
response = account.client.get("/contacts/#{email}")
|
11
|
+
|
12
|
+
new(account, response)
|
13
|
+
rescue DotMailer::NotFound
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# The API makes no distinction between finding
|
18
|
+
# by email or id, so we just delegate to
|
19
|
+
# Contact.find_by_email
|
20
|
+
def self.find_by_id(account, id)
|
21
|
+
find_by_email account, id
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.modified_since(account, time)
|
25
|
+
response = account.client.get("/contacts/modified-since/#{time.utc.xmlschema}")
|
26
|
+
|
27
|
+
response.map do |attributes|
|
28
|
+
new(account, attributes)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(account, attributes)
|
33
|
+
self.account = account
|
34
|
+
self.attributes = attributes
|
35
|
+
end
|
36
|
+
|
37
|
+
def id
|
38
|
+
attributes['id']
|
39
|
+
end
|
40
|
+
|
41
|
+
def email
|
42
|
+
attributes['email']
|
43
|
+
end
|
44
|
+
|
45
|
+
def email=(email)
|
46
|
+
attributes['email'] = email
|
47
|
+
end
|
48
|
+
|
49
|
+
def opt_in_type
|
50
|
+
attributes['optInType']
|
51
|
+
end
|
52
|
+
|
53
|
+
def opt_in_type=(opt_in_type)
|
54
|
+
raise UnknownOptInType, opt_in_type unless OptInType.exists?(opt_in_type)
|
55
|
+
|
56
|
+
attributes['optInType'] = opt_in_type
|
57
|
+
end
|
58
|
+
|
59
|
+
def email_type
|
60
|
+
attributes['emailType']
|
61
|
+
end
|
62
|
+
|
63
|
+
def email_type=(email_type)
|
64
|
+
attributes['emailType'] = email_type
|
65
|
+
end
|
66
|
+
|
67
|
+
def status
|
68
|
+
attributes['status']
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
%{#{self.class.name} id: #{id}, email: #{email}, opt_in_type: #{opt_in_type}, email_type: #{email_type}, status: #{status}, data_fields: #{data_fields.to_s}}
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect
|
76
|
+
to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
# A wrapper method for accessing data field values by name, e.g.:
|
80
|
+
#
|
81
|
+
# contact['FIRSTNAME']
|
82
|
+
#
|
83
|
+
def [](key)
|
84
|
+
if data_fields.has_key?(key)
|
85
|
+
data_fields[key]
|
86
|
+
else
|
87
|
+
raise UnknownDataField, key
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# A wrapper method for assigning data field values, e.g.:
|
92
|
+
#
|
93
|
+
# contact['FIRSTNAME'] = 'Lewis'
|
94
|
+
#
|
95
|
+
def []=(key, value)
|
96
|
+
if data_fields.has_key?(key)
|
97
|
+
data_fields[key] = value
|
98
|
+
else
|
99
|
+
raise UnknownDataField, key
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def save
|
104
|
+
client.put_json "/contacts/#{id}", attributes.merge('dataFields' => data_fields_for_api)
|
105
|
+
end
|
106
|
+
|
107
|
+
def subscribed?
|
108
|
+
status == SUBSCRIBED_STATUS
|
109
|
+
end
|
110
|
+
|
111
|
+
def resubscribe(return_url)
|
112
|
+
return false if subscribed?
|
113
|
+
|
114
|
+
client.post_json '/contacts/resubscribe',
|
115
|
+
'UnsubscribedContact' => {
|
116
|
+
'id' => id,
|
117
|
+
'Email' => email
|
118
|
+
},
|
119
|
+
'ReturnUrlToUseIfChallenged' => return_url
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
attr_accessor :attributes, :account
|
124
|
+
|
125
|
+
def client
|
126
|
+
account.client
|
127
|
+
end
|
128
|
+
|
129
|
+
# Convert data fields from the API into a flat hash.
|
130
|
+
#
|
131
|
+
# We coerce Date fields from strings into Time objects.
|
132
|
+
#
|
133
|
+
# The API returns data field values in the following format:
|
134
|
+
#
|
135
|
+
# 'dataFields' => [
|
136
|
+
# { 'key' => 'FIELD1', 'value' => 'some value'},
|
137
|
+
# { 'key' => 'FIELD2', 'value' => 'some other value'}
|
138
|
+
# ]
|
139
|
+
#
|
140
|
+
# We convert that here to:
|
141
|
+
#
|
142
|
+
# { 'FIELD1' => 'some value', 'FIELD2' => 'some other value' }
|
143
|
+
#
|
144
|
+
def data_fields
|
145
|
+
# Some API calls (e.g. modified-since) don't return data fields
|
146
|
+
return [] unless attributes['dataFields'].present?
|
147
|
+
|
148
|
+
@data_fields ||=
|
149
|
+
begin
|
150
|
+
account.data_fields.each_with_object({}) do |data_field, hash|
|
151
|
+
value = attributes['dataFields'].detect { |f| f['key'] == data_field.name }.try(:[], 'value')
|
152
|
+
|
153
|
+
if value.present? && data_field.date?
|
154
|
+
value = Time.parse(value)
|
155
|
+
end
|
156
|
+
|
157
|
+
hash[data_field.name] = value
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Convert data fields from a flat hash to an API compatible hash:
|
163
|
+
#
|
164
|
+
# { 'FIELD1' => 'some value', 'FIELD2' => 'some other value' }
|
165
|
+
#
|
166
|
+
# Becomes:
|
167
|
+
#
|
168
|
+
# [
|
169
|
+
# { 'key' => 'FIELD1', 'value' => 'some value'},
|
170
|
+
# { 'key' => 'FIELD2', 'value' => 'some other value'}
|
171
|
+
# ]
|
172
|
+
#
|
173
|
+
def data_fields_for_api
|
174
|
+
data_fields.map do |key, value|
|
175
|
+
{ 'key' => key, 'value' => value.to_s }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|