SFEley-acts_as_icontact 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.markdown CHANGED
@@ -152,9 +152,13 @@ When you call the `acts_as_icontact` method in an ActiveRecord class declaration
152
152
  4. If an `icontact_status` field exists, creates named scopes on the model class for each iContact status. _(Pending)_
153
153
 
154
154
  ### Options
155
- Option values and field mappings can be passed to the `acts_as_icontact` declaration to set default behavior for the model class. Right now there's only one option:
155
+ Option values and field mappings can be passed to the `acts_as_icontact` declaration to set default behavior for the model class.
156
156
 
157
- `default_lists` -- _The name or ID number of a list to subscribe new contacts to automatically, or an array of said names or numbers_
157
+ `list` -- _The name or ID number of a list to subscribe new contacts to automatically_
158
+ `lists` -- _Like `list` but takes an array of names or numbers; new contacts will be subscribed to all of them_
159
+ `exception_on_failure` -- _If true, throws an ActsAsIcontact::SyncError when synchronization fails. Defaults to false._
160
+
161
+ A note about failure: problems with synchronization are always logged to the standard Rails log. For most applications, however, updating iContact is a secondary consideration; if a new user is registering, you _probably_ don't want exceptions bubbling up and the whole transaction rolling back just because of a transient iContact server outage. So exceptions are something you have to deliberately enable.
158
162
 
159
163
  ### Field Mappings
160
164
  You can add contact integration to any ActiveRecord model that tracks an email address. (If your model _doesn't_ include email but you want to use iContact with it, you are very, very confused.)
@@ -162,13 +166,13 @@ You can add contact integration to any ActiveRecord model that tracks an email a
162
166
  Any fields that are named the same as iContact's personal information fields, or custom fields you've previously declared, will be autodiscovered. Otherwise you can map them:
163
167
 
164
168
  class Customer < ActiveRecord::Base
165
- acts_as_icontact :default_lists => ['New Customers', 'All Users'] # Puts new contact on two lists
166
- :given_name => :firstName, # Key is Rails field, value is iContact field
167
- :family_name => :lastName,
168
- :address1 => :street,
169
- :address2 => :street2,
170
- :id => :rails_id, # Custom field created in iContact
171
- :preferred? => :preferred_customer # Custom field
169
+ acts_as_icontact :lists => ['New Customers', 'All Users'] # Puts new contact on two lists
170
+ :firstName => :given_name, # Key is iContact field, value is Rails field
171
+ :lastName => :family_name,
172
+ :street => :address1,
173
+ :street2 => :address2,
174
+ :rails_id => :id # Custom field created in iContact
175
+ :preferred_customer => :preferred? # Custom field
172
176
  end
173
177
 
174
178
  A few iContact-specific fields are exceptions, and have different autodiscovery names to avoid collisions with other attributes in your application:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
@@ -2,17 +2,19 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{acts_as_icontact}
5
- s.version = "0.2.0"
5
+ s.version = "0.2.1"
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-26}
9
+ s.date = %q{2009-07-27}
10
+ s.default_executable = %q{icontact}
10
11
  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
12
 
12
13
  * Simple, consistent access to all resources in the iContact API; and
13
14
  * Automatic synchronizing between ActiveRecord models and iContact contact lists for Rails applications.
14
15
  }
15
16
  s.email = %q{sfeley@gmail.com}
17
+ s.executables = ["icontact"]
16
18
  s.extra_rdoc_files = [
17
19
  "README.markdown"
18
20
  ]
@@ -24,18 +26,22 @@ Gem::Specification.new do |s|
24
26
  "Rakefile",
25
27
  "VERSION",
26
28
  "acts_as_icontact.gemspec",
29
+ "bin/icontact",
27
30
  "init.rb",
28
31
  "lib/acts_as_icontact.rb",
29
32
  "lib/acts_as_icontact/config.rb",
30
33
  "lib/acts_as_icontact/connection.rb",
31
34
  "lib/acts_as_icontact/exceptions.rb",
32
35
  "lib/acts_as_icontact/rails.rb",
36
+ "lib/acts_as_icontact/rails/callbacks.rb",
33
37
  "lib/acts_as_icontact/rails/macro.rb",
38
+ "lib/acts_as_icontact/rails/mappings.rb",
34
39
  "lib/acts_as_icontact/resource.rb",
