SFEley-acts_as_icontact 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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