acts_as_icontact 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.2.3
@@ -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.2.1"
5
+ s.version = "0.2.3"
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"]
@@ -44,6 +44,7 @@ Gem::Specification.new do |s|
44
44
  "lib/acts_as_icontact/resources/custom_field.rb",
45
45
  "lib/acts_as_icontact/resources/list.rb",
46
46
  "lib/acts_as_icontact/resources/message.rb",
47
+ "lib/acts_as_icontact/resources/subscription.rb",
47
48
  "rails/init.rb",
48
49
  "spec/config_spec.rb",
49
50
  "spec/connection_spec.rb",
@@ -59,6 +60,7 @@ Gem::Specification.new do |s|
59
60
  "spec/resources/custom_field_spec.rb",
60
61
  "spec/resources/list_spec.rb",
61
62
  "spec/resources/message_spec.rb",
63
+ "spec/resources/subscription_spec.rb",
62
64
  "spec/spec.opts",
63
65
  "spec/spec_helper.rb",
64
66
  "spec/support/active_record/connection_adapters/nulldb_adapter.rb",
@@ -86,6 +88,7 @@ Gem::Specification.new do |s|
86
88
  "spec/resources/custom_field_spec.rb",
87
89
  "spec/resources/list_spec.rb",
88
90
  "spec/resources/message_spec.rb",
91
+ "spec/resources/subscription_spec.rb",
89
92
  "spec/spec_helper.rb",
90
93
  "spec/support/active_record/connection_adapters/nulldb_adapter.rb",
91
94
  "spec/support/nulldb_rspec.rb",
@@ -266,7 +266,7 @@ module ActsAsIcontact
266
266
  # Finesses the properties hash passed in to make iContact and Ruby idioms compatible.
267
267
  # Turns symbol keys into strings and runs the clean_value method on values.
268
268
  # Subclasses may add additional conversions.
269
- def clean_properties(properties)
269
+ def clean_properties(properties={})
270
270
  newhash = {}
271
271
  properties.each_pair do |key, value|
272
272
  newhash[key.to_s] = clean_value(value)
@@ -1,17 +1,20 @@
1
1
  module ActsAsIcontact
2
2
  class ResourceCollection < Enumerator
3
- attr_reader :total
3
+ attr_reader :total, :retrieved
4
4
 
5
- def initialize(klass, collection)
5
+ def initialize(klass, collection, forwardTo=nil)
6
6
  @klass = klass
7
+ @forwardTo = forwardTo
8
+
7
9
  @collection = collection[klass.collection_name]
8
10
  # Get number of elements
9
- @total = @collection.size
11
+ @retrieved = @collection.size
12
+ @total = collection["total"]
10
13
 
11
14
  enumcode = Proc.new do |yielder|
12
15
  counter = 0
13
- while counter < @total
14
- yielder.yield klass.new(@collection[counter])
16
+ while counter < @retrieved
17
+ yielder.yield resource(@collection[counter])
15
18
  counter += 1
16
19
  end
17
20
  end
@@ -20,8 +23,23 @@ module ActsAsIcontact
20
23
  end
21
24
 
22
25
  def [](index)
23
- @klass.new(@collection[index]) if @collection[index]
26
+ resource(@collection[index]) if @collection[index]
27
+ end
28
+
29
+ # Calls "next" to kick off the enumerator. This is more in line with what users would expect.
30
+ def first
31
+ self.rewind
32
+ self.next
24
33
  end
25
34
 
35
+ private
36
+ def resource(properties)
37
+ if @forwardTo
38
+ id = @forwardTo.primary_key
39
+ @forwardTo.find(properties[id])
40
+ else
41
+ @klass.new(properties)
42
+ end
43
+ end
26
44
  end
27
45
  end
@@ -15,6 +15,11 @@ module ActsAsIcontact
15
15
  def self.default_options
16
16
  super.merge(:status=>:total)
17
17
  end
18
+
19
+ # Returns the lists to which this contact is subscribed (via the Subscription class).
20
+ def lists
21
+ @lists ||= ActsAsIcontact::Subscription.lists(:contactId => id)
22
+ end
18
23
 
19
24
  end
20
25
  end
@@ -25,5 +25,10 @@ module ActsAsIcontact
25
25
  ActsAsIcontact::Message.find(welcomeMessageId)
26
26
  end
27
27
 
28
+ # Returns the contacts subscribed to this list (via the Subscription class).
29
+ def subscribers
30
+ @subscribers ||= ActsAsIcontact::Subscription.contacts(:listId => id)
31
+ end
32
+
28
33
  end
