exact_target_sdk 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ = ExactTarget SDK
2
+
3
+ == Version 0.0.0
4
+ * Initial release
5
+ * Supports Create method
6
+ * Supports Subscriber and TriggeredSend objects, allowing subscribers to be created and triggered emails to be sent to them
data/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 RevPAR Collective, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = ExactTarget SDK
2
+
3
+ An object-oriented wrapper for the ExactTarget SOAP API.
4
+
5
+ The ExactTarget web service guide can be viewed here:
6
+ http://docs.code.exacttarget.com/020_Web_Service_Guide
7
+
8
+ With few exceptions, ruby conventions for capitalization are ignored and those
9
+ outlined in the guide linked above are used. This is done in an attempt to be
10
+ as transparent as possible, so that the API may be used by referring only to
11
+ the guide linked above.
12
+
13
+ Note this SDK is currently very incomplete, allowing you only to create
14
+ subscribers and trigger sends. The framework is in place, however, to very
15
+ easily implement new objects by simply declaring their properties.
16
+
17
+ == Synopsis:
18
+
19
+ ExactTargetSDK.config(:username => 'foo', :password => 'mypass')
20
+
21
+ client = ExactTargetSDK::Client.new
22
+ definition = TriggeredSendDefinition.new('CustomerKey' => 'my_triggered_send')
23
+ subscriber = Subscriber.new('EmailAddress' => 'me@example.com')
24
+ triggered_send = TriggeredSend.new('TriggeredSendDefinition' => definition)
25
+ triggered_send.subscribers << subscriber
26
+
27
+ # Creates subscriber record, then executes the "my_triggered_send" trigger to
28
+ # that subscriber.
29
+ response = client.Create(subscriber, triggered_send)
30
+
31
+ puts "response status: #{response.OverallStatus}"
32
+ response.Results.each do |result|
33
+ puts "result..."
34
+ puts " result code: #{result.StatusCode}"
35
+ puts " result message: #{result.StatusMessage}"
36
+ end
@@ -0,0 +1,14 @@
1
+ require 'exact_target_sdk/config'
2
+ require 'exact_target_sdk/errors'
3
+
4
+ module ExactTargetSDK
5
+
6
+ autoload :APIObject, 'exact_target_sdk/api_object'
7
+ autoload :Client, 'exact_target_sdk/client'
8
+ autoload :CreateResponse, 'exact_target_sdk/create_response'
9
+ autoload :CreateResult, 'exact_target_sdk/create_result'
10
+ autoload :Subscriber, 'exact_target_sdk/subscriber'
11
+ autoload :TriggeredSend, 'exact_target_sdk/triggered_send'
12
+ autoload :TriggeredSendDefinition, 'exact_target_sdk/triggered_send_definition'
13
+
14
+ end
@@ -0,0 +1,147 @@
1
+ require 'active_model'
2
+
3
+ module ExactTargetSDK
4
+
5
+ # Parent class of all ExactTarget API objects (listed here:
6
+ # http://docs.code.exacttarget.com/020_Web_Service_Guide/Objects). Provides
7
+ # class-level declarations, validation, and rendering that makes modeling
8
+ # each object easy.
9
+ class APIObject
10
+
11
+ include ::ActiveModel::Validations
12
+
13
+ class << self
14
+
15
+ # Declares a property of this object, optionally requiring it upon
16
+ # validation.
17
+ #
18
+ # Provides a getter and setter for this property, keeping track of
19
+ # whether or not it has been set and registering it for rendering.
20
+ def property(name, required = false)
21
+ name = name.to_s
22
+ attr_reader name.to_sym
23
+ class_eval <<-__EOF__
24
+ def #{name}=(value)
25
+ @_set_#{name} = true
26
+ @#{name} = value
27
+ end
28
+ __EOF__
29
+ if required
30
+ validates name.to_sym, :presence => true
31
+ end
32
+ register_property!(name)
33
+ end
34
+
35
+ # Declares a property as an array of values.
36
+ #
37
+ # Provides a getter and setter for this property. The getter will
38
+ # always return an array (not null), so the client may simply append
39
+ # to this property.
40
+ #
41
+ # Note that once the property has been either read or written to, it
42
+ # will be rendered.
43
+ def array_property(name)
44
+ # TODO: type validation would be nice
45
+ name = name.to_s
46
+ class_eval <<-__EOF__
47
+ def #{name}
48
+ @_set_#{name} = true
49
+ @#{name} ||= []
50
+ end
51
+ def #{name}=(value)
52
+ @_set_#{name} = true
53
+ @#{name} = value
54
+ end
55
+ __EOF__
56
+ register_property!(name)
57
+ end
58
+
59
+ # Same as #property, adding validation the the provided value is an
60
+ # integer.
61
+ def int_property(name, required = false)
62
+ property(name, required)
63
+ validates name.to_sym, :numericality => { :allow_nil => true, :only_integer => true }
64
+ end
65
+
66
+ # Takes one or more method names as symbols, and executes them in order
67
+ # before validation occurs on this object.
68
+ def before_validation(*args)
69
+ before_validation_methods.concat(args)
70
+ end
71
+
72
+ # Returns an array of all registered properties.
73
+ def properties
74
+ @properties || []
75
+ end
76
+
77
+ private
78
+
79
+ # Returns the method names declared using #before_validation.
80
+ def before_validation_methods
81
+ @before_validation_methods ||= []
82
+ end
83
+
84
+ # Stores the given property name to be used at render time.
85
+ def register_property!(name)
86
+ @properties ||= []
87
+ @properties << name
88
+ @properties.uniq!
89
+ end
90
+
91
+ end
92
+
93
+ # By default, any properties may be passed and set.
94
+ #
95
+ # May be overridden.
96
+ def initialize(properties = {})
97
+ properties.each do |key, value|
98
+ self.send "#{key}=", value
99
+ end
100
+ end
101
+
102
+ # By default, returns the name of the class.
103
+ #
104
+ # May be overridden.
105
+ def type_name
106
+ self.class.name.split('::').last
107
+ end
108
+
109
+ # By default, runs validation and executes #render_properties!.
110
+ #
111
+ # If overridden, the child class should execute the before_validation
112
+ # methods, check wehter or not the object is valid, and then render
113
+ # the object.
114
+ def render!(xml)
115
+ self.class.before_validation_methods.each { |method| self.send(method) }
116
+ raise(InvalidAPIObject, self) if invalid?
117
+ render_properties!(xml)
118
+ end
119
+
120
+ # By default, loops through all registered properties, and renders
121
+ # each that has been explicitly set.
122
+ #
123
+ # May be overridden.
124
+ def render_properties!(xml)
125
+ self.class.properties.each do |property|
126
+ next unless instance_variable_get("@_set_#{property}")
127
+
128
+ property_value = self.send(property)
129
+
130
+ if property_value.is_a?(APIObject)
131
+ xml.__send__(property) do
132
+ property_value.render!(xml)
133
+ end
134
+ elsif property_value.is_a?(Array)
135
+ property_value.each do |current|
136
+ xml.__send__(property) do
137
+ current.render!(xml)
138
+ end
139
+ end
140
+ else
141
+ xml.__send__(property, property_value)
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,140 @@
1
+ require 'guid'
2
+ require 'savon'
3
+ require 'timeout'
4
+
5
+ module ExactTargetSDK
6
+
7
+ # Provides an object-oriented API to ExactTarget's Web Service API
8
+ # (http://docs.code.exacttarget.com/020_Web_Service_Guide)
9
+ #
10
+ # With few exceptions, ruby conventions for capitalization are ignored and those
11
+ # outlined in the guide linked above are used. This is done in an attempt to be
12
+ # as transparent as possible, so that the API may be used by referring only to
13
+ # the guide linked above.
14
+ class Client
15
+
16
+ # Constructs a client.
17
+ #
18
+ # Any of the options documented in ExactTargetSDK#config may be overridden
19
+ # using the options parameter.
20
+ #
21
+ # Since ExactTarget's API is stateless, constructing a client object will not
22
+ # make any remote calls.
23
+ def initialize(options = {})
24
+ self.config = {
25
+ }.merge!(ExactTargetSDK.config).merge!(options)
26
+
27
+ Savon.configure do |c|
28
+ c.logger = config[:logger]
29
+ c.raise_errors = false
30
+ end
31
+
32
+ initialize_client!
33
+ end
34
+
35
+ # Invokes the Create method.
36
+ #
37
+ # The provided arguments should each be sub-classes of APIObject, and each
38
+ # provided object will be created in order.
39
+ #
40
+ # Possible exceptions are:
41
+ # HTTPError if an HTTP error (such as a timeout) occurs
42
+ # SOAPFault if a SOAP fault occurs
43
+ # Timeout if there is a timeout waiting for the response
44
+ # InvalidAPIObject if any of the provided objects don't pass validation
45
+ #
46
+ # Returns a CreateResponse object.
47
+ def Create(*args)
48
+ # TODO: implement and accept CreateOptions
49
+
50
+ api_objects = args
51
+
52
+ response = execute_request 'Create' do |xml|
53
+ xml.CreateRequest do
54
+ xml.Options # TODO: support CreateOptions
55
+
56
+ api_objects.each do |api_object|
57
+ xml.Objects "xsi:type" => api_object.type_name do
58
+ api_object.render!(xml)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ CreateResponse.new(response)
65
+ end
66
+
67
+ private
68
+
69
+ attr_accessor :config, :client
70
+
71
+ # Constructs and saves the savon client using provided config.
72
+ def initialize_client!
73
+ self.client = ::Savon::Client.new do
74
+ wsdl.endpoint = config[:endpoint]
75
+ wsdl.namespace = config[:namespace]
76
+ http.open_timeout = config[:open_timeout]
77
+ http.read_timeout = config[:read_timeout]
78
+ end
79
+ end
80
+
81
+ # Builds the SOAP request for the given method, delegating body
82
+ # rendering to the provided block.
83
+ #
84
+ # Handles errors and re-raises with appropriate sub-class of
85
+ # ExactTargetSDK::Error.
86
+ #
87
+ # Returns the raw savon response.
88
+ def execute_request(method)
89
+ begin
90
+ response = client.request(method) do
91
+ soap.xml do |xml|
92
+ xml.s :Envelope,
93
+ "xmlns" => config[:namespace],
94
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
95
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
96
+ "xmlns:s" => "http://www.w3.org/2003/05/soap-envelope",
97
+ "xmlns:a" => "http://schemas.xmlsoap.org/ws/2004/08/addressing",
98
+ "xmlns:o" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" do
99
+
100
+ xml.s :Header do
101
+ xml.a :Action, method, "s:mustUnderstand" => "1"
102
+ xml.a :MessageID, "uuid:#{Guid.new.to_s}"
103
+ xml.a :ReplyTo do
104
+ xml.a :Address, "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"
105
+ end
106
+ xml.a :To, config[:endpoint], "s:mustUnderstand" => "1"
107
+ xml.o :Security, "s:mustUnderstand" => "1" do
108
+ xml.o :UsernameToken, "o:Id" => "test" do
109
+ xml.o :Username, config[:username]
110
+ xml.o :Password, config[:password]
111
+ end
112
+ end
113
+ end
114
+
115
+ xml.s :Body do
116
+ yield(xml)
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ if response.http_error?
123
+ raise HTTPError, response.http_error.to_s
124
+ end
125
+
126
+ if response.soap_fault?
127
+ raise SOAPFault, response.soap_fault.to_s
128
+ end
129
+
130
+ response
131
+ rescue Timeout::Error => e
132
+ timeout = ::ExactTargetSDK::Timeout.new("#{e.message}; open_timeout: #{config[:open_timeout]}; read_timeout: #{config[:read_timeout]}")
133
+ timeout.set_backtrace(e.backtrace)
134
+ raise timeout
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,61 @@
1
+ require 'logger'
2
+
3
+ module ExactTargetSDK
4
+
5
+ DEFAULT_TIMEOUT = 15
6
+ DEFAULT_ENDPOINT = 'https://webservice.s4.exacttarget.com/Service.asmx'
7
+ DEFAULT_NAMESPACE = 'http://exacttarget.com/wsdl/partnerAPI'
8
+
9
+ # Globally configures and retrieves configuration for the ExactTarget SDK.
10
+ #
11
+ # == Environment Variables
12
+ #
13
+ # For convenience in a command-line environment, configuration may be skipped
14
+ # by setting the EXACT_TARGET_SDK_USERNAME and EXACT_TARGET_SDK_PASSWORD
15
+ # environment variables, which are self-explanatory.
16
+ #
17
+ # == Rails
18
+ #
19
+ # If running in a rails environment, this configuration will automatically use
20
+ # the global Rails.logger instance. This behavior may be overridden by passing
21
+ # in a :logger option.
22
+ #
23
+ # @param [Hash] options
24
+ # @option options [String] :username (nil) ExactTarget account username
25
+ # @option options [String] :password (nil) ExactTarget account password
26
+ # @option options [Logger] :logger (Rails.logger) Logger to use
27
+ # @option options [Numeric] :open_timeout (ExactTargetSDK::DEFAULT_TIMEOUT)
28
+ # Number of seconds to wait for the connection to open
29
+ # (see Net::HTTP#open_timeout)
30
+ # @option options [Numeric] :read_timeout (ExactTargetSDK::DEFAULT_TIMEOUT)
31
+ # Number of seconds to wait for one block to be read
32
+ # (see Net::HTTP#read_timeout)
33
+ def self.config(options = nil)
34
+ @config ||= {
35
+ :username => ENV['EXACT_TARGET_SDK_USERNAME'],
36
+ :password => ENV['EXACT_TARGET_SDK_PASSWORD'],
37
+ :logger => default_logger,
38
+ :open_timeout => DEFAULT_TIMEOUT,
39
+ :read_timeout => DEFAULT_TIMEOUT,
40
+ :endpoint => DEFAULT_ENDPOINT,
41
+ :namespace => DEFAULT_NAMESPACE,
42
+ }
43
+
44
+ @config.merge!(options) if options
45
+
46
+ @config
47
+ end
48
+
49
+ private
50
+
51
+ def self.default_logger
52
+ if defined?(::Rails)
53
+ ::Rails.logger
54
+ else
55
+ logger = ::Logger.new(STDERR)
56
+ logger.level = ::Logger::INFO
57
+ logger
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,17 @@
1
+ module ExactTargetSDK
2
+ class CreateResponse
3
+
4
+ attr_reader :OverallStatus, :RequestID, :Results
5
+
6
+ def initialize(response)
7
+ response = response.to_hash[:create_response]
8
+ @OverallStatus = response[:overall_status]
9
+ @RequestID = response[:request_id]
10
+ @Results = []
11
+ (response[:results] || []).each do |result|
12
+ @Results << CreateResult.new(result)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module ExactTargetSDK
2
+ class CreateResult
3
+
4
+ attr_reader :StatusCode, :StatusMessage
5
+
6
+ def initialize(hash)
7
+ @StatusCode = hash[:status_code]
8
+ @StatusMessage = hash[:status_message]
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module ExactTargetSDK
2
+
3
+ # Parent of all errors raised by this SDK
4
+ class Error < ::StandardError
5
+ end
6
+
7
+ # Indicates an "HTTP error" as defined by savon
8
+ class HTTPError < Error
9
+ end
10
+
11
+ # Indicates a "SOAP fault" as defined by savon
12
+ class SOAPFault < Error
13
+ end
14
+
15
+ # Indicates the open or read timeouts were reached
16
+ class Timeout < Error
17
+ end
18
+
19
+ # Indicates validation failed on an APIObject, which is referenced
20
+ # in the exception.
21
+ class InvalidAPIObject < Error
22
+
23
+ attr_reader :api_object
24
+
25
+ def initialize(api_object)
26
+ @api_object = api_object
27
+ end
28
+
29
+ def message
30
+ "#{api_object.type_name} object is invalid: #{api_object.errors.full_messages.join('; ')}"
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,31 @@
1
+ module ExactTargetSDK
2
+
3
+ # Assumes that the "SubscriberKey feature" is not enabled on your account, and
4
+ # thus explicitly sets the SubscriberKey to be the same as the EmailAddress
5
+ # (and vice-versa). This allows all methods to be used without ever needing to
6
+ # refer to the SubscriberKey (just use EmailAddress).
7
+ #
8
+ # If the SubscriberKey is explicitly set, it will be left alone (in case you do
9
+ # have the SubscriberKey feature enabled).
10
+ #
11
+ # When updating, the email address may be updated by setting the SubscriberKey
12
+ # property to the current email address, and the EmailAddress property to the
13
+ # new email address.
14
+ class Subscriber < APIObject
15
+
16
+ property 'SubscriberKey', true
17
+ property 'EmailAddress', true
18
+ array_property 'Attributes'
19
+
20
+ before_validation :sync_subscriber_key_and_email_address
21
+
22
+ private
23
+
24
+ def sync_subscriber_key_and_email_address
25
+ self.SubscriberKey = self.EmailAddress if self.SubscriberKey.nil?
26
+ self.EmailAddress = self.SubscriberKey if self.EmailAddress.nil?
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,8 @@
1
+ module ExactTargetSDK
2
+ class TriggeredSend < APIObject
3
+
4
+ property 'TriggeredSendDefinition', true
5
+ array_property 'Subscribers'
6
+
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module ExactTargetSDK
2
+ class TriggeredSendDefinition < APIObject
3
+
4
+ property 'CustomerKey', true
5
+
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ExactTargetSDK
2
+ VERSION = '0.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exact_target_sdk
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - David Dawson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-30 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ hash: 5
27
+ segments:
28
+ - 3
29
+ - 1
30
+ version: "3.1"
31
+ version_requirements: *id001
32
+ name: activemodel
33
+ prerelease: false
34
+ type: :runtime
35
+ - !ruby/object:Gem::Dependency
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ hash: 9
42
+ segments:
43
+ - 0
44
+ - 1
45
+ version: "0.1"
46
+ version_requirements: *id002
47
+ name: guid
48
+ prerelease: false
49
+ type: :runtime
50
+ - !ruby/object:Gem::Dependency
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ hash: 25
57
+ segments:
58
+ - 0
59
+ - 9
60
+ version: "0.9"
61
+ version_requirements: *id003
62
+ name: savon
63
+ prerelease: false
64
+ type: :runtime
65
+ description: Provides an easy-to-use ruby interface into the ExactTarget SOAP API, using the Savon client.
66
+ email: daws23@gmail.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files:
72
+ - README.rdoc
73
+ - CHANGELOG.rdoc
74
+ - LICENSE.txt
75
+ files:
76
+ - lib/exact_target_sdk/api_object.rb
77
+ - lib/exact_target_sdk/client.rb
78
+ - lib/exact_target_sdk/config.rb
79
+ - lib/exact_target_sdk/create_response.rb
80
+ - lib/exact_target_sdk/create_result.rb
81
+ - lib/exact_target_sdk/errors.rb
82
+ - lib/exact_target_sdk/subscriber.rb
83
+ - lib/exact_target_sdk/triggered_send.rb
84
+ - lib/exact_target_sdk/triggered_send_definition.rb
85
+ - lib/exact_target_sdk/version.rb
86
+ - lib/exact_target_sdk.rb
87
+ - README.rdoc
88
+ - CHANGELOG.rdoc
89
+ - LICENSE.txt
90
+ homepage: https://github.com/daws/exact_target_sdk
91
+ licenses: []
92
+
93
+ post_install_message:
94
+ rdoc_options:
95
+ - --main
96
+ - README.rdoc
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.8.10
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: A simple wrapper for the ExactTarget SOAP API.
124
+ test_files: []
125
+