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 +13 -9
- data/VERSION +1 -1
- data/acts_as_icontact.gemspec +15 -3
- data/bin/icontact +22 -0
- data/lib/acts_as_icontact/rails/callbacks.rb +31 -0
- data/lib/acts_as_icontact/rails/macro.rb +14 -1
- data/lib/acts_as_icontact/rails/mappings.rb +74 -0
- data/lib/acts_as_icontact/rails.rb +10 -8
- data/lib/acts_as_icontact/resource.rb +11 -2
- data/lib/acts_as_icontact/resources/contact.rb +1 -1
- data/lib/acts_as_icontact/resources/custom_field.rb +51 -0
- data/lib/acts_as_icontact/resources/list.rb +5 -0
- data/spec/examples/schema.rb +7 -3
- data/spec/rails_spec/callbacks_spec.rb +19 -0
- data/spec/rails_spec/mappings_spec.rb +61 -0
- data/spec/rails_spec.rb +67 -21
- data/spec/resource_spec.rb +5 -0
- data/spec/resources/custom_field_spec.rb +52 -0
- data/spec/resources/list_spec.rb +5 -0
- data/spec/support/spec_fakeweb.rb +5 -0
- metadata +17 -6
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.
|
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
|
-
`
|
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 :
|
166
|
-
:
|
167
|
-
:
|
168
|
-
:
|
169
|
-
:
|
170
|
-
:
|
171
|
-
:
|
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.
|
1
|
+
0.2.1
|
data/acts_as_icontact.gemspec
CHANGED
@@ -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.
|
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-
|
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.
|
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
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
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"
|
@@ -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"
|
data/spec/examples/schema.rb
CHANGED
@@ -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.
|
9
|
-
t.
|
10
|
-
t.string "
|
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
|
-
#
|
7
|
-
|
8
|
-
|
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
|
-
|
32
|
-
|
33
|
-
@
|
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
|
data/spec/resource_spec.rb
CHANGED
@@ -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
|
data/spec/resources/list_spec.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|