35
40
  "lib/acts_as_icontact/resource_collection.rb",
36
41
  "lib/acts_as_icontact/resources/account.rb",
37
42
  "lib/acts_as_icontact/resources/client.rb",
38
43
  "lib/acts_as_icontact/resources/contact.rb",
44
+ "lib/acts_as_icontact/resources/custom_field.rb",
39
45
  "lib/acts_as_icontact/resources/list.rb",
40
46
  "lib/acts_as_icontact/resources/message.rb",
41
47
  "rails/init.rb",
@@ -43,11 +49,14 @@ Gem::Specification.new do |s|
43
49
  "spec/connection_spec.rb",
44
50
  "spec/examples/schema.rb",
45
51
  "spec/rails_spec.rb",
52
+ "spec/rails_spec/callbacks_spec.rb",
53
+ "spec/rails_spec/mappings_spec.rb",
46
54
  "spec/resource_collection_spec.rb",
47
55
  "spec/resource_spec.rb",
48
56
  "spec/resources/account_spec.rb",
49
57
  "spec/resources/client_spec.rb",
50
58
  "spec/resources/contact_spec.rb",
59
+ "spec/resources/custom_field_spec.rb",
51
60
  "spec/resources/list_spec.rb",
52
61
  "spec/resources/message_spec.rb",
53
62
  "spec/spec.opts",
@@ -60,18 +69,21 @@ Gem::Specification.new do |s|
60
69
  s.rdoc_options = ["--charset=UTF-8"]
61
70
  s.require_paths = ["lib"]
62
71
  s.rubyforge_project = %q{actsasicontact}
63
- s.rubygems_version = %q{1.3.4}
72
+ s.rubygems_version = %q{1.3.5}
64
73
  s.summary = %q{Automatic bridge between iContact e-mail marketing service and Rails ActiveRecord}
