SFEley-acts_as_icontact 0.1.3 → 0.1.4

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.3
1
+ 0.1.4
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{acts_as_icontact}
5
- s.version = "0.1.3"
5
+ s.version = "0.1.4"
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"]
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  "lib/acts_as_icontact/resources/account.rb",
34
34
  "lib/acts_as_icontact/resources/client.rb",
35
35
  "lib/acts_as_icontact/resources/contact.rb",
36
+ "lib/acts_as_icontact/resources/list.rb",
36
37
  "spec/config_spec.rb",
37
38
  "spec/connection_spec.rb",
38
39
  "spec/resource_collection_spec.rb",
@@ -40,6 +41,7 @@ Gem::Specification.new do |s|
40
41
  "spec/resources/account_spec.rb",
41
42
  "spec/resources/client_spec.rb",
42
43
  "spec/resources/contact_spec.rb",
44
+ "spec/resources/list_spec.rb",
43
45
  "spec/spec.opts",
44
46
  "spec/spec_fakeweb.rb",
45
47
  "spec/spec_helper.rb"
@@ -58,6 +60,7 @@ Gem::Specification.new do |s|
58
60
  "spec/resources/account_spec.rb",
59
61
  "spec/resources/client_spec.rb",
60
62
  "spec/resources/contact_spec.rb",
63
+ "spec/resources/list_spec.rb",
61
64
  "spec/spec_fakeweb.rb",
62
65
  "spec/spec_helper.rb"
63
66
  ]
@@ -2,6 +2,12 @@ module ActsAsIcontact
2
2
  # Thrown when a configuration value isn't provided or is invalid.
3
3
  class ConfigError < StandardError; end
4
4
 
5
+ # Thrown when a bad parameter is passed to a resource find.
6
+ class QueryError < StandardError; end
7
+
8
+ # Thrown before saving if iContact validation rules are not met.
9
+ class ValidationError < StandardError; end
10
+
5
11
  # Thrown when a resource calls save! and fails. Contains the +.errors+ array from
6
12
  # the resource.
7
13
  class RecordNotSaved < StandardError
@@ -33,10 +33,18 @@ module ActsAsIcontact
33
33
  if property =~ /(.*)=$/ # It's a value assignment
34
34
  @newvalues ||= []
35
35
  @newvalues << $1
36
- @properties[$1] = params[0]
36
+ if self.class.boolean_fields.include?($1)
37
+ @properties[$1] = params[0] ? 1 : 0
38
+ else
39
+ @properties[$1] = params[0]
40
+ end
37
41
  else
38
42
  if @properties.has_key?(property)
39
- @properties[property]
43
+ if self.class.boolean_fields.include?(property)
44
+ (@properties[property] == 1)
45
+ else
46
+ @properties[property]
47
+ end
40
48
  else
41
49
  super
42
50
  end
@@ -56,11 +64,15 @@ module ActsAsIcontact
56
64
  # error, raises an exception with it.
57
65
  def save
58
66
  if new_record?
67
+ fields = create_fields
68
+ validate_on_create(fields)
59
69
  result_type = self.class.collection_name
60
- response = self.class.connection.post([create_fields].to_json)
70
+ response = self.class.connection.post([fields].to_json)
61
71
  else
72
+ fields = update_fields
73
+ validate_on_update(fields)
62
74
  result_type = self.class.resource_name
63
- response = connection.post(update_fields.to_json)
75
+ response = connection.post(fields.to_json)
64
76
  end
65
77
  parsed = JSON.parse(response)
66
78
  if parsed[result_type].empty?
@@ -97,7 +109,10 @@ module ActsAsIcontact
97
109
 
98
110
  # Returns an array of resources starting at the base.
99
111
  def self.find(type, options={})
100
- uri_extension = uri_component + build_query(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)
101
116
  response = base[uri_extension].get
102
117
  parsed = JSON.parse(response)
103
118
  case type
@@ -109,13 +124,13 @@ module ActsAsIcontact
109
124
  end
110
125
 
111
126
  # Returns an array of resources starting at the base.
112
- def self.all
113
- find(:all)
127
+ def self.all(options={})
128
+ find(:all, options)
114
129
  end
115
130
 
