Skip to content

Commit

Permalink
Added some convenience code to deal with ConstantContact's camelcased…
Browse files Browse the repository at this point in the history
… XML

nodes (and ruby's tradition underscore'd)
Added some ActiveRecord-ish functionality: custom validation (via
validate()), update_attributes(), and before_save and after_save hooks
Beginnings of defining Email addresses (email_address.rb)
Beginnings of Campaign creation.
  • Loading branch information
bassnode committed Mar 24, 2010
1 parent cd8e94a commit 1bf4f72
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 10 deletions.
3 changes: 2 additions & 1 deletion lib/constant_contact.rb
Expand Up @@ -9,4 +9,5 @@
require File.join(directory, 'constant_contact', 'contact')
require File.join(directory, 'constant_contact', 'campaign')
require File.join(directory, 'constant_contact', 'contact_event')
require File.join(directory, 'constant_contact', 'activity')
require File.join(directory, 'constant_contact', 'activity')
require File.join(directory, 'constant_contact', 'email_address')
79 changes: 72 additions & 7 deletions lib/constant_contact/base.rb
@@ -1,11 +1,19 @@

module ConstantContact
class Base < ActiveResource::Base

self.site = "https://api.constantcontact.com"
self.format = :atom

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

class << self


# Returns an integer which can be used in #find calls.
# Assumes url structure with the id at the end, e.g.:
# http://api.constantcontact.com/ws/customers/yourname/contacts/29
def parse_id(url)
url.to_s.split(/\//).last.to_i
end

def api_key
if defined?(@api_key)
@api_key
Expand Down Expand Up @@ -38,23 +46,80 @@ def collection_path(prefix_options = {}, query_options = nil)

def element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
"#{collection_path}/#{id}"
integer_id = parse_id(id)
id_val = integer_id.zero? ? nil : "/#{integer_id}"
"#{collection_path}#{id_val}#{query_string(query_options)}"
end
end

# Slightly tweaked ARes::Base's implementation so all the
# attribute names are looked up using camelcase since
# that's how the CC API returns them.
def method_missing(method_symbol, *arguments) #:nodoc:
method_name = method_symbol.to_s

case method_name.last
when "="
attributes[method_name.first(-1).camelize] = arguments.first
when "?"
attributes[method_name.first(-1).camelize]
else
attributes.has_key?(method_name.camelize) ? attributes[method_name.camelize] : super
end
end

# Caching accessor for the the id integer
def int_id
@id ||= self.class.parse_id(self.attributes['id'])
end

# Mimics ActiveRecord's version
def update_attributes(atts={})
camelcased_hash = {}
atts.each{|key, val| camelcased_hash[key.to_s.camelize] = val}
self.attributes.update(camelcased_hash)
save
end

# Mimic ActiveRecord (snagged from HyperactiveResource).
def save
return false unless valid?
before_save
successful = super
after_save if successful
successful
end

def before_save
end

def after_save
end

# So client-side validations run
def valid?
errors.clear
validate
super
end

def encode
"<entry xmlns=\"http://www.w3.org/2005/Atom\">
<title type=\"text\"> </title>
<updated>2008-07-23T14:21:06.407Z</updated>
<updated>#{Time.now.strftime(DATE_FORMAT)}</updated>
<author>Bluesteel</author>
<id>data:,none</id>
<id>#{id.blank? ? 'data:,none' : id}</id>
<summary type=\"text\">Bluesteel</summary>
<content type=\"application/vnd.ctct+xml\">
#{self.to_xml}
</content>
</entry>"
end


# TODO: Move this out to a lib
def html_encode(txt)
mapping = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
txt.to_s.gsub(/[&"><]/) { |special| mapping[special] }
end
end
end
114 changes: 114 additions & 0 deletions lib/constant_contact/campaign.rb
@@ -1,4 +1,118 @@
# http://developer.constantcontact.com/doc/manageCampaigns
module ConstantContact
class Campaign < Base
STATUS_CODES = ['SENT', 'SCHEDULED', 'DRAFT', 'RUNNING']
# SENT All campaigns that have been sent and not currently scheduled for resend
# SCHEDULED All campaigns that are currently scheduled to be sent some time in the future
# DRAFT All campaigns that have not yet been scheduled for delivery
# RUNNING All campaigns that are currently being processed and delivered
@@column_names = [:archive_status, :archive_url, :bounces, :campaign_type, :clicks, :contact_lists, :date,
:email_content, :email_content_format, :email_text_content, :forward_email_link_text, :forwards,
:from_email, :from_name, :greeting_name, :greeting_salutation, :greeting_string,
:include_forward_email, :include_subscribe_link, :last_edit_date, :name, :opens, :opt_outs,
:organization_address1, :organization_address2, :organization_address3, :organization_city,
:organization_country, :organization_international_state, :organization_name, :organization_postal_code,
:organization_state, :permission_reminder, :reply_to_email, :sent, :spam_reports, :status,
:style_sheet, :subject, :subscribe_link_text, :view_as_webpage, :view_as_webpage_link_text, :view_as_webpage_text]


# Setup defaults when creating a new object since
# CC requires so many extraneous fields to be present
# when creating a new Campaign.
def initialize
obj = super
obj.set_defaults
obj
end

def to_xml
xml = Builder::XmlMarkup.new
xml.tag!("Campaign", :xmlns => "http://ws.constantcontact.com/ns/1.0/") do
self.attributes.each{ |k, v| xml.tag!(k.to_s.camelize, v) }
# Overrides the default formatting above to CC's required format.
xml.tag!("ReplyToEmail") do
xml.tag!('Email', :id => self.reply_to_email_url)
end
xml.tag!("FromEmail") do
xml.tag!('Email', :id => self.from_email_url)
end
xml.tag!("ContactLists") do
xml.tag!("ContactList", :id => self.list_url)
end
end
end

def list_url
id = defined?(self.list_id) ? self.list_id : 1
List.find(id).id
end

def from_email_url
id = defined?(self.from_email_id) ? self.from_email_id : 1
EmailAddress.find(id).id
end

def reply_to_email_url
from_email_url
end


protected
def set_defaults
self.view_as_webpage = 'NO' unless attributes.has_key?('ViewAsWebpage')
self.from_name = self.class.user unless attributes.has_key?('FromName')
self.permission_reminder = 'YES' unless attributes.has_key?('PermissionReminder')
self.permission_reminder_text = %Q{You're receiving this email because of your relationship with us. Please &lt;ConfirmOptin>&lt;a style="color:#0000ff;">confirm&lt;/a>&lt;/ConfirmOptin> your continued interest in receiving email from us.} unless attributes.has_key?('PermissionReminderText')
self.greeting_salutation = 'Dear' unless attributes.has_key?('GreetingSalutation')
self.greeting_name = "FirstName" unless attributes.has_key?('GreetingName')
self.greeting_string = 'Greetings!' unless attributes.has_key?('GreetingString')
self.status = 'DRAFT' unless attributes.has_key?('Status')
self.style_sheet = '' unless attributes.has_key?('StyleSheet')
self.include_forward_email = 'NO' unless attributes.has_key?('IncludeForwardEmail')
self.forward_email_link_text = '' unless attributes.has_key?('ForwardEmailLinkText')
self.subscribe_link_text = '' unless attributes.has_key?('SubscribeLinkText')
self.include_subscribe_link = 'NO' unless attributes.has_key?('IncludeSubscribeLink')
self.organization_name = self.class.user unless attributes.has_key?('OrganizationName')
self.organization_address1 = '123 Main' unless attributes.has_key?('OrganizationAddress1')
self.organization_address2 = '' unless attributes.has_key?('OrganizationAddress2')
self.organization_address3 = '' unless attributes.has_key?('OrganizationAddress3')
self.organization_city = 'Kansas City' unless attributes.has_key?('OrganizationCity')
self.organization_state = 'KS' unless attributes.has_key?('OrganizationState')
self.organization_international_state = '' unless attributes.has_key?('OrganizationInternationalState')
self.organization_country = 'US' unless attributes.has_key?('OrganizationCountry')
self.organization_postal_code = '64108' unless attributes.has_key?('OrganizationPostalCode')
end

# Encodes and formats data if present.
def before_save
unless @encoded
self.style_sheet = html_encode(style_sheet) if attributes.has_key?('StyleSheet')
self.permission_reminder_text = html_encode(permission_reminder_text) if attributes.has_key?('PermissionReminderText')
self.email_content = html_encode(email_content)
self.email_text_content = "<Text>#{email_text_content}</Text>"
self.date = self.date.strftime(DATE_FORMAT) if attributes.has_key?('Date')
@encoded = true
end

end


def validate
unless attributes.has_key?('EmailContentFormat') && ['html', 'xhtml'].include?(email_content_format.downcase)
errors.add(:email_content_format, 'must be either HTML or XHTML (the latter for advanced email features)')
end

if attributes.has_key?('ViewAsWebpage') && view_as_webpage.downcase == 'yes'
unless attributes['ViewAsWebpageLinkText'].present? && attributes['ViewAsWebpageText'].present?
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")
end
end

errors.add(:email_content, 'cannot be blank') unless attributes.has_key?('EmailContent')
errors.add(:email_text_content, 'cannot be blank') unless attributes.has_key?('EmailTextContent')
errors.add(:name, 'cannot be blank') unless attributes.has_key?('Name')
errors.add(:subject, 'cannot be blank') unless attributes.has_key?('Subject')
end

end
end
15 changes: 13 additions & 2 deletions lib/constant_contact/contact.rb
@@ -1,11 +1,19 @@
# Value limits: http://constantcontact.custhelp.com/cgi-bin/constantcontact.cfg/php/enduser/std_adp.php?p_faqid=2217
module ConstantContact
class Contact < Base
attr_accessor :opt_in_source

# @@column_names = [ :addr1, :addr2, :addr3, :city, :company_name, :country_code, :country_name,
# :custom_field1, :custom_field10, :custom_field11, :custom_field12, :custom_field13,
# :custom_field14, :custom_field15, :custom_field2, :custom_field3, :custom_field4, :custom_field5,
# :custom_field6, :custom_field7, :custom_field8, :custom_field9, :email_address, :email_type,
# :first_name, :home_phone, :insert_time, :job_title, :last_name, :last_update_time, :name, :note,
# :postal_code, :state_code, :state_name, :status, :sub_postal_code, :work_phone ]

def to_xml
xml = Builder::XmlMarkup.new
xml.tag!("Contact", :xmlns => "http://ws.constantcontact.com/ns/1.0/") do
self.attributes.each{|k, v| xml.tag!(k.camelize, v)}
self.attributes.each{|k, v| xml.tag!( k.to_s.camelize, v )}
xml.tag!("OptInSource", self.opt_in_source)
xml.tag!("ContactLists") do
xml.tag!("ContactList", :id=> self.list_url)
Expand All @@ -21,7 +29,10 @@ def list_url
id = defined?(self.list_id) ? self.list_id : 1
"http://api.constantcontact.com/ws/customers/#{self.class.user}/lists/#{id}"
end


# Can we email them?
def active?
status.downcase == 'active'
end
end
end
2 changes: 2 additions & 0 deletions lib/constant_contact/list.rb
@@ -1,5 +1,7 @@
module ConstantContact
class List < Base

# @@column_names = [:contact_count, :display_on_signup, :members, :name, :opt_in_default, :short_name, :sort_order]

def self.find_by_name(name)
lists = self.find :all
Expand Down

0 comments on commit 1bf4f72

Please sign in to comment.