65
74
  s.test_files = [
66
75
  "spec/config_spec.rb",
67
76
  "spec/connection_spec.rb",
68
77
  "spec/examples/schema.rb",
78
+ "spec/rails_spec/callbacks_spec.rb",
79
+ "spec/rails_spec/mappings_spec.rb",
69
80
  "spec/rails_spec.rb",
70
81
  "spec/resource_collection_spec.rb",
71
82
  "spec/resource_spec.rb",
72
83
  "spec/resources/account_spec.rb",
73
84
  "spec/resources/client_spec.rb",
74
85
  "spec/resources/contact_spec.rb",
86
+ "spec/resources/custom_field_spec.rb",
75
87
  "spec/resources/list_spec.rb",
76
88
  "spec/resources/message_spec.rb",
77
89
  "spec/spec_helper.rb",
data/bin/icontact ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+ require 'acts_as_icontact'
5
+ require 'rubygems'
6
+ require 'readline'
7
+ require 'bond'
8
+ require 'bond/completion'
9
+
10
+ module ActsAsIcontact
11
+ # Lifted from: http://tagaholic.me/2009/07/23/mini-irb-and-mini-script-console.html
12
+ history_file = File.join(ENV["HOME"], '.icontact_history')
13
+ IO.readlines(history_file).each {|e| Readline::HISTORY << e.chomp } if File.exists?(history_file)
14
+ print "# ActsAsIcontact command line (type 'exit' to quit)\n"
15
+ while (input = Readline.readline('>> ', true)) != 'exit'
16
+ begin puts "=> #{eval(input).inspect}"; rescue Exception; puts "Error: #{$!}" end
17
+ end
18
+ File.open(history_file, 'w') {|f| f.write Readline::HISTORY.to_a.join("\n") }
19
+
20
+ end
21
+
22
+ exit!
@@ -0,0 +1,31 @@
1
+ module ActsAsIcontact
2
+ module Rails
3
+ module Callbacks
4
+
5
+ protected
6
+ # Called after a new record has been saved. Creates a new contact in iContact.
7
+ def icontact_after_create
8
+ c = ActsAsIcontact::Contact.new
9
+ self.class.icontact_mappings.each do |rails, iContact|
10
+ if (value = self.send(rails))
11
+ ic = (iContact.to_s + '=').to_sym # Blah. This feels like it should be easier.
12
+ c.send(ic, value)
13
+ end
14
+ end
15
+ if c.save
16
+ # Update with iContact fields returned
17
+ @icontact_in_progress = true
18
+ self.class.icontact_mappings.each do |rails, iContact|
19
+ unless (value = c.send(iContact)).blank?
20
+ r = (rails.to_s + '=').to_sym # Blah. This feels like it should be easier.
21
+ self.send(r, value)
22
+ end
23
+ end
24
+ self.save
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -6,7 +6,20 @@ module ActsAsIcontact
6
6
  # The core macro for ActsAsIcontact's Rails integration. Establishes callbacks to keep Rails models in
7
7
  # sync with iContact. See the README for more on what it does.
8
8
  def acts_as_icontact(options = {})
9
- true
9
+ # Fail on exceptions?
10
+ @icontact_exception_on_failure = options.delete(:exception_on_failure) || false
11
+
12
+ # Combines :list and :lists parameters into one array
13
+ @icontact_default_lists = []
14
+ @icontact_default_lists << options.delete(:list) if options.has_key?(:list)
15
+ @icontact_default_lists += options.delete(:lists) if options.has_key?(:lists)
16
+
17
+ # Set up field mappings
18
+ set_mappings(options)
19
+
20
+ # If we haven't flaked out so far, we must be doing okay. Make magic happen.
21
+ include ActsAsIcontact::Rails::Callbacks
22
+ after_create :icontact_after_create
10
23
  end
11
24
 
12
25
  end
@@ -0,0 +1,74 @@
1
+ module ActsAsIcontact
2
+ module Rails
3
+ module ClassMethods
4
+ module Mappings
5
+
6
+ ICONTACT_DEFAULT_MAPPINGS = {
7
+ :contactId => [:icontact_id, :icontactId],
8
+ :email => [:email, :email_address, :eMail, :emailAddress],
9
+ :firstName => [:firstName, :first_name, :fname],
10
+ :lastName => [:lastName, :last_name, :lname],
11
+ :street => [:street, :street1, :address, :address1],
12
+ :street2 => [:street2, :address2],
13
+ :city => [:city],
14
+ :state => [:state, :province, :state_or_province],
15
+ :postalCode => [:postalCode, :postal_code, :zipCode, :zip_code, :zip],
16
+ :phone => [:phone, :phoneNumber, :phone_number],
17
+ :fax => [:fax, :faxNumber, :fax_number],
18
+ :business => [:business, :company, :companyName, :company_name, :businessName, :business_name],
19
+ :status => [:icontact_status, :icontactStatus],
20
+ :createDate => [:icontact_created, :icontactCreated, :icontact_create_date, :icontactCreateDate],
21
+ :bounceCount => [:icontact_bounces, :icontactBounces, :icontact_bounce_count, :icontactBounceCount]
22
+ }
23
+
24
+ # A hash containing the final list of iContact-to-Rails mappings. The mappings take into account both
25
+ # analysis of existing field names and explicit mappings on the `acts_as_icontact` macro line.
26
+ def icontact_mappings
27
+ @icontact_mappings
28
+ end
29
+
30
+ # A two-element array indicating the association used to uniquely identify this record between Rails and iContact.
31
+ # The first element is the Rails field; the second element is the iContact field.
32
+ # First uses whatever field maps the contactId; if none, looks for a mapping from the Rails ID. Uses the email address
33
+ # mapping (which is required) as a last resort.
34
+ def icontact_identity_map
35
+ icontact_mappings.rassoc(:contactId) or icontact_mappings.assoc(:id) or icontact_mappings.rassoc(:email)
36
+ end
37
+
38
+ protected
39
+
40
+ # Sets up the mapping hash from iContact fields to Rails fields.
41
+ def set_mappings(options)
42
+ @icontact_mappings = {}
43
+ set_default_mappings
44
+ set_custom_field_mappings
45
+ @icontact_mappings.merge!(options)
46
+ end
47
+
48
+ private
49
+ def set_default_mappings
50
+ ICONTACT_DEFAULT_MAPPINGS.each do |iContactField, candidates|
51
+ candidates.each do |candidate|
52
+ if rails_field?(candidate)
53
+ @icontact_mappings[candidate] = iContactField
54
+ break
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def set_custom_field_mappings
61
+ @icontact_custom_fields ||= CustomField.list
62
+ @icontact_custom_fields.each do |field|
63
+ f = field.to_sym
64
+ @icontact_mappings[f] = f if rails_field?(f)
65
+ end
66
+ end
67
+
68
+ def rails_field?(field)
69
+ column_names.include?(field.to_s) or instance_methods.include?(field)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -4,18 +4,20 @@ Dir[File.join(File.dirname(__FILE__), 'rails', '*.rb')].sort.each do |path|
4
4
  require "acts_as_icontact/rails/#{filename}"
5
5
  end
6
6
 
7
- module ActsAsIcontact
8
- module Rails
9
- module ClassMethods
10
- include Macro
11
- end
12
- end
13
- end
7
+ # module ActsAsIcontact
8
+ # module Rails
9
+ # module ClassMethods
10
+ # extend Mappings
11
+ # extend Macro
12
+ # end
13
+ # end
14
+ # end
14
15
 
15
16
  if defined?(::ActiveRecord)
16
17
  module ::ActiveRecord
17
18
  class Base
18
- extend ActsAsIcontact::Rails::ClassMethods
19
+ extend ActsAsIcontact::Rails::ClassMethods::Mappings
20
+ extend ActsAsIcontact::Rails::ClassMethods::Macro
19
21
  end
20
22
  end
21
23
  end
@@ -102,7 +102,7 @@ module ActsAsIcontact
102
102
  @errors
103
103
  end
104
104
 
105
- # Returns an array of resources starting at the base.
105
+ # Returns a resource or collection of resources.
106
106
  def self.find(type, options={})
107
107
  case type
108
108
  when :first
@@ -111,6 +111,8 @@ module ActsAsIcontact
111
111
  all(options)
112
112
  when Integer
113
113
  find_by_id(type)
114
+ when String
115
+ find_by_string(type) # Implemented in subclasses
114
116
  else
115
117
  raise ActsAsIcontact::QueryError, "Don't know how to find '#{type.to_s}'"
116
118
  end
@@ -137,8 +139,10 @@ module ActsAsIcontact
137
139
  def self.find_by_id(id)
138
140
  response = self.connection[id].get
139
141
  parsed = JSON.parse(response)
140
- raise ActsAsiContact::QueryError, "iContact's response did not contain a #{resource_name}!" unless parsed[resource_name]
142
+ raise ActsAsIcontact::QueryError, "iContact's response did not contain a #{resource_name}!" unless parsed[resource_name]
141
143
  self.new(parsed[resource_name])
144
+ rescue RestClient::ResourceNotFound
145
+ raise ActsAsIcontact::QueryError, "The #{resource_name} with id #{id} could not be found"
142
146
  end
143
147
 
144
148
  # Two resources are identical if they have exactly the same property array.
@@ -196,6 +200,11 @@ module ActsAsIcontact
196
200
  base[uri_component]
197
201
  end
198
202
 
203
+ # Allows some subclasses to implement finding by a key identifier string. The base resource just throws an exception.
204
+ def self.find_by_string(value)
205
+ raise ActsAsIcontact::QueryError, "You cannot search on #{collection_name} with a string value!"
206
+ end
207
+
199
208
  # The primary key field for this resource. Used on updates.
200
209
  def self.primary_key
201
210
  resource_name + "Id"
@@ -3,7 +3,7 @@ module ActsAsIcontact
3
3
 
4
4
  # Email is required
5
5
  def self.required_on_create
6
- super << ['email']
6
+ super << 'email'
7
7
  end
8
8
 
9
9
  # Derived from clientFolder
@@ -0,0 +1,51 @@
1
+ module ActsAsIcontact
2
+ class CustomField < Resource
3
+
4
+ # Has a default fieldType of "text" and displayToUser of "false" if not overridden as options.
5
+ def initialize(properties={})
6
+ # Capture privateName as the ID field if it was passed in
7
+ @customFieldId = properties["privateName"] || properties[:privateName]
8
+ super({:fieldType => "text", :displayToUser => false}.merge(properties))
9
+ end
10
+
11
+ # Uses privateName as its ID in resource URLs. The custom field resource is just weird that way.
12
+ def id
13
+ @customFieldId
14
+ end
15
+
16
+ # privateName must not contain certain punctuation; fieldType must be "text" or "checkbox".
17
+ def validate_on_save(fields)
18
+ raise ActsAsIcontact::ValidationError, "privateName cannot contain spaces, quotes, slashes or brackets" if fields["privateName"] =~ /[\s\"\'\/\\\[\]]/
19
+ raise ActsAsIcontact::ValidationError, "fieldType must be 'text' or 'checkbox'" unless fields["fieldType"] =~ /^(text|checkbox)$/
20
+ end
21
+
22
+
23
+ # Searches on privateName. For this class it's the proper way to find by the primary key anyway.
24
+ def self.find_by_string(value)
25
+ find_by_id(value)
26
+ end
27
+
28
+ # Requires privateName, displayToUser, fieldType
29
+ def self.required_on_create
30
+ super + %w(privateName displayToUser fieldType)
31
+ end
32
+
33
+ # Treats displayToUser as a boolean field (accepts true and false)
34
+ def self.boolean_fields
35
+ super << "displayToUser"
36
+ end
37
+
38
+ # Uses privateName as its primary key field
39
+ def self.primary_key
40
+ "privateName"
41
+ end
42
+
43
+ # Returns an array listing all custom fields. This is very convenient for certain tasks (e.g. the mapping in our Rails integration).
44
+ def self.list
45
+ list = []
46
+ fields = self.all
47
+ fields.each {|f| list << f.privateName}
48
+ list
49
+ end
50
+ end
51
+ end
@@ -5,6 +5,11 @@ module ActsAsIcontact
5
5
  ActsAsIcontact.client
6
6
  end
7
7
 
8
+ # Searches on list name.
9
+ def self.find_by_string(value)
10
+ first(:name => value)
11
+ end
12
+
8
13
  # Requires name, emailOwnerOnChange, welcomeOnManualAdd, welcomeOnSignupAdd, and welcomeMessageId.
9
14
  def self.required_on_create
10
15
  super << "name" << "emailOwnerOnChange" << "welcomeOnManualAdd" << "welcomeOnSignupAdd" << "welcomeMessageId"
@@ -5,9 +5,13 @@ ActiveRecord::Schema.define do
5
5
  t.string "email", :null => false
6
6
  t.string "icontact_status"
7
7
  t.string "status"
8
- t.string "icontact_id"
9
- t.datetime "icontact_created"
10
- t.string "bounces"
8
+ t.integer "icontact_id"
9
+ t.string "address"
10
+ t.string "state_or_province"
11
+ t.string "zip"
12
+ t.string "business"
13
+ t.datetime "icontactCreated"
14
+ t.integer "bounces"
11
15
  t.string "custom_field"
12
16
  t.datetime "created_at"
13
17
  t.datetime "updated_at"
@@ -0,0 +1,19 @@
1
+ share_as :Callbacks do
2
+ context "callbacks" do
3
+ before(:each) do
4
+ @class.acts_as_icontact :list => "First Test", :surname => :lastName
5
+ @person = @class.new(:firstName => "John", :surname => "Smith", :email => "john@example.org")
6
+ end
7
+
8
+ it "will create a new contact after record creation" do
9
+ # ActsAsIcontact::Contact.any_instance.expects(:save).returns(true)
10
+ @person.save
11
+ end
12
+
13
+ it "updates the Person with the results of the contact save" do
14
+ @person.save
15
+ @person.icontact_id.should == 333444
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,61 @@
1
+ share_as :Mappings do
2
+
3
+ context "mappings" do
4
+ before(:each) do
5
+ @class.acts_as_icontact :list => 444444, :surname => :lastName
6
+ end
7
+
8
+ it "uses any non-option keys as field mappings" do
9
+ @class.icontact_mappings[:surname].should == :lastName
10
+ end
11
+
12
+ it "does not map option keys" do
13
+ @class.icontact_mappings.should_not have_key(:list)
14
+ end
15
+
16
+ it "maps fields it can find from the default list" do
17
+ @class.icontact_mappings[:firstName].should == :firstName
18
+ end
19
+
20
+ it "maps second choices when it can find them" do
21
+ @class.icontact_mappings[:zip].should == :postalCode
22
+ end
23
+
24
+ it "maps the icontact_ exception names" do
25
+ @class.icontact_mappings[:icontactCreated].should == :createDate
26
+ end
27
+
28
+ it "does not map the default form of exception names" do
29
+ @class.icontact_mappings.should_not have_key(:status)
30
+ end
31
+
32
+ it "maps icontact_status" do
33
+ @class.icontact_mappings[:icontact_status].should == :status
34
+ end
35
+
36
+ it "maps the address field" do
37
+ @class.icontact_mappings[:address].should == :street
38
+ end
39
+
40
+ it "maps custom fields" do
41
+ @class.icontact_mappings[:custom_field].should == :custom_field
42
+ end
43
+ end
44
+
45
+ context "identity mapping" do
46
+ it "looks for contactId first" do
47
+ @class.acts_as_icontact
48
+ @class.icontact_identity_map.should == [:icontact_id, :contactId]
49
+ end
50
+
51
+ it "looks for a Rails ID custom field second" do
52
+ @class.acts_as_icontact :icontact_id => nil, :id => :test_field
53
+ @class.icontact_identity_map.should == [:id, :test_field]
54
+ end
55
+
56
+ it "uses email as last resort" do
57
+ @class.acts_as_icontact :icontact_id => nil
58
+ @class.icontact_identity_map.should == [:email, :email]
59
+ end
60
+ end
61
+ end
data/spec/rails_spec.rb CHANGED
@@ -3,24 +3,9 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  require 'activerecord'
4
4
  require 'rails/init'
5
5
 
6
- # Dummy model
7
- class Person < ActiveRecord::Base
8
- # create_table "people", :force => true do |t|
9
- # t.string "firstName"
10
- # t.string "surname"
11
- # t.string "email", :null => false
12
- # t.string "icontact_status"
13
- # t.string "status"
14
- # t.string "icontact_id"
15
- # t.datetime "icontact_created"
16
- # t.string "bounces"
17
- # t.string "custom_field"
18
- # t.datetime "created_at"
19
- # t.datetime "updated_at"
20
- # end
21
-
22
- acts_as_icontact
23
- end
6
+ # Spreading out our Rails specs
7
+ Dir["#{File.dirname(__FILE__)}/rails_spec/*.rb"].each {|f| load f}
8
+
24
9
 
25
10
  describe "Rails integration" do
26
11
  before(:all) do
@@ -28,9 +13,70 @@ describe "Rails integration" do
28
13
  :schema => File.dirname(__FILE__) + '/examples/schema.rb'
29
14
  end
30
15
 
31
- it "relies on a model with an email address" do
32
- @person = Person.new(:email => "john@example.org")
33
- @person.email.should == "john@example.org"
16
+ before(:each) do
17
+ # Create each Person class in a different module so that we can try different permutations.
18
+ @module = Module.new do
19
+ # Dummy model - comes from spec/examples/schema.rb and using the nullDB adapter
20
+ class Person < ActiveRecord::Base
21
+ # create_table "people", :force => true do |t|
22
+ # t.string "firstName"
23
+ # t.string "surname"
24
+ # t.string "email", :null => false
25
+ # t.string "icontact_status"
26
+ # t.string "status"
27
+ # t.string "icontact_id"
28
+ # t.string "address"
29
+ # t.string "state_or_province"
30
+ # t.string "zip"
31
+ # t.string "business"
32
+ # t.datetime "icontactCreated"
33
+ # t.string "bounces"
34
+ # t.string "custom_field"
35
+ # t.datetime "created_at"
36
+ # t.datetime "updated_at"
37
+ # end
38
+
39
+ # Fake out ActiveRecord's column introspection
40
+ def self.name
41
+ "Person"
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ @class = @module.module_eval("Person")
48
+ end
49
+
50
+ it "allows the acts_as_icontact macro method" do
51
+ @class.should respond_to(:acts_as_icontact)
52
+ end
53
+
54
+ it "sets whether to throw exceptions on failure" do
55
+ @class.acts_as_icontact :exception_on_failure => true
56
+ @class.instance_variable_get(:@icontact_exception_on_failure).should be_true
57
+ end
58
+
59
+ it "defaults to NOT setting exceptions on failure" do
60
+ @class.acts_as_icontact
61
+ @class.instance_variable_get(:@icontact_exception_on_failure).should be_false
62
+ end
63
+
64
+ it "sets default lists from a single list in the options" do
65
+ @class.acts_as_icontact :list => 444444
66
+ @class.instance_variable_get(:@icontact_default_lists).should == [444444]
67
+ end
68
+
69
+ it "sets default lists from an array of lists in the options" do
70
+ @class.acts_as_icontact :lists => [444555, "Test List 3"]
71
+ @class.instance_variable_get(:@icontact_default_lists).should == [444555, "Test List 3"]
72
+ end
73
+
74
+ it "sets default lists from both a list _and_ an array of lists in the options" do
75
+ @class.acts_as_icontact :lists => [444555, "Test List 3"], :list => 444444
76
+ @class.instance_variable_get(:@icontact_default_lists).should == [444444, 444555, "Test List 3"]
34
77
  end
35
78
 
79
+ include Mappings
80
+ include Callbacks
81
+
36
82
  end
@@ -80,6 +80,11 @@ describe ActsAsIcontact::Resource do
80
80
  a.too.should == "sar"
81
81
  end
82
82
 
83
+ it "can attempt to find a single resource by string identifier" do
84
+ ActsAsIcontact::Resource.expects(:find_by_string).returns(nil)
85
+ ActsAsIcontact::Resource.find("bar")
86
+ end
87
+
83
88
  end
84
89
 
85
90
  it "knows its properties" do
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe ActsAsIcontact::CustomField do
4
+ it "requires privateName, displayToUser, fieldType" do
5
+ cf = ActsAsIcontact::CustomField.new(:displayToUser => nil, :fieldType => nil)
6
+ lambda{cf.save}.should raise_error(ActsAsIcontact::ValidationError, "Missing required fields: privateName, displayToUser, fieldType")
7
+ end
8
+
9
+ it "uses true and false to assign boolean fields" do
10
+ cf = ActsAsIcontact::CustomField.new
11
+ cf.displayToUser = true
12
+ cf.instance_variable_get(:@properties)["displayToUser"].should == 1
13
+ end
14
+
15
+ it "defaults displayToUser to false" do
16
+ cf = ActsAsIcontact::CustomField.new
17
+ cf.displayToUser.should be_false
18
+ end
19
+
20
+ it "defaults fieldType to text" do
21
+ cf = ActsAsIcontact::CustomField.new
22
+ cf.fieldType.should == "text"
23
+ end
24
+
25
+ it "uses privateName as its primary key" do
26
+ ActsAsIcontact::CustomField.primary_key.should == "privateName"
27
+ end
28
+
29
+ it "uses privateName as its resource ID" do
30
+ cf = ActsAsIcontact::CustomField.new(:privateName => "blah")
31
+ cf.connection.url.should =~ /blah/
32
+ end
33
+
34
+ it "can find fields by string" do
35
+ cf = ActsAsIcontact::CustomField.find("test_field")
36
+ cf.publicName.should == "Test Field"
37
+ end
38
+
39
+ it "validates the format of the privateName" do
40
+ cf = ActsAsIcontact::CustomField.new(:privateName => "This isn't a valid [privateName].")
41
+ lambda{cf.save}.should raise_error(ActsAsIcontact::ValidationError, "privateName cannot contain spaces, quotes, slashes or brackets")
42
+ end
43
+
44
+ it "validates the value of fieldType" do
45
+ cf = ActsAsIcontact::CustomField.new(:fieldType => "radio", :privateName => "test")
46
+ lambda{cf.save}.should raise_error(ActsAsIcontact::ValidationError, "fieldType must be 'text' or 'checkbox'")
47
+ end
48
+
49
+ it "can easily retrieve a list of custom field names" do
50
+ ActsAsIcontact::CustomField.list.should == %w(test_field custom_field)
51
+ end
52
+ end
@@ -21,6 +21,11 @@ describe ActsAsIcontact::List do
21
21
  l.welcomeOnManualAdd.should be_true
22
22
  end
23
23
 
24
+ it "can find a list by name" do
25
+ l = ActsAsIcontact::List.find("First Test")
26
+ l.id.should == 444444
27
+ end
28
+
24
29
  context "associations" do
25
30
  # Create one good list
26
31
  before(:each) do
@@ -30,6 +30,7 @@ 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
34
 
34
35
  # Lists
35
36
  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."}]}>)
@@ -40,3 +41,7 @@ FakeWeb.register_uri(:get, "#{ic}/messages/555555", :body => %q<{"message":{"mes
40
41
 
41
42
  #### Test message for associations originating from Message spec
42
43
  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"}]}>)
44
+
45
+ # CustomField
46
+ 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
+ FakeWeb.register_uri(:get, "#{ic}/customfields/test_field", :body => %q<{"customfield":{"privateName":"test_field","publicName":"Test Field","displayToUser":"0","fieldType":"text"}}>)
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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Eley
@@ -9,14 +9,14 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-26 00:00:00 -07:00
13
- default_executable:
12
+ date: 2009-07-27 00:00:00 -07:00
13
+ default_executable: icontact
14
14
  dependencies: []
15
15
 
16
16
  description: "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: * Simple, consistent access to all resources in the iContact API; and * Automatic synchronizing between ActiveRecord models and iContact contact lists for Rails applications."
17
17
  email: sfeley@gmail.com
18
- executables: []
19
-
18
+ executables:
19
+ - icontact
20
20
  extensions: []
21
21
 
22
22
  extra_rdoc_files:
@@ -29,18 +29,22 @@ files:
29
29
  - Rakefile
30
30
  - VERSION
31
31
  - acts_as_icontact.gemspec
32
+ - bin/icontact
32
33
  - init.rb
33
34
  - lib/acts_as_icontact.rb
34
35
  - lib/acts_as_icontact/config.rb
35
36
  - lib/acts_as_icontact/connection.rb
36
37
  - lib/acts_as_icontact/exceptions.rb
37
38
  - lib/acts_as_icontact/rails.rb
39
+ - lib/acts_as_icontact/rails/callbacks.rb
38
40
  - lib/acts_as_icontact/rails/macro.rb
41
+ - lib/acts_as_icontact/rails/mappings.rb
39
42
  - lib/acts_as_icontact/resource.rb
40
43
  - lib/acts_as_icontact/resource_collection.rb
41
44
  - lib/acts_as_icontact/resources/account.rb
42
45
  - lib/acts_as_icontact/resources/client.rb
43
46
  - lib/acts_as_icontact/resources/contact.rb
47
+ - lib/acts_as_icontact/resources/custom_field.rb
44
48
  - lib/acts_as_icontact/resources/list.rb
45
49
  - lib/acts_as_icontact/resources/message.rb
46
50
  - rails/init.rb
@@ -48,11 +52,14 @@ files:
48
52
  - spec/connection_spec.rb
49
53
  - spec/examples/schema.rb
50
54
  - spec/rails_spec.rb
55
+ - spec/rails_spec/callbacks_spec.rb
56
+ - spec/rails_spec/mappings_spec.rb
51
57
  - spec/resource_collection_spec.rb
52
58
  - spec/resource_spec.rb
53
59
  - spec/resources/account_spec.rb
54
60
  - spec/resources/client_spec.rb
55
61
  - spec/resources/contact_spec.rb
62
+ - spec/resources/custom_field_spec.rb
56
63
  - spec/resources/list_spec.rb
57
64
  - spec/resources/message_spec.rb
58
65
  - spec/spec.opts
@@ -62,6 +69,7 @@ files:
62
69
  - spec/support/spec_fakeweb.rb
63
70
  has_rdoc: false
64
71
  homepage: http://github.com/SFEley/acts_as_icontact
72
+ licenses:
65
73
  post_install_message:
66
74
  rdoc_options:
67
75
  - --charset=UTF-8
@@ -82,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
90
  requirements: []
83
91
 
84
92
  rubyforge_project: actsasicontact
85
- rubygems_version: 1.2.0
93
+ rubygems_version: 1.3.5
86
94
  signing_key:
87
95
  specification_version: 3
88
96
  summary: Automatic bridge between iContact e-mail marketing service and Rails ActiveRecord
@@ -90,12 +98,15 @@ test_files:
90
98
  - spec/config_spec.rb
91
99
  - spec/connection_spec.rb
92
100
  - spec/examples/schema.rb
101
+ - spec/rails_spec/callbacks_spec.rb
102
+ - spec/rails_spec/mappings_spec.rb
93
103
  - spec/rails_spec.rb
94
104
  - spec/resource_collection_spec.rb
95
105
  - spec/resource_spec.rb
96
106
  - spec/resources/account_spec.rb
97
107
  - spec/resources/client_spec.rb
98
108
  - spec/resources/contact_spec.rb
109
+ - spec/resources/custom_field_spec.rb
99
110
  - spec/resources/list_spec.rb
100
111
  - spec/resources/message_spec.rb
101
112
  - spec/spec_helper.rb