SFEley-acts_as_icontact 0.1.4 → 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +12 -12
- data/VERSION +1 -1
- data/acts_as_icontact.gemspec +5 -2
- data/lib/acts_as_icontact/config.rb +12 -10
- data/lib/acts_as_icontact/resource.rb +81 -23
- data/lib/acts_as_icontact/resources/account.rb +6 -1
- data/lib/acts_as_icontact/resources/client.rb +5 -0
- data/lib/acts_as_icontact/resources/contact.rb +1 -0
- data/lib/acts_as_icontact/resources/list.rb +7 -0
- data/lib/acts_as_icontact/resources/message.rb +19 -0
- data/spec/config_spec.rb +9 -9
- data/spec/resource_spec.rb +26 -12
- data/spec/resources/list_spec.rb +4 -1
- data/spec/resources/message_spec.rb +28 -0
- data/spec/spec_fakeweb.rb +21 -11
- data/spec/spec_helper.rb +1 -1
- metadata +5 -2
data/README.markdown
CHANGED
@@ -23,26 +23,26 @@ Using ActsAsIcontact is easy, but going through iContact's authorization process
|
|
23
23
|
|
24
24
|
$ sudo gem install acts_as_icontact
|
25
25
|
|
26
|
-
2. _Optional but recommended:_ Go to <http://
|
26
|
+
2. _Optional but recommended:_ Go to <http://sandbox.icontact.com> and sign up for an iContact Sandbox account. This will let you test your app without risk of blowing away your production mailing lists.
|
27
27
|
|
28
|
-
3. Enable the ActsAsIcontact gem for use with your iContact account. The URL and credentials you'll use are different between the
|
28
|
+
3. Enable the ActsAsIcontact gem for use with your iContact account. The URL and credentials you'll use are different between the sandbox and production environments:
|
29
29
|
|
30
|
-
* **
|
30
|
+
* **Sandbox:** Go to <http://app.sandbox.icontact.com/icp/core/externallogin> and enter `Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx` for the Application Id. Choose a password for ActsAsIcontact that's different from your account password.
|
31
31
|
|
32
32
|
* **PRODUCTION:** Go to <http://app.icontact.com/icp/core/externallogin> and enter `IYDOhgaZGUKNjih3hl1ItLln7zpAtWN2` for the Application Id. Choose a password for ActsAsIcontact that's different from your account password.
|
33
33
|
|
34
|
-
4. Set your _(
|
34
|
+
4. Set your _(sandbox, if applicable)_ account username and the password you just chose for API access. You can either set the environment variables `ICONTACT_MODE`, `ICONTACT_USERNAME`, and `ICONTACT_PASSWORD`, or you can explicitly do it with calls to the Config module:
|
35
35
|
|
36
36
|
require 'rubygems'
|
37
37
|
require 'acts_as_icontact'
|
38
38
|
|
39
|
-
ActsAsIcontact::Config.mode = :
|
40
|
-
ActsAsIcontact::Config.username =
|
39
|
+
ActsAsIcontact::Config.mode = :sandbox
|
40
|
+
ActsAsIcontact::Config.username = my_sandbox_username
|
41
41
|
ActsAsIcontact::Config.password = my_api_password
|
42
42
|
|
43
43
|
If you're using Rails, the recommended approach is to require the gem with `config.gem 'acts_as_icontact'` in your **config/environment.rb** file, and then set up an initializer (i.e. **config/initializers/acts\_as\_icontact.rb**) with the above code. See more about Rails below.
|
44
44
|
|
45
|
-
5. Rinse and repeat with production credentials when you're ready to move out of the
|
45
|
+
5. Rinse and repeat with production credentials when you're ready to move out of the sandbox environment.
|
46
46
|
|
47
47
|
API Access
|
48
48
|
----------
|
@@ -120,20 +120,20 @@ Rails Integration
|
|
120
120
|
The _real_ power of ActsAsIcontact is its automatic syncing with ActiveRecord. At this time this feature is focused entirely on Contacts.
|
121
121
|
|
122
122
|
### Activation
|
123
|
-
First add the line `config.gem 'acts_as_icontact'` to your **config/environment.rb** file. Then create an initializer (e.g. **config/initializers/acts\_as\_icontact.rb**) and set it up with your username and password. If applicable, you can give it both the
|
123
|
+
First add the line `config.gem 'acts_as_icontact'` to your **config/environment.rb** file. Then create an initializer (e.g. **config/initializers/acts\_as\_icontact.rb**) and set it up with your username and password. If applicable, you can give it both the sandbox _and_ production credentials:
|
124
124
|
|
125
125
|
module ActsAsIcontact
|
126
126
|
case Config.mode
|
127
|
-
when :
|
128
|
-
Config.username =
|
129
|
-
Config.password =
|
127
|
+
when :sandbox
|
128
|
+
Config.username = my_sandbox_username
|
129
|
+
Config.password = my_sandbox_password
|
130
130
|
when :production
|
131
131
|
Config.username = my_production_username
|
132
132
|
Config.password = my_production_password
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
-
If ActsAsIcontact detects that it's running in a Rails app, the default behavior is to set the mode to `:production` if RAILS\_ENV is equal to "production" and `:
|
136
|
+
If ActsAsIcontact detects that it's running in a Rails app, the default behavior is to set the mode to `:production` if RAILS\_ENV is equal to "production" and `:sandbox` if RAILS\_ENV is set to anything else. (Incidentally, if you're _not_ in a Rails app but running Rack, the same logic applies for the RACK\_ENV environment variable.)
|
137
137
|
|
138
138
|
Finally, enable one of your models to synchronize with iContact with a simple declaration:
|
139
139
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.5
|
data/acts_as_icontact.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{acts_as_icontact}
|
5
|
-
s.version = "0.1.
|
5
|
+
s.version = "0.1.5"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Stephen Eley"]
|
9
|
-
s.date = %q{2009-07-
|
9
|
+
s.date = %q{2009-07-25}
|
10
10
|
s.description = %q{ActsAsIcontact connects Ruby applications with the iContact e-mail marketing service using the iContact API v2.0. Building on the RestClient gem, it offers two significant feature sets:
|
11
11
|
|
12
12
|
* Simple, consistent access to all resources in the iContact API; and
|
@@ -34,6 +34,7 @@ Gem::Specification.new do |s|
|
|
34
34
|
"lib/acts_as_icontact/resources/client.rb",
|
35
35
|
"lib/acts_as_icontact/resources/contact.rb",
|
36
36
|
"lib/acts_as_icontact/resources/list.rb",
|
37
|
+
"lib/acts_as_icontact/resources/message.rb",
|
37
38
|
"spec/config_spec.rb",
|
38
39
|
"spec/connection_spec.rb",
|
39
40
|
"spec/resource_collection_spec.rb",
|
@@ -42,6 +43,7 @@ Gem::Specification.new do |s|
|
|
42
43
|
"spec/resources/client_spec.rb",
|
43
44
|
"spec/resources/contact_spec.rb",
|
44
45
|
"spec/resources/list_spec.rb",
|
46
|
+
"spec/resources/message_spec.rb",
|
45
47
|
"spec/spec.opts",
|
46
48
|
"spec/spec_fakeweb.rb",
|
47
49
|
"spec/spec_helper.rb"
|
@@ -61,6 +63,7 @@ Gem::Specification.new do |s|
|
|
61
63
|
"spec/resources/client_spec.rb",
|
62
64
|
"spec/resources/contact_spec.rb",
|
63
65
|
"spec/resources/list_spec.rb",
|
66
|
+
"spec/resources/message_spec.rb",
|
64
67
|
"spec/spec_fakeweb.rb",
|
65
68
|
"spec/spec_helper.rb"
|
66
69
|
]
|
@@ -3,15 +3,15 @@ module ActsAsIcontact
|
|
3
3
|
# required by the iContact API for authentication.
|
4
4
|
module Config
|
5
5
|
|
6
|
-
# Sets :production or :
|
6
|
+
# Sets :production or :sandbox. This changes the AppId and URL.
|
7
7
|
def self.mode=(val)
|
8
8
|
@mode = val
|
9
9
|
end
|
10
10
|
|
11
|
-
# Determines whether to return the
|
11
|
+
# Determines whether to return the sandbox or production AppId and URL.
|
12
12
|
# If not explicitly set, it will first look for an ICONTACT_MODE environment variable.
|
13
13
|
# If it doesn't find one, it will attempt to detect a Rails or Rack environment; in either
|
14
|
-
# case it will default to :production if RAILS_ENV or RACK_ENV is 'production', and :
|
14
|
+
# case it will default to :production if RAILS_ENV or RACK_ENV is 'production', and :sandbox
|
15
15
|
# otherwise. If none of these conditions apply, it assumes :production. (Because that
|
16
16
|
# probably means someone's doing ad hoc queries.)
|
17
17
|
def self.mode
|
@@ -19,9 +19,9 @@ module ActsAsIcontact
|
|
19
19
|
when ENV["ICONTACT_MODE"]
|
20
20
|
ENV["ICONTACT_MODE"].to_sym
|
21
21
|
when Object.const_defined?(:Rails)
|
22
|
-
(ENV["RAILS_ENV"] == 'production' ? :production : :
|
22
|
+
(ENV["RAILS_ENV"] == 'production' ? :production : :sandbox)
|
23
23
|
when Object.const_defined?(:Rack)
|
24
|
-
(ENV["RACK_ENV"] == 'production' ? :production : :
|
24
|
+
(ENV["RACK_ENV"] == 'production' ? :production : :sandbox)
|
25
25
|
else
|
26
26
|
:production
|
27
27
|
end
|
@@ -32,7 +32,7 @@ module ActsAsIcontact
|
|
32
32
|
# to change this. Ever.
|
33
33
|
def self.app_id
|
34
34
|
case mode
|
35
|
-
when :beta
|
35
|
+
when :beta, :sandbox
|
36
36
|
"Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx"
|
37
37
|
when :production
|
38
38
|
"IYDOhgaZGUKNjih3hl1ItLln7zpAtWN2"
|
@@ -45,14 +45,16 @@ module ActsAsIcontact
|
|
45
45
|
end
|
46
46
|
|
47
47
|
# Prefixed to the beginning of every API request. You can override this if you have some special
|
48
|
-
# need (e.g. working against a testing server, or if iContact
|
49
|
-
#
|
48
|
+
# need (e.g. working against a testing server, or if iContact changes their URL and you have to
|
49
|
+
# fix it before the gem gets updated), but for the most part you can leave it alone.
|
50
50
|
def self.url
|
51
51
|
@url ||= case mode
|
52
|
-
when :beta
|
53
|
-
"https://app.beta.icontact.com/icp/"
|
54
52
|
when :production
|
55
53
|
"https://app.icontact.com/icp/"
|
54
|
+
when :sandbox
|
55
|
+
"https://app.sandbox.icontact.com/icp/"
|
56
|
+
when :beta # The 'beta' environment still works as of 7/25/2009
|
57
|
+
"https://app.beta.icontact.com/icp/"
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
@@ -8,11 +8,10 @@ module ActsAsIcontact
|
|
8
8
|
|
9
9
|
# Creates a new resource object from a values hash. (Which is passed to us via the magic of JSON.)
|
10
10
|
def initialize(properties={})
|
11
|
-
@properties = properties
|
11
|
+
@properties = clean_properties(properties)
|
12
12
|
@new_record = !@properties.has_key?(self.class.primary_key)
|
13
13
|
# Initialize other useful attributes
|
14
14
|
@errors = []
|
15
|
-
|
16
15
|
end
|
17
16
|
|
18
17
|
# Returns the primary key ID for an existing resource. Returns nil if the resource is a new record.
|
@@ -33,12 +32,8 @@ module ActsAsIcontact
|
|
33
32
|
if property =~ /(.*)=$/ # It's a value assignment
|
34
33
|
@newvalues ||= []
|
35
34
|
@newvalues << $1
|
36
|
-
|
37
|
-
|
38
|
-
else
|
39
|
-
@properties[$1] = params[0]
|
40
|
-
end
|
41
|
-
else
|
35
|
+
@properties[$1] = clean_value(params[0])
|
36
|
+
else
|
42
37
|
if @properties.has_key?(property)
|
43
38
|
if self.class.boolean_fields.include?(property)
|
44
39
|
(@properties[property] == 1)
|
@@ -109,28 +104,46 @@ module ActsAsIcontact
|
|
109
104
|
|
110
105
|
# Returns an array of resources starting at the base.
|
111
106
|
def self.find(type, options={})
|
112
|
-
query_options = default_options.merge(options)
|
113
|
-
query_options.merge(:limit => 1) if type == :first # Minor optimization
|
114
|
-
validate_options(query_options)
|
115
|
-
uri_extension = uri_component + build_query(query_options)
|
116
|
-
response = base[uri_extension].get
|
117
|
-
parsed = JSON.parse(response)
|
118
107
|
case type
|
119
|
-
when :first
|
120
|
-
|
121
|
-
when :all
|
122
|
-
|
108
|
+
when :first
|
109
|
+
first(options)
|
110
|
+
when :all
|
111
|
+
all(options)
|
112
|
+
when Integer
|
113
|
+
find_by_id(type)
|
114
|
+
else
|
115
|
+
raise ActsAsIcontact::QueryError, "Don't know how to find '#{type.to_s}'"
|
123
116
|
end
|
124
117
|
end
|
125
118
|
|
126
119
|
# Returns an array of resources starting at the base.
|
127
120
|
def self.all(options={})
|
128
|
-
|
121
|
+
query_options = default_options.merge(options)
|
122
|
+
validate_options(query_options)
|
123
|
+
result = query_collection(query_options)
|
124
|
+
ResourceCollection.new(self, result)
|
129
125
|
end
|
130
126
|
|
131
127
|
# Returns the first account associated with this username.
|
132
128
|
def self.first(options={})
|
133
|
-
|
129
|
+
query_options = default_options.merge(options).merge(:limit => 1) # Minor optimization
|
130
|
+
validate_options(query_options)
|
131
|
+
result = query_collection(query_options)
|
132
|
+
self.new(result[collection_name].first)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the single resource at the URL identified by the passed integer. Takes no options; this is
|
136
|
+
# not a search, this is a single record retrieval. Raises an exception if the record can't be found.
|
137
|
+
def self.find_by_id(id)
|
138
|
+
response = self.connection[id].get
|
139
|
+
parsed = JSON.parse(response)
|
140
|
+
raise ActsAsiContact::QueryError, "iContact's response did not contain a #{resource_name}!" unless parsed[resource_name]
|
141
|
+
self.new(parsed[resource_name])
|
142
|
+
end
|
143
|
+
|
144
|
+
# Two resources are identical if they have exactly the same property array.
|
145
|
+
def ==(obj)
|
146
|
+
properties == obj.properties
|
134
147
|
end
|
135
148
|
|
136
149
|
protected
|
@@ -153,10 +166,10 @@ module ActsAsIcontact
|
|
153
166
|
@properties
|
154
167
|
end
|
155
168
|
|
156
|
-
# The base RestClient resource that this particular class nests from.
|
157
|
-
# the
|
169
|
+
# The base RestClient resource that this particular class nests from. Defaults to
|
170
|
+
# the clientFolders path since that's the most common case.
|
158
171
|
def self.base
|
159
|
-
ActsAsIcontact.
|
172
|
+
ActsAsIcontact.client
|
160
173
|
end
|
161
174
|
|
162
175
|
# The name of the singular resource type pulled from iContact. Defaults to the lowercase
|
@@ -227,11 +240,49 @@ module ActsAsIcontact
|
|
227
240
|
# Validation rules that ensure proper data is passed to iContact on resource creation.
|
228
241
|
def validate_on_create(fields)
|
229
242
|
check_required_fields(fields, self.class.required_on_create)
|
243
|
+
validate_on_save(fields)
|
230
244
|
end
|
231
245
|
|
232
246
|
# Validation rules that ensure proper data is passed to iContact on resource update.
|
233
247
|
def validate_on_update(fields)
|
234
248
|
check_required_fields(fields, self.class.required_on_update)
|
249
|
+
validate_on_save(fields)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Validation rules that apply to both creates and updates. The method on the abstract Resource class is just a placeholder;
|
253
|
+
# this is intended to be used by resource subclasses.
|
254
|
+
def validate_on_save(fields)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Finesses the properties hash passed in to make iContact and Ruby idioms compatible.
|
258
|
+
# Turns symbol keys into strings and runs the clean_value method on values.
|
259
|
+
# Subclasses may add additional conversions.
|
260
|
+
def clean_properties(properties)
|
261
|
+
newhash = {}
|
262
|
+
properties.each_pair do |key, value|
|
263
|
+
newhash[key.to_s] = clean_value(value)
|
264
|
+
end
|
265
|
+
newhash
|
266
|
+
end
|
267
|
+
|
268
|
+
# Finesses values passed in to properties to make iContact and Ruby idioms compatible.
|
269
|
+
# Turns symbols into strings, numbers into integers or floats, true/false into 1 and 0,
|
270
|
+
# and empty strings into nil. Subclasses may add additional conversions.
|
271
|
+
def clean_value(value)
|
272
|
+
case value
|
273
|
+
when Symbol then value.to_s
|
274
|
+
when TrueClass then 1
|
275
|
+
when FalseClass then 0
|
276
|
+
when /^\d+$/ then value.to_i # Integer
|
277
|
+
when /^\d+(\.\d+)?([eE]\d+)?$/ then value.to_f # Float
|
278
|
+
when blank? then nil
|
279
|
+
else value
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# The properties array, for comparison against other resources or debugging.
|
284
|
+
def properties
|
285
|
+
@properties
|
235
286
|
end
|
236
287
|
|
237
288
|
private
|
@@ -241,6 +292,13 @@ module ActsAsIcontact
|
|
241
292
|
build = "?" + terms.join('&')
|
242
293
|
end
|
243
294
|
|
295
|
+
def self.query_collection(options={})
|
296
|
+
uri_extension = uri_component + build_query(options)
|
297
|
+
response = base[uri_extension].get
|
298
|
+
parsed = JSON.parse(response)
|
299
|
+
parsed
|
300
|
+
end
|
301
|
+
|
244
302
|
def check_required_fields(fields, required)
|
245
303
|
# Check that all required fields are filled in
|
246
304
|
missing = required.select{|f| fields[f].blank?}
|
@@ -2,6 +2,11 @@ module ActsAsIcontact
|
|
2
2
|
# The top-level Accounts resource from iContact. Currently only supports retrieval -- and is
|
3
3
|
# highly targeted toward the _first_ account, since that seems to be the dominant use case.
|
4
4
|
class Account < Resource
|
5
|
+
# This is the one major resource that comes directly from the main path.
|
6
|
+
def self.base
|
7
|
+
ActsAsIcontact.connection
|
8
|
+
end
|
9
|
+
|
5
10
|
def self.uri_component
|
6
11
|
'a'
|
7
12
|
end
|
@@ -15,7 +20,7 @@ module ActsAsIcontact
|
|
15
20
|
# The accountId retrieved from iContact. Can also be set manually for performance optimization,
|
16
21
|
# but remembers it so that it won't be pulled more than once anyway.
|
17
22
|
def self.account_id
|
18
|
-
@account_id ||= Account.first.
|
23
|
+
@account_id ||= Account.first.id.to_i
|
19
24
|
end
|
20
25
|
|
21
26
|
# Manually sets the accountId used in subsequent calls. Setting this in your initializer will save
|
@@ -2,6 +2,11 @@ module ActsAsIcontact
|
|
2
2
|
# The nested Client Folder resource from iContact. Currently only supports retrieval -- and is
|
3
3
|
# highly targeted toward the _first_ client folder, since that seems to be the dominant use case.
|
4
4
|
class Client < Resource
|
5
|
+
# Derives from the Account resource.
|
6
|
+
def self.base
|
7
|
+
ActsAsIcontact.account
|
8
|
+
end
|
9
|
+
|
5
10
|
def self.resource_name
|
6
11
|
'clientfolder'
|
7
12
|
end
|
@@ -13,5 +13,12 @@ module ActsAsIcontact
|
|
13
13
|
def self.boolean_fields
|
14
14
|
super << "emailOwnerOnChange" << "welcomeOnManualAdd" << "welcomeOnSignupAdd"
|
15
15
|
end
|
16
|
+
|
17
|
+
# The welcome message pointed to by the welcomeMessageId.
|
18
|
+
def welcomeMessage
|
19
|
+
return nil unless welcomeMessageId
|
20
|
+
ActsAsIcontact::Message.find(welcomeMessageId)
|
21
|
+
end
|
22
|
+
|
16
23
|
end
|
17
24
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActsAsIcontact
|
2
|
+
class Message < Resource
|
3
|
+
# Has a default messageType of "normal" if another isn't passed as an option.
|
4
|
+
def initialize(properties={})
|
5
|
+
super({:messageType => "normal"}.merge(properties))
|
6
|
+
end
|
7
|
+
|
8
|
+
# Requires messageType and subject
|
9
|
+
def self.required_on_create
|
10
|
+
super << "messageType" << "subject"
|
11
|
+
end
|
12
|
+
|
13
|
+
# messageType must be one of four values: normal, autoresponder, welcome, or confirmation
|
14
|
+
def validate_on_save(fields)
|
15
|
+
messageType = %w(normal autoresponder welcome confirmation)
|
16
|
+
raise ActsAsIcontact::ValidationError, "messageType must be one of: " + messageType.join(', ') unless messageType.include?(fields["messageType"])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/config_spec.rb
CHANGED
@@ -41,9 +41,9 @@ describe "Configuration" do
|
|
41
41
|
Object.expects(:const_defined?).with(:Rails).returns(true)
|
42
42
|
end
|
43
43
|
|
44
|
-
it "is
|
44
|
+
it "is sandbox if RAILS_ENV is not production" do
|
45
45
|
ENV["RAILS_ENV"] = 'staging'
|
46
|
-
ActsAsIcontact::Config.mode.should == :
|
46
|
+
ActsAsIcontact::Config.mode.should == :sandbox
|
47
47
|
end
|
48
48
|
|
49
49
|
it "is production if RAILS_ENV is production" do
|
@@ -65,9 +65,9 @@ describe "Configuration" do
|
|
65
65
|
Object.expects(:const_defined?).with(:Rack).returns(true)
|
66
66
|
end
|
67
67
|
|
68
|
-
it "is
|
68
|
+
it "is sandbox if RACK_ENV is not production" do
|
69
69
|
ENV["RACK_ENV"] = 'staging'
|
70
|
-
ActsAsIcontact::Config.mode.should == :
|
70
|
+
ActsAsIcontact::Config.mode.should == :sandbox
|
71
71
|
end
|
72
72
|
|
73
73
|
it "is production if RACK_ENV is production" do
|
@@ -81,17 +81,17 @@ describe "Configuration" do
|
|
81
81
|
end
|
82
82
|
|
83
83
|
|
84
|
-
context ":
|
84
|
+
context ":sandbox" do
|
85
85
|
before(:each) do
|
86
|
-
ActsAsIcontact::Config.mode = :
|
86
|
+
ActsAsIcontact::Config.mode = :sandbox
|
87
87
|
end
|
88
88
|
|
89
|
-
it "returns the
|
89
|
+
it "returns the sandbox AppId" do
|
90
90
|
ActsAsIcontact::Config.app_id.should == "Ml5SnuFhnoOsuZeTOuZQnLUHTbzeUyhx"
|
91
91
|
end
|
92
92
|
|
93
|
-
it "returns the
|
94
|
-
ActsAsIcontact::Config.url.should == "https://app.
|
93
|
+
it "returns the sandbox URL" do
|
94
|
+
ActsAsIcontact::Config.url.should == "https://app.sandbox.icontact.com/icp/"
|
95
95
|
end
|
96
96
|
|
97
97
|
end
|
data/spec/resource_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
2
|
|
3
3
|
describe ActsAsIcontact::Resource do
|
4
4
|
it "has a RestClient connection" do
|
5
|
-
ActsAsIcontact::Resource.connection.url.should == ActsAsIcontact.
|
5
|
+
ActsAsIcontact::Resource.connection.url.should == ActsAsIcontact.client['resources'].url
|
6
6
|
end
|
7
7
|
|
8
8
|
it "can return all resources for the given URL" do
|
@@ -66,15 +66,20 @@ describe ActsAsIcontact::Resource do
|
|
66
66
|
end
|
67
67
|
|
68
68
|
it "maps the 'first' method to find(:first)" do
|
69
|
-
ActsAsIcontact::Resource.expects(:
|
70
|
-
ActsAsIcontact::Resource.
|
69
|
+
ActsAsIcontact::Resource.expects(:first).with({:foo=>:bar}).returns(nil)
|
70
|
+
ActsAsIcontact::Resource.find(:first, :foo=>:bar)
|
71
71
|
end
|
72
72
|
|
73
73
|
it "maps the 'all' method to find(:all)" do
|
74
|
-
ActsAsIcontact::Resource.expects(:
|
75
|
-
ActsAsIcontact::Resource.
|
74
|
+
ActsAsIcontact::Resource.expects(:all).with({:foo=>:bar}).returns(nil)
|
75
|
+
ActsAsIcontact::Resource.find(:all, :foo=>:bar)
|
76
76
|
end
|
77
77
|
|
78
|
+
it "can find a single resource by ID" do
|
79
|
+
a = ActsAsIcontact::Resource.find(1)
|
80
|
+
a.too.should == "sar"
|
81
|
+
end
|
82
|
+
|
78
83
|
end
|
79
84
|
|
80
85
|
it "knows its properties" do
|
@@ -94,7 +99,7 @@ describe ActsAsIcontact::Resource do
|
|
94
99
|
|
95
100
|
it "has its own connection if it's not new" do
|
96
101
|
r = ActsAsIcontact::Resource.first
|
97
|
-
r.connection.url.should == ActsAsIcontact.
|
102
|
+
r.connection.url.should == ActsAsIcontact.client['resources/1'].url
|
98
103
|
end
|
99
104
|
|
100
105
|
it "does not have a connection if it's new" do
|
@@ -103,7 +108,7 @@ describe ActsAsIcontact::Resource do
|
|
103
108
|
end
|
104
109
|
|
105
110
|
it "knows its REST base resource" do
|
106
|
-
ActsAsIcontact::Resource.base.should == ActsAsIcontact.
|
111
|
+
ActsAsIcontact::Resource.base.should == ActsAsIcontact.client
|
107
112
|
end
|
108
113
|
|
109
114
|
it "knows its primary key" do
|
@@ -118,7 +123,16 @@ describe ActsAsIcontact::Resource do
|
|
118
123
|
ActsAsIcontact::Resource.never_on_create.should == ["resourceId"]
|
119
124
|
end
|
120
125
|
|
121
|
-
|
126
|
+
it "accepts symbols for properties on creation" do
|
127
|
+
a = ActsAsIcontact::Resource.new(:foofoo => "bunny")
|
128
|
+
a.foofoo.should == "bunny"
|
129
|
+
end
|
130
|
+
|
131
|
+
it "typecasts all integer property values if it can" do
|
132
|
+
a = ActsAsIcontact::Resource.new("indianaPi" => "3")
|
133
|
+
a.indianaPi.should == 3
|
134
|
+
end
|
135
|
+
|
122
136
|
context "updating records" do
|
123
137
|
before(:each) do
|
124
138
|
@res = ActsAsIcontact::Resource.first
|
@@ -139,7 +153,7 @@ describe ActsAsIcontact::Resource do
|
|
139
153
|
|
140
154
|
it "knows the minimum set of properties that changed or must be sent" do
|
141
155
|
@res.too = "tar"
|
142
|
-
@res.send(:update_fields).should == {"resourceId" =>
|
156
|
+
@res.send(:update_fields).should == {"resourceId" => 1, "too" => "tar"}
|
143
157
|
end
|
144
158
|
|
145
159
|
it "throws an exception if required fields aren't included" do
|
@@ -249,7 +263,7 @@ describe ActsAsIcontact::Resource do
|
|
249
263
|
|
250
264
|
context "with successful save" do
|
251
265
|
before(:each) do
|
252
|
-
FakeWeb.register_uri(:post, "https://app.
|
266
|
+
FakeWeb.register_uri(:post, "https://app.sandbox.icontact.com/icp/a/111111/c/222222/resources", :body => %q<{"resources":[{"resourceId":"100","foo":"flar","kroo":"krar","too":"sar"}]}>)
|
253
267
|
@res.too = "sar"
|
254
268
|
end
|
255
269
|
|
@@ -279,7 +293,7 @@ describe ActsAsIcontact::Resource do
|
|
279
293
|
|
280
294
|
context "with failed save but status 200" do
|
281
295
|
before(:each) do
|
282
|
-
FakeWeb.register_uri(:post, "https://app.
|
296
|
+
FakeWeb.register_uri(:post, "https://app.sandbox.icontact.com/icp/a/111111/c/222222/resources", :body => %q<{"resources":[],"warnings":["You did not provide a foo. foo is a required field. Please provide a foo","This was not a good record"]}>)
|
283
297
|
@res = ActsAsIcontact::Resource.new
|
284
298
|
@res.foo = nil
|
285
299
|
@result = @res.save
|
@@ -304,7 +318,7 @@ describe ActsAsIcontact::Resource do
|
|
304
318
|
|
305
319
|
context "with failed save on HTTP failure exception" do
|
306
320
|
before(:each) do
|
307
|
-
FakeWeb.register_uri(:post, "https://app.
|
321
|
+
FakeWeb.register_uri(:post, "https://app.sandbox.icontact.com/icp/a/111111/c/222222/resources", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
|
308
322
|
@res = ActsAsIcontact::Resource.new
|
309
323
|
@res.foo = nil
|
310
324
|
@result = @res.save
|
data/spec/resources/list_spec.rb
CHANGED
@@ -27,6 +27,9 @@ describe ActsAsIcontact::List do
|
|
27
27
|
@list = ActsAsIcontact::List.first(:name => "First Test")
|
28
28
|
end
|
29
29
|
it "knows its subscribers"
|
30
|
-
|
30
|
+
|
31
|
+
it "knows its welcome message" do
|
32
|
+
@list.welcomeMessage.should == ActsAsIcontact::Message.find(555555)
|
33
|
+
end
|
31
34
|
end
|
32
35
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe ActsAsIcontact::Message do
|
4
|
+
|
5
|
+
it "defaults messageType to normal" do
|
6
|
+
m = ActsAsIcontact::Message.new
|
7
|
+
m.messageType.should == "normal"
|
8
|
+
end
|
9
|
+
|
10
|
+
it "requires messageType and subject" do
|
11
|
+
m = ActsAsIcontact::Message.new(:messageType => nil)
|
12
|
+
lambda{m.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: messageType, subject")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "must have an acceptable messageType" do
|
16
|
+
m = ActsAsIcontact::Message.new(:messageType => "dummy", :subject => "test")
|
17
|
+
lambda{m.save}.should raise_error(ActsAsIcontact::ValidationError, "messageType must be one of: normal, autoresponder, welcome, confirmation")
|
18
|
+
end
|
19
|
+
|
20
|
+
context "associations" do
|
21
|
+
before(:each) do
|
22
|
+
@message = ActsAsIcontact::Message.first(:subject => "Test Message")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "knows which campaign it has (if any)"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/spec/spec_fakeweb.rb
CHANGED
@@ -2,31 +2,41 @@ require 'rubygems'
|
|
2
2
|
require 'fakeweb'
|
3
3
|
|
4
4
|
FakeWeb.allow_net_connect = false
|
5
|
-
i = "https://app.
|
5
|
+
i = "https://app.sandbox.icontact.com/icp"
|
6
6
|
ic = "#{i}/a/111111/c/222222"
|
7
7
|
|
8
8
|
# Resources (this one's a fake stub for pure testing)
|
9
|
-
FakeWeb.register_uri(:get, "#{
|
10
|
-
FakeWeb.register_uri(:get, "#{
|
11
|
-
FakeWeb.register_uri(:get, "#{
|
12
|
-
FakeWeb.register_uri(:get, "#{
|
13
|
-
FakeWeb.register_uri(:get, "#{
|
14
|
-
FakeWeb.register_uri(:
|
15
|
-
FakeWeb.register_uri(:
|
16
|
-
FakeWeb.register_uri(:post, "#{
|
9
|
+
FakeWeb.register_uri(:get, "#{ic}/resources?limit=500", :body => %q<{"resources":[{"foo":"bar","resourceId":"1","too":"bar"},{"foo":"aar","resourceId":"2"},{"foo":"far","resourceId":"3"},{"foo":"car","resourceId":"4"},{"foo":"dar","resourceId":"5"},{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"},{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":20,"offset":0}>)
|
10
|
+
FakeWeb.register_uri(:get, "#{ic}/resources?limit=1", :body => %q<{"resources":[{"foo":"bar","resourceId":"1","too":"bar"},{"foo":"aar","resourceId":"2"},{"foo":"far","resourceId":"3"},{"foo":"car","resourceId":"4"},{"foo":"dar","resourceId":"5"},{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"},{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":20,"offset":0}>)
|
11
|
+
FakeWeb.register_uri(:get, "#{ic}/resources?limit=5", :body => %q<{"resources":[{"foo":"bar","resourceId":"1"},{"foo":"aar","resourceId":"2"},{"foo":"far","resourceId":"3"},{"foo":"car","resourceId":"4"},{"foo":"dar","resourceId":"5"}],"total":12,"limit":5,"offset":0}>)
|
12
|
+
FakeWeb.register_uri(:get, "#{ic}/resources?limit=500&offset=5", :body => %q<{"resources":[{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"},{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":20,"offset":5}>)
|
13
|
+
FakeWeb.register_uri(:get, "#{ic}/resources?offset=5&limit=5", :body => %q<{"resources":[{"foo":"ear","resourceId":"6"},{"foo":"gar","resourceId":"7"},{"foo":"har","resourceId":"8"},{"foo":"iar","resourceId":"9"},{"foo":"jar","resourceId":"10"}],"total":12,"limit":5,"offset":5}>)
|
14
|
+
FakeWeb.register_uri(:get, "#{ic}/resources?offset=10&limit=5", :body => %q<{"resources":[{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":5,"offset":10}>)
|
15
|
+
FakeWeb.register_uri(:get, "#{ic}/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
|
16
|
+
FakeWeb.register_uri(:post, "#{ic}/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
|
17
|
+
FakeWeb.register_uri(:post, "#{ic}/resources/2", :body => %q<{"resource":{},"warnings":["You did not provide a foo. foo is a required field. Please provide a foo","This was not a good record"]}>)
|
18
|
+
FakeWeb.register_uri(:post, "#{ic}/resources/3", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
|
17
19
|
|
18
20
|
# Time
|
19
21
|
FakeWeb.register_uri(:get, "#{i}/time", :body => %q<{"time":"2009-07-13T01:28:18-04:00","timestamp":1247462898}>)
|
20
22
|
|
21
23
|
# Accounts
|
24
|
+
FakeWeb.register_uri(:get, "#{i}/a?limit=1", :body => %q<{"accounts":[{"billingStreet":"","billingCity":"","billingState":"","billingPostalCode":"","billingCountry":"","city":"Testville","accountId":"111111","companyName":"","country":"United States","email":"bob@example.org","enabled":1,"fax":"","firstName":"Bob","lastName":"Tester","multiClientFolder":"0","multiUser":"0","phone":"","postalCode":"12345","state":"TN","street":"123 Test Street","title":"","accountType":"0","subscriberLimit":"250000"}],"total":1,"limit":20,"offset":0}>)
|
22
25
|
FakeWeb.register_uri(:get, "#{i}/a?limit=500", :body => %q<{"accounts":[{"billingStreet":"","billingCity":"","billingState":"","billingPostalCode":"","billingCountry":"","city":"Testville","accountId":"111111","companyName":"","country":"United States","email":"bob@example.org","enabled":1,"fax":"","firstName":"Bob","lastName":"Tester","multiClientFolder":"0","multiUser":"0","phone":"","postalCode":"12345","state":"TN","street":"123 Test Street","title":"","accountType":"0","subscriberLimit":"250000"}],"total":1,"limit":20,"offset":0}>)
|
23
26
|
|
24
27
|
# Clients
|
28
|
+
FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=1", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
|
25
29
|
FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=500", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
|
26
30
|
|
27
31
|
# Contacts
|
28
|
-
FakeWeb.register_uri(:get, "#{ic}/contacts?limit=
|
32
|
+
FakeWeb.register_uri(:get, "#{ic}/contacts?limit=1&status=total&firstName=John&lastName=Test", :body => %q<{"contacts":[{"email":"john@example.org","firstName":"John","lastName":"Test","status":"normal","contactId":"333333","createDate":"2009-07-24 01:00:00"}]}>)
|
29
33
|
|
30
34
|
# Lists
|
31
|
-
FakeWeb.register_uri(:get, "#{ic}/lists?limit=
|
35
|
+
FakeWeb.register_uri(:get, "#{ic}/lists?limit=1&name=First%20Test", :body => %q<{"lists":[{"listId":"444444","name":"First Test","emailOwnerOnChange":"0","welcomeOnManualAdd":"0","welcomeOnSignupAdd":"0","welcomeMessageId":"555555","description":"Just a test list."}]}>)
|
32
36
|
|
37
|
+
# Message
|
38
|
+
#### Test message for List association
|
39
|
+
FakeWeb.register_uri(:get, "#{ic}/messages/555555", :body => %q<{"message":{"messageId":"555555","subject":"Welcome!","messageType":"welcome","textBody":"Welcome to the Test List!","htmlBody":"<p>Welcome to the <b>Test List</b>!</p>","createDate":"20090725 14:55:12"}}>)
|
40
|
+
|
41
|
+
#### Test message for associations originating from Message spec
|
42
|
+
FakeWeb.register_uri(:get, "#{ic}/messages?limit=1&subject=Test%20Message", :body => %q<{"messages":[{"messageId":"666666","subject":"Test Message","messageType":"normal","textBody":"Hi there!\nThis is just a test.","htmlBody":"<p><b>Hi there!</b></p><p>This is just a <i>test.</i></p>","createDate":"20090725 14:53:33"}]}>)
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: SFEley-acts_as_icontact
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Eley
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-25 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -39,6 +39,7 @@ files:
|
|
39
39
|
- lib/acts_as_icontact/resources/client.rb
|
40
40
|
- lib/acts_as_icontact/resources/contact.rb
|
41
41
|
- lib/acts_as_icontact/resources/list.rb
|
42
|
+
- lib/acts_as_icontact/resources/message.rb
|
42
43
|
- spec/config_spec.rb
|
43
44
|
- spec/connection_spec.rb
|
44
45
|
- spec/resource_collection_spec.rb
|
@@ -47,6 +48,7 @@ files:
|
|
47
48
|
- spec/resources/client_spec.rb
|
48
49
|
- spec/resources/contact_spec.rb
|
49
50
|
- spec/resources/list_spec.rb
|
51
|
+
- spec/resources/message_spec.rb
|
50
52
|
- spec/spec.opts
|
51
53
|
- spec/spec_fakeweb.rb
|
52
54
|
- spec/spec_helper.rb
|
@@ -85,5 +87,6 @@ test_files:
|
|
85
87
|
- spec/resources/client_spec.rb
|
86
88
|
- spec/resources/contact_spec.rb
|
87
89
|
- spec/resources/list_spec.rb
|
90
|
+
- spec/resources/message_spec.rb
|
88
91
|
- spec/spec_fakeweb.rb
|
89
92
|
- spec/spec_helper.rb
|