constant_contact 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +56 -0
- data/lib/constant_contact.rb +13 -0
- data/lib/constant_contact/activity.rb +41 -0
- data/lib/constant_contact/base.rb +150 -0
- data/lib/constant_contact/campaign.rb +112 -0
- data/lib/constant_contact/contact.rb +82 -0
- data/lib/constant_contact/contact_event.rb +7 -0
- data/lib/constant_contact/email_address.rb +6 -0
- data/lib/constant_contact/formats.rb +3 -0
- data/lib/constant_contact/formats/atom_format.rb +78 -0
- data/lib/constant_contact/formats/html_encoded_format.rb +17 -0
- data/lib/constant_contact/list.rb +11 -0
- data/lib/constant_contact/member.rb +5 -0
- data/lib/constant_contact/version.rb +3 -0
- data/test/constant_contact/activity_test.rb +41 -0
- data/test/constant_contact/atom_format_test.rb +28 -0
- data/test/constant_contact/base_test.rb +85 -0
- data/test/constant_contact/contact_test.rb +135 -0
- data/test/constant_contact/list_test.rb +17 -0
- data/test/constant_contact/member_test.rb +25 -0
- data/test/constant_contact_test.rb +8 -0
- data/test/fixtures/all_contacts.xml +51 -0
- data/test/fixtures/contactlistscollection.xml +96 -0
- data/test/fixtures/contactlistsindividuallist.xml +30 -0
- data/test/fixtures/memberscollection.xml +763 -0
- data/test/fixtures/new_list.xml +16 -0
- data/test/fixtures/nocontent.xml +12 -0
- data/test/fixtures/service_document.xml +15 -0
- data/test/fixtures/single_contact_by_email.xml +34 -0
- data/test/fixtures/single_contact_by_id.xml +71 -0
- data/test/fixtures/single_contact_by_id_with_no_contactlists.xml +65 -0
- data/test/test_helper.rb +46 -0
- metadata +186 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
ConstantContact
|
2
|
+
===============
|
3
|
+
This is a very ActiveResource-like ruby wrapper to the Constant Contact API. It's not quite done yet and if you are interested in using this code, check it out, and message me with questions.
|
4
|
+
|
5
|
+
See the [ActiveResource::Base docs](http://api.rubyonrails.org/classes/ActiveResource/Base.html) for more information on how to use this ActiveResource-based wrapper.
|
6
|
+
|
7
|
+
Examples
|
8
|
+
--------
|
9
|
+
|
10
|
+
All examples require setting up either the specific class you'll be use or the Base object before use:
|
11
|
+
|
12
|
+
ConstantContact::Base.user = 'user'
|
13
|
+
ConstantContact::Base.api_key = 'api-key'
|
14
|
+
ConstantContact::Base.password = 'password'
|
15
|
+
|
16
|
+
|
17
|
+
### Find Lists
|
18
|
+
|
19
|
+
ConstantContact::List.find(1)
|
20
|
+
ConstantContact::List.find :all
|
21
|
+
|
22
|
+
### Find A Contact
|
23
|
+
|
24
|
+
|
25
|
+
ConstantContact::Contact.find(1)
|
26
|
+
ConstantContact::Contact.find(:first, :params => {:email => 'jon@example.com'})
|
27
|
+
ConstantContact::Contact.find_by_email('jon@example.com') # => same as previous line
|
28
|
+
|
29
|
+
### Create a Contact (with rescue if it already exists)
|
30
|
+
|
31
|
+
ConstantContact::Base.user = 'user'
|
32
|
+
ConstantContact::Base.api_key = 'api-key'
|
33
|
+
ConstantContact::Base.password = 'password'
|
34
|
+
|
35
|
+
# Contact not found. Create it.
|
36
|
+
begin
|
37
|
+
@contact = ConstantContact::Contact.new(
|
38
|
+
:email_address => "jon@example.com",
|
39
|
+
:first_name => "jon",
|
40
|
+
:last_name => "smith"
|
41
|
+
)
|
42
|
+
@contact.save
|
43
|
+
rescue ActiveResource::ResourceConflict => e
|
44
|
+
# contact already exists
|
45
|
+
puts 'Contact already exists. Saving contact failed.'
|
46
|
+
puts e
|
47
|
+
end
|
48
|
+
|
49
|
+
### Find a Contact By Email Address, Check if They're a Member of the Default List
|
50
|
+
|
51
|
+
c = ConstantContact::Contact.find_by_email('jon@example.com')
|
52
|
+
@contact = ConstantContact::Contact.find(@contact.int_id)
|
53
|
+
puts 'In default contact list.' if @contact.contact_lists.include?(1) # contact_lists is an array of list ids
|
54
|
+
|
55
|
+
|
56
|
+
Copyright (c) 2009 Timothy Case, released under the MIT license
|
@@ -0,0 +1,13 @@
|
|
1
|
+
directory = File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'active_resource'
|
4
|
+
require 'action_pack'
|
5
|
+
require File.join(directory, 'constant_contact', 'formats')
|
6
|
+
require File.join(directory, 'constant_contact', 'base')
|
7
|
+
require File.join(directory, 'constant_contact', 'list');
|
8
|
+
require File.join(directory, 'constant_contact', 'member')
|
9
|
+
require File.join(directory, 'constant_contact', 'contact')
|
10
|
+
require File.join(directory, 'constant_contact', 'campaign')
|
11
|
+
require File.join(directory, 'constant_contact', 'contact_event')
|
12
|
+
require File.join(directory, 'constant_contact', 'activity')
|
13
|
+
require File.join(directory, 'constant_contact', 'email_address')
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module ConstantContact
|
3
|
+
class Activity < Base
|
4
|
+
self.format = ActiveResource::Formats::HtmlEncodedFormat
|
5
|
+
attr_accessor :contacts, :lists, :activity_type
|
6
|
+
|
7
|
+
def encode
|
8
|
+
post_data = "activityType=#{self.activity_type}"
|
9
|
+
post_data += self.encoded_data
|
10
|
+
post_data += self.encoded_lists
|
11
|
+
return post_data
|
12
|
+
end
|
13
|
+
|
14
|
+
def activity_type
|
15
|
+
@activity_type ||= "SV_ADD"
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
def encoded_data
|
20
|
+
result = "&data="
|
21
|
+
result += CGI.escape("Email Address,First Name,Last Name\n")
|
22
|
+
contact_strings = []
|
23
|
+
self.contacts.each do |contact|
|
24
|
+
contact_strings << "#{contact.email_address}, #{contact.first_name}, #{contact.last_name}"
|
25
|
+
end
|
26
|
+
result += CGI.escape(contact_strings.join("\n"))
|
27
|
+
return result
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def encoded_lists
|
32
|
+
result = ""
|
33
|
+
self.lists.each do |list|
|
34
|
+
result += "&lists="
|
35
|
+
result += CGI.escape(list.id)
|
36
|
+
end
|
37
|
+
return result
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module ConstantContact
|
2
|
+
class Base < ActiveResource::Base
|
3
|
+
|
4
|
+
self.site = "https://api.constantcontact.com"
|
5
|
+
self.format = :atom
|
6
|
+
|
7
|
+
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Returns an integer which can be used in #find calls.
|
11
|
+
# Assumes url structure with the id at the end, e.g.:
|
12
|
+
# http://api.constantcontact.com/ws/customers/yourname/contacts/29
|
13
|
+
def parse_id(url)
|
14
|
+
url.to_s.split(/\//).last.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def api_key
|
18
|
+
if defined?(@api_key)
|
19
|
+
@api_key
|
20
|
+
elsif superclass != Object && superclass.api_key
|
21
|
+
superclass.api_key.dup.freeze
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def api_key=(api_key)
|
26
|
+
@connection = nil
|
27
|
+
@api_key = api_key
|
28
|
+
end
|
29
|
+
|
30
|
+
def connection(refresh = false)
|
31
|
+
if defined?(@connection) || superclass == Object
|
32
|
+
@connection = ActiveResource::Connection.new(site, format) if refresh || @connection.nil?
|
33
|
+
@connection.user = "#{api_key}%#{user}" if user
|
34
|
+
@connection.password = password if password
|
35
|
+
@connection.timeout = timeout if timeout
|
36
|
+
@connection
|
37
|
+
else
|
38
|
+
superclass.connection
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def collection_path(prefix_options = {}, query_options = nil)
|
43
|
+
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
44
|
+
"/ws/customers/#{self.user}#{prefix(prefix_options)}#{collection_name}#{query_string(query_options)}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def element_path(id, prefix_options = {}, query_options = nil)
|
48
|
+
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
|
49
|
+
integer_id = parse_id(id)
|
50
|
+
id_val = integer_id.zero? ? nil : "/#{integer_id}"
|
51
|
+
"#{collection_path}#{id_val}#{query_string(query_options)}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Slight modification to AR::Base.find_every to handle instances
|
55
|
+
# where a single element is returned. This enables calling
|
56
|
+
# <tt>find(:first, {:params => {:email => 'sample@example.com'}})
|
57
|
+
def find_every(options)
|
58
|
+
case from = options[:from]
|
59
|
+
when Symbol
|
60
|
+
instantiate_collection(get(from, options[:params]))
|
61
|
+
when String
|
62
|
+
path = "#{from}#{query_string(options[:params])}"
|
63
|
+
instantiate_collection(connection.get(path, headers) || [])
|
64
|
+
else
|
65
|
+
prefix_options, query_options = split_options(options[:params])
|
66
|
+
path = collection_path(prefix_options, query_options)
|
67
|
+
result = connection.get(path, headers)
|
68
|
+
case result.class.name
|
69
|
+
when 'Hash': instantiate_collection( [ result ], prefix_options )
|
70
|
+
else
|
71
|
+
instantiate_collection( (result || []), prefix_options )
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Slightly tweaked ARes::Base's implementation so all the
|
78
|
+
# attribute names are looked up using camelcase since
|
79
|
+
# that's how the CC API returns them.
|
80
|
+
def method_missing(method_symbol, *arguments) #:nodoc:
|
81
|
+
method_name = method_symbol.to_s
|
82
|
+
|
83
|
+
case method_name.last
|
84
|
+
when "="
|
85
|
+
attributes[method_name.first(-1).camelize] = arguments.first
|
86
|
+
when "?"
|
87
|
+
attributes[method_name.first(-1).camelize]
|
88
|
+
else
|
89
|
+
attributes.has_key?(method_name.camelize) ? attributes[method_name.camelize] : super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Caching accessor for the the id integer
|
94
|
+
def int_id
|
95
|
+
@id ||= self.class.parse_id(self.attributes['id'])
|
96
|
+
end
|
97
|
+
|
98
|
+
# Mimics ActiveRecord's version
|
99
|
+
def update_attributes(atts={})
|
100
|
+
camelcased_hash = {}
|
101
|
+
atts.each{|key, val| camelcased_hash[key.to_s.camelize] = val}
|
102
|
+
self.attributes.update(camelcased_hash)
|
103
|
+
save
|
104
|
+
end
|
105
|
+
|
106
|
+
# Mimic ActiveRecord (snagged from HyperactiveResource).
|
107
|
+
def save
|
108
|
+
return false unless valid?
|
109
|
+
before_save
|
110
|
+
successful = super
|
111
|
+
after_save if successful
|
112
|
+
successful
|
113
|
+
end
|
114
|
+
|
115
|
+
def before_save
|
116
|
+
end
|
117
|
+
|
118
|
+
def after_save
|
119
|
+
end
|
120
|
+
|
121
|
+
def validate
|
122
|
+
end
|
123
|
+
|
124
|
+
# So client-side validations run
|
125
|
+
def valid?
|
126
|
+
errors.clear
|
127
|
+
validate
|
128
|
+
super
|
129
|
+
end
|
130
|
+
|
131
|
+
def encode
|
132
|
+
"<entry xmlns=\"http://www.w3.org/2005/Atom\">
|
133
|
+
<title type=\"text\"> </title>
|
134
|
+
<updated>#{Time.now.strftime(DATE_FORMAT)}</updated>
|
135
|
+
<author>Bluesteel</author>
|
136
|
+
<id>#{id.blank? ? 'data:,none' : id}</id>
|
137
|
+
<summary type=\"text\">Bluesteel</summary>
|
138
|
+
<content type=\"application/vnd.ctct+xml\">
|
139
|
+
#{self.to_xml}
|
140
|
+
</content>
|
141
|
+
</entry>"
|
142
|
+
end
|
143
|
+
|
144
|
+
# TODO: Move this out to a lib
|
145
|
+
def html_encode(txt)
|
146
|
+
mapping = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
147
|
+
txt.to_s.gsub(/[&"><]/) { |special| mapping[special] }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# http://developer.constantcontact.com/doc/manageCampaigns
|
2
|
+
module ConstantContact
|
3
|
+
class Campaign < Base
|
4
|
+
STATUS_CODES = ['SENT', 'SCHEDULED', 'DRAFT', 'RUNNING']
|
5
|
+
# SENT All campaigns that have been sent and not currently scheduled for resend
|
6
|
+
# SCHEDULED All campaigns that are currently scheduled to be sent some time in the future
|
7
|
+
# DRAFT All campaigns that have not yet been scheduled for delivery
|
8
|
+
# RUNNING All campaigns that are currently being processed and delivered
|
9
|
+
@@column_names = [:archive_status, :archive_url, :bounces, :campaign_type, :clicks, :contact_lists, :date,
|
10
|
+
:email_content, :email_content_format, :email_text_content, :forward_email_link_text, :forwards,
|
11
|
+
:from_email, :from_name, :greeting_name, :greeting_salutation, :greeting_string,
|
12
|
+
:include_forward_email, :include_subscribe_link, :last_edit_date, :name, :opens, :opt_outs,
|
13
|
+
:organization_address1, :organization_address2, :organization_address3, :organization_city,
|
14
|
+
:organization_country, :organization_international_state, :organization_name, :organization_postal_code,
|
15
|
+
:organization_state, :permission_reminder, :reply_to_email, :sent, :spam_reports, :status,
|
16
|
+
:style_sheet, :subject, :subscribe_link_text, :view_as_webpage, :view_as_webpage_link_text, :view_as_webpage_text]
|
17
|
+
|
18
|
+
|
19
|
+
# Setup defaults when creating a new object since
|
20
|
+
# CC requires so many extraneous fields to be present
|
21
|
+
# when creating a new Campaign.
|
22
|
+
def initialize
|
23
|
+
obj = super
|
24
|
+
obj.set_defaults
|
25
|
+
obj
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_xml
|
29
|
+
xml = Builder::XmlMarkup.new
|
30
|
+
xml.tag!("Campaign", :xmlns => "http://ws.constantcontact.com/ns/1.0/") do
|
31
|
+
self.attributes.each{ |k, v| xml.tag!(k.to_s.camelize, v) }
|
32
|
+
# Overrides the default formatting above to CC's required format.
|
33
|
+
xml.tag!("ReplyToEmail") do
|
34
|
+
xml.tag!('Email', :id => self.reply_to_email_url)
|
35
|
+
end
|
36
|
+
xml.tag!("FromEmail") do
|
37
|
+
xml.tag!('Email', :id => self.from_email_url)
|
38
|
+
end
|
39
|
+
xml.tag!("ContactLists") do
|
40
|
+
xml.tag!("ContactList", :id => self.list_url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_url
|
46
|
+
id = defined?(self.list_id) ? self.list_id : 1
|
47
|
+
List.find(id).id
|
48
|
+
end
|
49
|
+
|
50
|
+
def from_email_url
|
51
|
+
id = defined?(self.from_email_id) ? self.from_email_id : 1
|
52
|
+
EmailAddress.find(id).id
|
53
|
+
end
|
54
|
+
|
55
|
+
def reply_to_email_url
|
56
|
+
from_email_url
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
protected
|
61
|
+
def set_defaults
|
62
|
+
self.view_as_webpage = 'NO' unless attributes.has_key?('ViewAsWebpage')
|
63
|
+
self.from_name = self.class.user unless attributes.has_key?('FromName')
|
64
|
+
self.permission_reminder = 'YES' unless attributes.has_key?('PermissionReminder')
|
65
|
+
self.permission_reminder_text = %Q{You're receiving this email because of your relationship with us. Please <ConfirmOptin><a style="color:#0000ff;">confirm</a></ConfirmOptin> your continued interest in receiving email from us.} unless attributes.has_key?('PermissionReminderText')
|
66
|
+
self.greeting_salutation = 'Dear' unless attributes.has_key?('GreetingSalutation')
|
67
|
+
self.greeting_name = "FirstName" unless attributes.has_key?('GreetingName')
|
68
|
+
self.greeting_string = 'Greetings!' unless attributes.has_key?('GreetingString')
|
69
|
+
self.status = 'DRAFT' unless attributes.has_key?('Status')
|
70
|
+
self.style_sheet = '' unless attributes.has_key?('StyleSheet')
|
71
|
+
self.include_forward_email = 'NO' unless attributes.has_key?('IncludeForwardEmail')
|
72
|
+
self.forward_email_link_text = '' unless attributes.has_key?('ForwardEmailLinkText')
|
73
|
+
self.subscribe_link_text = '' unless attributes.has_key?('SubscribeLinkText')
|
74
|
+
self.include_subscribe_link = 'NO' unless attributes.has_key?('IncludeSubscribeLink')
|
75
|
+
self.organization_name = self.class.user unless attributes.has_key?('OrganizationName')
|
76
|
+
self.organization_address1 = '123 Main' unless attributes.has_key?('OrganizationAddress1')
|
77
|
+
self.organization_address2 = '' unless attributes.has_key?('OrganizationAddress2')
|
78
|
+
self.organization_address3 = '' unless attributes.has_key?('OrganizationAddress3')
|
79
|
+
self.organization_city = 'Kansas City' unless attributes.has_key?('OrganizationCity')
|
80
|
+
self.organization_state = 'KS' unless attributes.has_key?('OrganizationState')
|
81
|
+
self.organization_international_state = '' unless attributes.has_key?('OrganizationInternationalState')
|
82
|
+
self.organization_country = 'US' unless attributes.has_key?('OrganizationCountry')
|
83
|
+
self.organization_postal_code = '64108' unless attributes.has_key?('OrganizationPostalCode')
|
84
|
+
end
|
85
|
+
|
86
|
+
# Formats data if present.
|
87
|
+
def before_save
|
88
|
+
self.email_text_content = "<Text>#{email_text_content}</Text>" unless email_text_content.match /^\<Text/
|
89
|
+
self.date = self.date.strftime(DATE_FORMAT) if attributes.has_key?('Date')
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def validate
|
94
|
+
# NOTE: Needs to be uppercase!
|
95
|
+
unless attributes.has_key?('EmailContentFormat') && ['HTML', 'XHTML'].include?(email_content_format)
|
96
|
+
errors.add(:email_content_format, 'must be either HTML or XHTML (the latter for advanced email features)')
|
97
|
+
end
|
98
|
+
|
99
|
+
if attributes.has_key?('ViewAsWebpage') && view_as_webpage.downcase == 'yes'
|
100
|
+
unless attributes['ViewAsWebpageLinkText'].present? && attributes['ViewAsWebpageText'].present?
|
101
|
+
errors.add(:view_as_webpage, "You need to set view_as_webpage_link_text and view_as_webpage_link if view_as_webpage is YES")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
errors.add(:email_content, 'cannot be blank') unless attributes.has_key?('EmailContent')
|
106
|
+
errors.add(:email_text_content, 'cannot be blank') unless attributes.has_key?('EmailTextContent')
|
107
|
+
errors.add(:name, 'cannot be blank') unless attributes.has_key?('Name')
|
108
|
+
errors.add(:subject, 'cannot be blank') unless attributes.has_key?('Subject')
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Value limits: http://constantcontact.custhelp.com/cgi-bin/constantcontact.cfg/php/enduser/std_adp.php?p_faqid=2217
|
2
|
+
module ConstantContact
|
3
|
+
class Contact < Base
|
4
|
+
attr_accessor :opt_in_source
|
5
|
+
|
6
|
+
# NOTE: All column names are available only when finding a contact by id.
|
7
|
+
# @@column_names = [ :addr1, :addr2, :addr3, :city, :company_name, :country_code, :country_name,
|
8
|
+
# :custom_field1, :custom_field10, :custom_field11, :custom_field12, :custom_field13,
|
9
|
+
# :custom_field14, :custom_field15, :custom_field2, :custom_field3, :custom_field4, :custom_field5,
|
10
|
+
# :custom_field6, :custom_field7, :custom_field8, :custom_field9, :email_address, :email_type,
|
11
|
+
# :first_name, :home_phone, :insert_time, :job_title, :last_name, :last_update_time, :name, :note,
|
12
|
+
# :postal_code, :state_code, :state_name, :status, :sub_postal_code, :work_phone ]
|
13
|
+
|
14
|
+
def to_xml
|
15
|
+
xml = Builder::XmlMarkup.new
|
16
|
+
xml.tag!("Contact", :xmlns => "http://ws.constantcontact.com/ns/1.0/") do
|
17
|
+
self.attributes.reject {|k,v| k == 'ContactLists'}.each{|k, v| xml.tag!( k.to_s.camelize, v )}
|
18
|
+
xml.tag!("OptInSource", self.opt_in_source)
|
19
|
+
xml.tag!("ContactLists") do
|
20
|
+
@contact_lists = [1] if @contact_lists.nil? && self.new?
|
21
|
+
self.contact_lists.sort.each do |list_id|
|
22
|
+
xml.tag!("ContactList", :id=> self.list_url(list_id))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def opt_in_source
|
29
|
+
@opt_in_source ||= "ACTION_BY_CUSTOMER"
|
30
|
+
end
|
31
|
+
|
32
|
+
# see http://developer.constantcontact.com/doc/manageContacts#create_contact for more info about the two values.
|
33
|
+
def opt_in_source=(val)
|
34
|
+
@opt_in_source = val if ['ACTION_BY_CONTACT','ACTION_BY_CUSTOMER'].include?(val)
|
35
|
+
end
|
36
|
+
|
37
|
+
def list_url(id=nil)
|
38
|
+
id ||= defined?(self.list_id) ? self.list_id : 1
|
39
|
+
"http://api.constantcontact.com/ws/customers/#{self.class.user}/lists/#{id}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Can we email them?
|
43
|
+
def active?
|
44
|
+
status.downcase == 'active'
|
45
|
+
end
|
46
|
+
|
47
|
+
def contact_lists
|
48
|
+
return @contact_lists if defined?(@contact_lists)
|
49
|
+
# otherwise, attempt to assign it
|
50
|
+
@contact_lists = if self.attributes.keys.include?('ContactLists')
|
51
|
+
if self.ContactLists
|
52
|
+
if self.ContactLists.ContactList.is_a?(Array)
|
53
|
+
self.ContactLists.ContactList.collect { |list|
|
54
|
+
ConstantContact::Base.parse_id(list.id)
|
55
|
+
}
|
56
|
+
else
|
57
|
+
[ ConstantContact::Base.parse_id(self.ContactLists.ContactList.id) ]
|
58
|
+
end
|
59
|
+
else
|
60
|
+
[] # => Contact is not a member of any lists (legitimatly empty!)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def contact_lists=(val)
|
68
|
+
@contact_lists = val.kind_of?(Array) ? val : [val]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.find_by_email(email_address)
|
72
|
+
find :first, {:params => {:email => email_address.downcase}}
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
def validate
|
77
|
+
# errors.add(:opt_in_source, 'must be either ACTION_BY_CONTACT or ACTION_BY_CUSTOMER') unless ['ACTION_BY_CONTACT','ACTION_BY_CUSTOMER'].include?(attributes['OptInSource'])
|
78
|
+
# errors.add(:email_address, 'cannot be blank') unless attributes.has_key?('EmailAddress')
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|