116
131
  # Returns the first account associated with this username.
117
- def self.first
118
- find(:first)
132
+ def self.first(options={})
133
+ find(:first, options)
119
134
  end
120
135
 
121
136
  protected
@@ -173,6 +188,11 @@ module ActsAsIcontact
173
188
  resource_name + "Id"
174
189
  end
175
190
 
191
+ # Options that are always passed on 'find' requests unless overridden.
192
+ def self.default_options
193
+ {:limit => 500}
194
+ end
195
+
176
196
  # Fields that _must_ be included for this resource upon creation.
177
197
  def self.required_on_create
178
198
  []
@@ -193,11 +213,41 @@ module ActsAsIcontact
193
213
  []
194
214
  end
195
215
 
216
+ # Fields that operate as 0/1 boolean toggles. Can be assigned to with true and false.
217
+ def self.boolean_fields
218
+ []
219
+ end
220
+
221
+ # Validation rules that ensure proper parameters are passed to iContact on querying.
222
+ def self.validate_options(options)
223
+ # See: http://developer.icontact.com/forums/api-beta-moderated-support/there-upper-limit-result-sets#comment-136
224
+ raise ActsAsIcontact::QueryError, "Limit must be between 1 and 500" if options[:limit].to_i < 1 or options[:limit].to_i > 500
225
+ end
226
+
227
+ # Validation rules that ensure proper data is passed to iContact on resource creation.
228
+ def validate_on_create(fields)
229
+ check_required_fields(fields, self.class.required_on_create)
230
+ end
231
+
232
+ # Validation rules that ensure proper data is passed to iContact on resource update.
233
+ def validate_on_update(fields)
234
+ check_required_fields(fields, self.class.required_on_update)
235
+ end
236
+
196
237
  private
197
238
  def self.build_query(options={})
198
239
  return "" if options.empty?
199
240
  terms = options.collect{|k,v| "#{k}=#{URI.escape(v.to_s)}"}
200
241
  build = "?" + terms.join('&')
201
242
  end
243
+
244
+ def check_required_fields(fields, required)
245
+ # Check that all required fields are filled in
246
+ missing = required.select{|f| fields[f].blank?}
247
+ unless missing.empty?
248
+ missing_fields = missing.join(', ')
249
+ raise ActsAsIcontact::ValidationError, "Missing required fields: #{missing_fields}"
250
+ end
251
+ end
202
252
  end
203
253
  end
@@ -1,11 +1,19 @@
1
1
  module ActsAsIcontact
2
2
  class Contact < Resource
3
+
4
+ # Email is required
3
5
  def self.required_on_create
4
- ['email']
6
+ super << ['email']
5
7
  end
6
8
 
9
+ # Derived from clientFolder
7
10
  def self.base
8
- ActsAsIcontact::client
11
+ ActsAsIcontact.client
12
+ end
13
+
14
+ # Defaults to status=total to return contacts on or off lists
15
+ def self.default_options
16
+ super.merge(:status=>:total)
9
17
  end
10
18
  end
11
19
  end
@@ -0,0 +1,17 @@
1
+ module ActsAsIcontact
2
+ class List < Resource
3
+ # Derives from clientFolder.
4
+ def self.base
5
+ ActsAsIcontact.client
6
+ end
7
+
8
+ # Requires name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, and welcomeMessageId.
9
+ def self.required_on_create
10
+ super << "name" << "emailOwnerOnChange" << "welcomeOnManualAdd" << "welcomeOnSignupAdd" << "welcomeMessageId"
11
+ end
12
+
13
+ def self.boolean_fields
14
+ super << "emailOwnerOnChange" << "welcomeOnManualAdd" << "welcomeOnSignupAdd"
15
+ end
16
+ end
17
+ end
@@ -51,6 +51,30 @@ describe ActsAsIcontact::Resource do
51
51
  r.first.foo.should == "kar"
52
52
  r[1].foo.should == "yar"
53
53
  end