29
34
  end
@@ -0,0 +1,75 @@
1
+ module ActsAsIcontact
2
+
3
+ # Subscriptions are effectively a "has_many :through" mapping between Contacts and Lists. They are implemented
4
+ # that way, with a .lists property on Contact and a .subscribers property on List that both work through the
5
+ # Subscription class.
6
+ class Subscription < Resource
7
+
8
+ STATUSES = %w(normal pending unsubscribed)
9
+
10
+ # Defaults status to "normal"
11
+ def initialize(properties = {})
12
+ super({:status => "normal"}.merge(properties))
13
+ end
14
+
15
+ # Searches on the compound primary key ("listid_contactid").
16
+ def self.find_by_string(value)
17
+ find_by_id(value)
18
+ end
19
+
20
+ # Requires listId, contactId, and status when creating a record
21
+ def self.required_on_create
22
+ super + %w(listId contactId status)
23
+ end
24
+
25
+ # Never send listId or contactId on update
26
+ def self.never_on_update
27
+ super + %w(listId contactId)
28
+ end
29
+
30
+
31
+ # Overrides "all" to pass an optional forwardTo parameter to ResourceCollection. This instructs ResourceCollection
32
+ # to look up and create a new object of the given type, rather than the base resource. (This is how the #lists and
33
+ # #contacts methods are implemented.)
34
+ def self.all(options={}, forwardTo = nil)
35
+ query_options = default_options.merge(options)
36
+ validate_options(query_options)
37
+ result = query_collection(query_options)
38
+ ResourceCollection.new(self, result, forwardTo)
39
+ end
40
+
41
+ # Returns a collection of all contacts matching the query. Unfortunately, this has to be performed by looking up
42
+ # each one individually. We forward the Contact class to ResourceCollection to do that work.
43
+ def self.contacts(options={})
44
+ all(options, ActsAsIcontact::Contact)
45
+ end
46
+
47
+ # Returns a collection of all lists matching the query. Unfortunately, this has to be performed by looking up
48
+ # each one individually. We forward the List class to ResourceCollection to do that work.
49
+ def self.lists(options={})
50
+ all(options, ActsAsIcontact::List)
51
+ end
52
+
53
+ # status must be one of: normal, pending, unsubscribed
54
+ def validate_on_save(fields)
55
+ raise ActsAsIcontact::ValidationError, "Status must be one of: #{STATUSES.join(', ')}" unless STATUSES.include?(fields["status"])
56
+ end
57
+
58
+ # The list that this subscription is associated with.
59
+ def list
60
+ ActsAsIcontact::List.find(listId) if properties["listId"]
61
+ end
62
+
63
+ # The contact that this subscription is associated with.
64
+ def contact
65
+ ActsAsIcontact::Contact.find(contactId) if properties["contactId"]
66
+ end
67
+
68
+ # The confirmation message for this subscription, if any.
69
+ def confirmationMessage
70
+ ActsAsIcontact::Message.find(confirmationMessageId) if properties["confirmationMessageId"]
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -20,4 +20,8 @@ describe ActsAsIcontact::ResourceCollection do
20
20
  @this[1].yoo.should == "yar"
21
21
  end
22
22
 
23
+ it "treats 'first' as an initial call of 'next'" do
24
+ @this.first.foo.should == "bar"
25
+ @this.next.yoo.should == "yar"
26
+ end
23
27
  end
@@ -18,7 +18,10 @@ describe ActsAsIcontact::Contact do
18
18
  @john = ActsAsIcontact::Contact.first(:firstName => "John", :lastName => "Test")
19
19
  end
20
20
 
21
- it "knows which lists it's subscribed to"
21
+ it "knows which lists it's subscribed to" do
22
+ @john.lists.first.should == ActsAsIcontact::List.find(444444)
23
+ end
24
+
22
25
  it "knows its history"
23
26
  end
24
27
 
@@ -31,7 +31,11 @@ describe ActsAsIcontact::List do
31
31
  before(:each) do
32
32
  @list = ActsAsIcontact::List.first(:name => "First Test")
33
33
  end
34
- it "knows its subscribers"
34
+
35
+ it "knows its subscribers" do
36
+ @list.subscribers.first.should == ActsAsIcontact::Contact.find(333444)
37
+ @list.subscribers.next.should == ActsAsIcontact::Contact.find(333333)
38
+ end
35
39
 
36
40
  it "knows its welcome message" do