54
+
55
+ it "defaults to a limit of 500" do
56
+ ActsAsIcontact::Resource.base.expects(:[]).with(regexp_matches(/limit=500/)).returns(stub(:get => '{"resources":[]}'))
57
+ r = ActsAsIcontact::Resource.find(:all)
58
+ end
59
+
60
+ it "throws an exception if a limit higher than 500 is attempted" do
61
+ lambda{r = ActsAsIcontact::Resource.find(:all, :limit => 501)}.should raise_error(ActsAsIcontact::QueryError, "Limit must be between 1 and 500")
62
+ end
63
+
64
+ it "throws an exception if a limit lower than 500 is attempted" do
65
+ lambda{r = ActsAsIcontact::Resource.find(:all, :limit => 501)}.should raise_error(ActsAsIcontact::QueryError, "Limit must be between 1 and 500")
66
+ end
67
+
68
+ it "maps the 'first' method to find(:first)" do
69
+ ActsAsIcontact::Resource.expects(:find).with(:first,{:foo=>:bar}).returns(nil)
70
+ ActsAsIcontact::Resource.first(:foo=>:bar)
71
+ end
72
+
73
+ it "maps the 'all' method to find(:all)" do
74
+ ActsAsIcontact::Resource.expects(:find).with(:all,{:foo=>:bar}).returns(nil)
75
+ ActsAsIcontact::Resource.all(:foo=>:bar)
76
+ end
77
+
54
78
  end
55
79
 
56
80
  it "knows its properties" do
@@ -94,6 +118,7 @@ describe ActsAsIcontact::Resource do
94
118
  ActsAsIcontact::Resource.never_on_create.should == ["resourceId"]
95
119
  end
96
120
 
121
+
97
122
  context "updating records" do
98
123
  before(:each) do
99
124
  @res = ActsAsIcontact::Resource.first
@@ -117,6 +142,11 @@ describe ActsAsIcontact::Resource do
117
142
  @res.send(:update_fields).should == {"resourceId" => "1", "too" => "tar"}
118
143
  end
119
144
 
145
+ it "throws an exception if required fields aren't included" do
146
+ @res.resourceId = nil
147
+ lambda{@res.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: resourceId")
148
+ end
149
+
120
150
  context "with successful save" do
121
151
  before(:each) do
122
152
  @res.too = "sar"
@@ -1,8 +1,25 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe ActsAsIcontact::Contact do
4
- it "defaults to a limit of 500"
5
- it "defaults to searching on all contacts regardless of list status"
6
- it "throws an exception if a limit higher than 500 is attempted"
7
4
 
5
+ it "defaults to searching on all contacts regardless of list status" do
6
+ ActsAsIcontact::Contact.base.expects(:[]).with(regexp_matches(/status=total/)).returns(stub(:get => '{"contacts":[]}'))
7
+ r = ActsAsIcontact::Contact.find(:all)
8
+ end
9
+
10
+ it "requires email address" do
11
+ c = ActsAsIcontact::Contact.new
12
+ lambda{c.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: email")
13
+ end
14
+
15
+ context "associations" do
16
+ # We have _one_ really good contact set up here
17
+ before(:each) do
18
+ @john = ActsAsIcontact::Contact.first(:firstName => "John", :lastName => "Test")
19
+ end
20
+
21
+ it "knows which lists it's subscribed to"
22
+ it "knows its history"
23
+ end
24
+
8
25
  end
@@ -0,0 +1,32 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ActsAsIcontact::List do
4
+ it "requires name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, welcomeMessageId" do
5
+ l = ActsAsIcontact::List.new
6
+ lambda{l.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, welcomeMessageId")
7
+ end
8
+
9
+ it "uses true and false to assign boolean fields" do
10
+ l = ActsAsIcontact::List.new
11
+ l.emailOwnerOnChange = true
12
+ l.welcomeOnSignupAdd = false
13
+ l.instance_variable_get(:@properties)["emailOwnerOnChange"].should == 1
14
+ l.instance_variable_get(:@properties)["welcomeOnSignupAdd"].should == 0
15
+ end
16
+
17
+ it "uses true and false to retrieve boolean fields" do
18
+ l = ActsAsIcontact::List.new
19
+ l.instance_variable_set(:@properties,{"welcomeOnManualAdd" => 1, "emailOwnerOnChange" => 0})
20
+ l.emailOwnerOnChange.should be_false
21
+ l.welcomeOnManualAdd.should be_true
22
+ end
23
+
24
+ context "associations" do
25
+ # Create one good list
26
+ before(:each) do
27
+ @list = ActsAsIcontact::List.first(:name => "First Test")
28
+ end
29
+ it "knows its subscribers"
30
+ it "knows its welcome message"
31
+ end
32
+ end
data/spec/spec_fakeweb.rb CHANGED
@@ -2,22 +2,31 @@ require 'rubygems'
2
2
  require 'fakeweb'
3
3
 
4
4
  FakeWeb.allow_net_connect = false
5
+ i = "https://app.beta.icontact.com/icp"
6
+ ic = "#{i}/a/111111/c/222222"
5
7
 
6
8
  # Resources (this one's a fake stub for pure testing)
7
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/resources", :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}>)
8
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/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}>)
9
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/resources?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}>)
10
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/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}>)
11
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/resources?offset=10&limit=5", :body => %q<{"resources":[{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":5,"offset":10}>)
12
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
13
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/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"]}>)
14
- FakeWeb.register_uri(:post, "https://app.beta.icontact.com/icp/resources/3", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
9
+ FakeWeb.register_uri(:get, "#{i}/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, "#{i}/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}>)
11
+ FakeWeb.register_uri(:get, "#{i}/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}>)
12
+ FakeWeb.register_uri(:get, "#{i}/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}>)
13
+ FakeWeb.register_uri(:get, "#{i}/resources?offset=10&limit=5", :body => %q<{"resources":[{"foo":"kar","resourceId":"11"},{"foo":"yar","resourceId":"12"}],"total":12,"limit":5,"offset":10}>)
14
+ FakeWeb.register_uri(:post, "#{i}/resources/1", :body => %q<{"resource":{"foo":"bar","resourceId":"1","too":"sar"}}>)
15
+ FakeWeb.register_uri(:post, "#{i}/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"]}>)
16
+ FakeWeb.register_uri(:post, "#{i}/resources/3", :status => ["400","Bad Request"], :body => %q<{"errors":["You did not provide a clue. Clue is a required field. Please provide a clue"]}>)
15
17
 
16
18
  # Time
17
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/time", :body => %q<{"time":"2009-07-13T01:28:18-04:00","timestamp":1247462898}>)
19
+ FakeWeb.register_uri(:get, "#{i}/time", :body => %q<{"time":"2009-07-13T01:28:18-04:00","timestamp":1247462898}>)
18
20
 
19
21
  # Accounts
20
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/a", :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
+ 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}>)
21
23
 