37
41
  @list.welcomeMessage.should == ActsAsIcontact::Message.find(555555)
@@ -0,0 +1,57 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ActsAsIcontact::Subscription do
4
+
5
+ it "requires listId, contactId, status on create" do
6
+ s = ActsAsIcontact::Subscription.new(:status => nil)
7
+ lambda{s.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: listId, contactId, status")
8
+ end
9
+
10
+ it "validates that status is in (pending, normal, unsubscribed)" do
11
+ s = ActsAsIcontact::Subscription.new(:status => "foo", :listId => 1, :contactId => 2)
12
+ lambda{s.save}.should raise_error(ActsAsIcontact::ValidationError, "Status must be one of: normal, pending, unsubscribed")
13
+ end
14
+
15
+ it "sets status to normal by default" do
16
+ s = ActsAsIcontact::Subscription.new
17
+ s.status.should == "normal"
18
+ end
19
+
20
+ it "can find by the compound primary key" do
21
+ s = ActsAsIcontact::Subscription.find("444444_333333")
22
+ s.listId.should == 444444
23
+ s.contactId.should == 333333
24
+ end
25
+
26
+ it "can return the contacts for a query as a collection" do
27
+ collection = ActsAsIcontact::Subscription.contacts(:listId => 444444)
28
+ collection.first.should == ActsAsIcontact::Contact.find(333444)
29
+ collection.next.should == ActsAsIcontact::Contact.find(333333)
30
+ end
31
+
32
+ it "can return the lists for a query as a collection" do
33
+ collection = ActsAsIcontact::Subscription.lists(:contactId => 333444)
34
+ collection.first.should == ActsAsIcontact::List.find(444444)
35
+ end
36
+
37
+ context "associations" do
38
+ # Create one good subscription
39
+ before(:each) do
40
+ @sub = ActsAsIcontact::Subscription.first(:contactId => 333444)
41
+ end
42
+
43
+ it "knows its contact" do
44
+ @sub.contact.should == ActsAsIcontact::Contact.find(333444)
45
+ end
46
+
47
+ it "knows its list" do
48
+ @sub.list.should == ActsAsIcontact::List.find(444444)
49
+ end
50
+
51
+ it "knows its confirmation message" do
52
+ @sub.confirmationMessage.should == ActsAsIcontact::Message.find(555666)
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -30,14 +30,19 @@ FakeWeb.register_uri(:get, "#{i}/a/111111/c?limit=500", :body => %q<{"clientfold
30
30
 
31
31
  # Contacts
32
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"}]}>)
33
- FakeWeb.register_uri(:post, "#{ic}/contacts", :body => %q<{"contacts":[{"email":"john@example.org","firstName":"John","lastName":"Smith","status":"normal","contactId":"333444","createDate":"2009-07-24 01:00:00","street":"","street2":"","prefix":"","suffix":"","fax":"","phone":"","city":"","state":"","postalCode":"","bounceCount":0,"custom_field":"","test_field":"","business":""}]}>)
33
+ FakeWeb.register_uri(:post, "#{ic}/contacts", :body => %q<{"contacts":[{"email":"john@example.org","firstName":"John","lastName":"Smith","status":"normal","contactId":"333444","createDate":"2009-07-24 01:00:00","street":"","street2":"","prefix":"","suffix":"","fax":"","phone":"","city":"","state":"","postalCode":"","bounceCount":0,"custom_field":"","test_field":"","business":""},{"email":"john@example.org","firstName":"John","lastName":"Test","status":"normal","contactId":"333333","createDate":"2009-07-24 01:00:00"}]}>)
34
+ FakeWeb.register_uri(:get, "#{ic}/contacts/333444", :body => %q<{"contact":{"email":"john@example.org","firstName":"John","lastName":"Smith","status":"normal","contactId":"333444","createDate":"2009-07-24 01:00:00","street":"","street2":"","prefix":"","suffix":"","fax":"","phone":"","city":"","state":"","postalCode":"","bounceCount":0,"custom_field":"","test_field":"","business":""}}>)
35
+ FakeWeb.register_uri(:get, "#{ic}/contacts/333333", :body => %q<{"contact":{"email":"john@example.org","firstName":"John","lastName":"Test","status":"normal","contactId":"333333","createDate":"2009-07-24 01:00:00"}}>)
34
36
 
35
37
  # Lists
36
38
  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."}]}>)
39
+ FakeWeb.register_uri(:get, "#{ic}/lists/444444", :body => %q<{"list":{"listId":"444444","name":"First Test","emailOwnerOnChange":"0","welcomeOnManualAdd":"0","welcomeOnSignupAdd":"0","welcomeMessageId":"555555","description":"Just a test list."}}>)
37
40
 
38
41
  # Message
39
- #### Test message for List association
42
+ #### Test welcome message for List association
40
43
  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"}}>)
44
+ #### Test confirmation message
45
+ FakeWeb.register_uri(:get, "#{ic}/messages/555666", :body => %q<{"message":{"messageId":"555666","subject":"Confirm!","messageType":"confirmation","textBody":"Please confirm your subscription.","htmlBody":"<p>Please confirm your subscription.</p>","createDate":"20090727 14:55:12"}}>)
41
46
 
42
47
  #### Test message for associations originating from Message spec
43
48
  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"}]}>)
@@ -45,3 +50,13 @@ FakeWeb.register_uri(:get, "#{ic}/messages?limit=1&subject=Test%20Message", :bod
45
50
  # CustomField
46
51
  FakeWeb.register_uri(:get, "#{ic}/customfields?limit=500", :body => %q<{"customfields":[{"privateName":"test_field","publicName":"Test Field","displayToUser":"0","fieldType":"text"},{"privateName":"custom_field","publicName":"This is for the Rails integration specs","displayToUser":1,"fieldType":"text"}],"total":2}>)
47
52
  FakeWeb.register_uri(:get, "#{ic}/customfields/test_field", :body => %q<{"customfield":{"privateName":"test_field","publicName":"Test Field","displayToUser":"0","fieldType":"text"}}>)
53
+
54
+ # Subscription
55
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions/444444_333333", :body => %q<{"subscription":{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333333,"listId":444444, "subscriptionId":"444444_333333"}}>)
56
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions/444444_333444", :body => %q<{"subscription":{"status":"normal","addDate":"2009-07-27T15:37:38-04:00","contactId":333444,"listId":444444, "subscriptionId":"444444_333444"}}>)
57
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions?limit=500", :body => %q<{"subscriptions":[{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333333,"listId":444444, "subscriptionId":"444444_333333"},{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333333,"listId":444444, "subscriptionId":"444444_333333"}],"total":2}>)
58
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions?limit=500&listId=444444", :body => %q<{"subscriptions":[{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333444,"listId":444444, "subscriptionId":"444444_333444","confirmationMessageId":555666},{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333333,"listId":444444, "subscriptionId":"444444_333333"}],"total":1}>)
59
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions?limit=1&contactId=333444", :body => %q<{"subscriptions":[{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333444,"listId":444444, "subscriptionId":"444444_333333","confirmationMessageId":555666}]}>)
60
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions?limit=500&contactId=333444", :body => %q<{"subscriptions":[{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333444,"listId":444444, "subscriptionId":"444444_333444","confirmationMessageId":555666}]}>)
61
+ FakeWeb.register_uri(:get, "#{ic}/subscriptions?limit=500&contactId=333333", :body => %q<{"subscriptions":[{"status":"normal","addDate":"2009-07-27T15:36:37-04:00","contactId":333333,"listId":444444, "subscriptionId":"444444_333333","confirmationMessageId":555666}]}>)
62
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_icontact
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Eley
@@ -52,6 +52,7 @@ files:
52
52
  - lib/acts_as_icontact/resources/custom_field.rb
53
53
  - lib/acts_as_icontact/resources/list.rb
54
54
  - lib/acts_as_icontact/resources/message.rb
55
+ - lib/acts_as_icontact/resources/subscription.rb
55
56
  - rails/init.rb
56
57
  - spec/config_spec.rb
57
58
  - spec/connection_spec.rb
@@ -67,6 +68,7 @@ files:
67
68
  - spec/resources/custom_field_spec.rb
68
69
  - spec/resources/list_spec.rb
69
70
  - spec/resources/message_spec.rb
71
+ - spec/resources/subscription_spec.rb
70
72
  - spec/spec.opts
71
73
  - spec/spec_helper.rb
72
74
  - spec/support/active_record/connection_adapters/nulldb_adapter.rb
@@ -115,6 +117,7 @@ test_files:
115
117
  - spec/resources/custom_field_spec.rb
116
118
  - spec/resources/list_spec.rb
117
119
  - spec/resources/message_spec.rb
120
+ - spec/resources/subscription_spec.rb
118
121
  - spec/spec_helper.rb
119
122
  - spec/support/active_record/connection_adapters/nulldb_adapter.rb
120
123
  - spec/support/nulldb_rspec.rb