22
24
  # Clients
23
- FakeWeb.register_uri(:get, "https://app.beta.icontact.com/icp/a/111111/c", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
25
+ FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=500", :body => %q<{"clientfolders":[{"clientFolderId":"222222","logoId":null,"emailRecipient":"bob@example.org"}],"total":1}>)
26
+
27
+ # Contacts
28
+ FakeWeb.register_uri(:get, "#{ic}/contacts?limit=500&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
+
30
+ # Lists
31
+ FakeWeb.register_uri(:get, "#{ic}/lists?limit=500&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
+
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.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Eley
@@ -38,6 +38,7 @@ files:
38
38
  - lib/acts_as_icontact/resources/account.rb
39
39
  - lib/acts_as_icontact/resources/client.rb
40
40
  - lib/acts_as_icontact/resources/contact.rb
41
+ - lib/acts_as_icontact/resources/list.rb
41
42
  - spec/config_spec.rb
42
43
  - spec/connection_spec.rb
43
44
  - spec/resource_collection_spec.rb
@@ -45,6 +46,7 @@ files:
45
46
  - spec/resources/account_spec.rb
46
47
  - spec/resources/client_spec.rb
47
48
  - spec/resources/contact_spec.rb
49
+ - spec/resources/list_spec.rb
48
50
  - spec/spec.opts
49
51
  - spec/spec_fakeweb.rb
50
52
  - spec/spec_helper.rb
@@ -82,5 +84,6 @@ test_files:
82
84
  - spec/resources/account_spec.rb
83
85
  - spec/resources/client_spec.rb
84
86
  - spec/resources/contact_spec.rb
87
+ - spec/resources/list_spec.rb
85
88
  - spec/spec_fakeweb.rb
86
89
  - spec/spec_helper.rb