markety 1.4.3 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/.gitignore +3 -0
- data/README.md +66 -15
- data/Rakefile +1 -1
- data/example_xml/get_lead_failure.xml +42 -0
- data/example_xml/get_lead_success.xml +63 -0
- data/example_xml/get_lead_success_two_leads.xml +88 -0
- data/example_xml/list_op_add_to_list_success.xml +42 -0
- data/example_xml/list_op_is_member_of_list_failure.xml +50 -0
- data/example_xml/list_op_is_member_of_list_success.xml +51 -0
- data/example_xml/list_op_remove_from_list_failure.xml +50 -0
- data/example_xml/list_op_remove_from_list_success.xml +42 -0
- data/example_xml/sync_lead_failure.xml +60 -0
- data/example_xml/sync_lead_success.xml +78 -0
- data/lib/markety.rb +26 -1
- data/lib/markety/authentication_header.rb +3 -3
- data/lib/markety/client.rb +35 -141
- data/lib/markety/command.rb +11 -0
- data/lib/markety/command/get_lead.rb +27 -0
- data/lib/markety/command/list_operation.rb +68 -0
- data/lib/markety/command/sync_lead.rb +55 -0
- data/lib/markety/enums.rb +18 -16
- data/lib/markety/lead.rb +69 -0
- data/lib/markety/lead_key.rb +5 -4
- data/lib/markety/response.rb +15 -0
- data/lib/markety/response/generic_response.rb +45 -0
- data/lib/markety/response/get_lead_response.rb +35 -0
- data/lib/markety/response/list_operation_response.rb +30 -0
- data/lib/markety/response/response_factory.rb +27 -0
- data/lib/markety/response/sync_lead_response.rb +32 -0
- data/lib/markety/version.rb +1 -3
- data/spec/markety/lead_key_spec.rb +10 -11
- data/spec/markety/{lead_record_spec.rb → lead_spec.rb} +13 -30
- data/spec/markety/response/get_lead_response_spec.rb +170 -0
- data/spec/markety/response/list_operation_response_spec.rb +76 -0
- data/spec/markety/response/sync_lead_response_spec.rb +107 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/savon_helper.rb +14 -0
- metadata +61 -33
- data/lib/markety/lead_record.rb +0 -73
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'markety/command/get_lead'
|
2
|
+
require 'markety/command/sync_lead'
|
3
|
+
require 'markety/command/list_operation'
|
4
|
+
|
5
|
+
module Markety
|
6
|
+
# All Command submodules are included in the Markety::Client class.
|
7
|
+
# They are implemented in separate modules only for maintainability
|
8
|
+
# (specifically, to keep Markety::Client from becoming huge).
|
9
|
+
module Command
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Markety
|
2
|
+
module Command
|
3
|
+
|
4
|
+
# GetLead commands return Response::GetLeadResponse objects
|
5
|
+
module GetLead
|
6
|
+
|
7
|
+
# IDs are unique per lead, so the response can only contain one lead.
|
8
|
+
def get_lead_by_idnum(idnum)
|
9
|
+
get_lead(LeadKey.new(LeadKeyType::IDNUM, idnum))
|
10
|
+
end
|
11
|
+
|
12
|
+
# Multiple leads can share an email address,
|
13
|
+
# so this may result in more than one lead in the response.
|
14
|
+
def get_leads_by_email(email)
|
15
|
+
get_lead(LeadKey.new(LeadKeyType::EMAIL, email))
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def get_lead(lead_key)
|
22
|
+
send_request(:get_lead, {"leadKey" => lead_key.to_hash})
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Markety
|
2
|
+
module Command
|
3
|
+
# ListOperation commands return Response::ListOperationResponse objects.
|
4
|
+
#
|
5
|
+
# In all these functions:
|
6
|
+
# * An exception will be thrown if you pass in a Lead and it doesn't have an idnum
|
7
|
+
# * Allowed options:
|
8
|
+
# [strict] From {Marketo's docs}[http://developers.marketo.com/documentation/soap/listoperation/]: <em>Strict mode will fail for the entire operation if any subset of the call fails. Non-strict mode will complete everything it can and return errors for anything that failed.</em>
|
9
|
+
#
|
10
|
+
# All ListOp failures look similar, and all successes look similar.
|
11
|
+
module ListOperation
|
12
|
+
|
13
|
+
# note: If you add something that's already in the list, ListOperationResponse::list_operation_success? will be +true+
|
14
|
+
def add_to_list(list_name, lead_or_idnum, options={})
|
15
|
+
list_operation(list_name, "ADDTOLIST", lead_or_idnum, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
# note: If you remove something that's not in the list, ListOperationResponse::list_operation_success? will be +false+
|
19
|
+
def remove_from_list(list_name, lead_or_idnum, options={})
|
20
|
+
list_operation(list_name, "REMOVEFROMLIST", lead_or_idnum, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# ListOperationResponse::list_operation_success? is the result of this query
|
24
|
+
def is_member_of_list(list_name, lead_or_idnum, options={})
|
25
|
+
list_operation(list_name, "ISMEMBEROFLIST", lead_or_idnum, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
ADD_TO = 'ADDTOLIST' # :nodoc:
|
31
|
+
REMOVE_FROM = 'REMOVEFROMLIST' # :nodoc:
|
32
|
+
IS_MEMBER_OF = 'ISMEMBEROFLIST' # :nodoc:
|
33
|
+
private_constant :ADD_TO
|
34
|
+
private_constant :REMOVE_FROM
|
35
|
+
private_constant :IS_MEMBER_OF
|
36
|
+
|
37
|
+
ALLOWED_OPS = [ADD_TO,REMOVE_FROM,IS_MEMBER_OF] # :nodoc:
|
38
|
+
private_constant :ALLOWED_OPS
|
39
|
+
|
40
|
+
def list_operation(list_name, list_operation_type, lead_or_idnum, options)
|
41
|
+
raise "Unknown list operation type" unless ALLOWED_OPS.include?(list_operation_type)
|
42
|
+
idnum = lead_or_idnum
|
43
|
+
if lead_or_idnum.is_a? Markety::Lead
|
44
|
+
raise "Lead has no idnum, which this command needs" unless lead_or_idnum.idnum
|
45
|
+
idnum = lead_or_idnum.idnum
|
46
|
+
end
|
47
|
+
|
48
|
+
strict = options.has_key?(:strict) ? !!options[:strict] : true
|
49
|
+
|
50
|
+
strict = !!options[:strict]
|
51
|
+
send_request(:list_operation, {
|
52
|
+
list_operation: list_operation_type,
|
53
|
+
strict: strict,
|
54
|
+
list_key: {
|
55
|
+
key_type: 'MKTOLISTNAME',
|
56
|
+
key_value: list_name
|
57
|
+
},
|
58
|
+
list_member_list: {
|
59
|
+
lead_key: [{
|
60
|
+
key_type: 'IDNUM',
|
61
|
+
key_value: idnum
|
62
|
+
}]
|
63
|
+
}
|
64
|
+
})
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Markety
|
2
|
+
module Command
|
3
|
+
# SyncLead commands return Response::SyncResponse objects
|
4
|
+
module SyncLead
|
5
|
+
|
6
|
+
# Create a new lead or update an existing lead in Marketo.
|
7
|
+
# * +lead+ - the lead to create or sync
|
8
|
+
# * +sync_method+ - a SyncMethod enum value
|
9
|
+
def sync_lead(lead, sync_method)
|
10
|
+
request_hash = create_sync_lead_request_hash(lead,sync_method)
|
11
|
+
send_request(:sync_lead, request_hash)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def create_sync_lead_request_hash(lead, sync_method)
|
17
|
+
raise "missing sync method" unless sync_method
|
18
|
+
|
19
|
+
case sync_method
|
20
|
+
when SyncMethod::MARKETO_ID
|
21
|
+
raise "lead has no idnum" unless lead.idnum
|
22
|
+
when SyncMethod::FOREIGN_ID
|
23
|
+
raise "lead has no foreign_sys_person_id" unless lead.foreign_sys_person_id
|
24
|
+
when SyncMethod::EMAIL
|
25
|
+
raise "lead has no email" unless lead.email
|
26
|
+
else
|
27
|
+
raise "unrecognized Markety::SyncMethod '#{sync_method}'"
|
28
|
+
end
|
29
|
+
|
30
|
+
# note from gbirchmeier:
|
31
|
+
# A Marketo support guy told me the fields must come in a very particular order,
|
32
|
+
# thus why this flow is a little janky.
|
33
|
+
# I've since come to doubt this advice (the Marketo support guys do not appear to
|
34
|
+
# actually be very technical), but I'm not going to fix something that's not broke.
|
35
|
+
|
36
|
+
request_hash = {
|
37
|
+
lead_record: { },
|
38
|
+
return_lead: true,
|
39
|
+
}
|
40
|
+
|
41
|
+
# id fields must come first in lead_record
|
42
|
+
request_hash[:lead_record][:id]=lead.idnum if sync_method==SyncMethod::MARKETO_ID
|
43
|
+
use_foreign_id = lead.foreign_sys_person_id && [SyncMethod::MARKETO_ID,SyncMethod::FOREIGN_ID].include?(sync_method)
|
44
|
+
request_hash[:lead_record][:foreignSysPersonId]=lead.foreign_sys_person_id if use_foreign_id
|
45
|
+
request_hash[:lead_record]["Email"]=lead.email if lead.email
|
46
|
+
|
47
|
+
# now lead attributes (which must be ordered name/type/value) (type is optional, but must precede value if present)
|
48
|
+
request_hash[:lead_record][:lead_attribute_list] = { attribute: lead.send(:attributes_soap_array) }
|
49
|
+
|
50
|
+
request_hash
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/markety/enums.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
module Markety
|
2
|
-
# Types of
|
3
|
-
|
4
|
-
ADD_TO = 'ADDTOLIST'
|
5
|
-
REMOVE_FROM = 'REMOVEFROMLIST'
|
6
|
-
IS_MEMBER_OF = 'ISMEMBEROFLIST'
|
7
|
-
end
|
8
|
-
|
9
|
-
# Types of keys that can be used to look up a lead
|
2
|
+
# Types of keys that can be used to look up a lead.
|
3
|
+
# (Other key types exist, but Markety only supports these at this time.)
|
10
4
|
module LeadKeyType
|
11
5
|
IDNUM = "IDNUM"
|
12
|
-
COOKIE = "COOKIE"
|
13
6
|
EMAIL = "EMAIL"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
|
8
|
+
# COOKIE = "COOKIE"
|
9
|
+
# LEADOWNEREMAIL = "LEADOWNEREMAIL"
|
10
|
+
# SFDCACCOUNTID = "SFDCACCOUNTID"
|
11
|
+
# SFDCCONTACTID = "SFDCCONTACTID"
|
12
|
+
# SFDCLEADID = "SFDCLEADID"
|
13
|
+
# SFDCLEADOWNERID = "SFDCLEADOWNERID"
|
14
|
+
# SFDCOPPTYID = "SFDCOPPTYID"
|
15
|
+
end
|
16
|
+
|
17
|
+
# a parameter type to Markety::Command::SyncLead
|
18
|
+
module SyncMethod
|
19
|
+
MARKETO_ID = "MARKETO_ID"
|
20
|
+
FOREIGN_ID = "FOREIGN_ID"
|
21
|
+
EMAIL = "EMAIL"
|
20
22
|
end
|
21
|
-
end
|
23
|
+
end
|
data/lib/markety/lead.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Markety
|
2
|
+
# Represents a record of the data known about a lead within Marketo
|
3
|
+
class Lead
|
4
|
+
attr_reader :types, :idnum, :attributes
|
5
|
+
attr_accessor :foreign_sys_person_id, :email
|
6
|
+
|
7
|
+
def initialize(email:nil, idnum:nil, foreign_sys_person_id:nil)
|
8
|
+
@idnum = idnum
|
9
|
+
@foreign_sys_person_id = foreign_sys_person_id
|
10
|
+
@email = email
|
11
|
+
@attributes = {}
|
12
|
+
@types = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
@attributes==other.send(:attributes) &&
|
17
|
+
@idnum==other.idnum &&
|
18
|
+
@email==other.email &&
|
19
|
+
@foreign_sys_person_id==other.foreign_sys_person_id
|
20
|
+
end
|
21
|
+
|
22
|
+
# hydrates an instance from a savon hash returned from the marketo API
|
23
|
+
def self.from_hash(savon_hash)
|
24
|
+
lead = Lead.new(email: savon_hash[:email], idnum:savon_hash[:id].to_i)
|
25
|
+
|
26
|
+
unless savon_hash[:lead_attribute_list].nil?
|
27
|
+
if savon_hash[:lead_attribute_list][:attribute].kind_of? Hash
|
28
|
+
attributes = [savon_hash[:lead_attribute_list][:attribute]]
|
29
|
+
else
|
30
|
+
attributes = savon_hash[:lead_attribute_list][:attribute]
|
31
|
+
end
|
32
|
+
|
33
|
+
attributes.each do |attribute|
|
34
|
+
lead.set_attribute(attribute[:attr_name], attribute[:attr_value], attribute[:attr_type])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
lead
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# update the value of the named attribute
|
43
|
+
def set_attribute(name, value, type = "string")
|
44
|
+
@attributes[name] = value
|
45
|
+
@types[name] = type
|
46
|
+
end
|
47
|
+
|
48
|
+
# get the value for the named attribute
|
49
|
+
def get_attribute(name)
|
50
|
+
@attributes[name]
|
51
|
+
end
|
52
|
+
|
53
|
+
# get the type of the named attribute
|
54
|
+
def get_attribute_type(name)
|
55
|
+
@types[name]
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
private
|
60
|
+
def attributes_soap_array()
|
61
|
+
arr = []
|
62
|
+
@attributes.each_pair do |name,value|
|
63
|
+
arr << {attr_name: name, attr_type: self.get_attribute_type(name), attr_value: value }
|
64
|
+
end
|
65
|
+
arr
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/markety/lead_key.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
module Markety
|
2
2
|
# Encapsulates a key used to look up or describe a specific marketo lead.
|
3
|
+
# Markety users should not use this class directly.
|
3
4
|
class LeadKey
|
4
|
-
# - *key_type* the type of key to use see LeadKeyType
|
5
|
-
# - *key_value*
|
5
|
+
# - *key_type* - value of LeadKeyType enum; the type of key to use see LeadKeyType
|
6
|
+
# - *key_value* - a string value for the given type
|
6
7
|
def initialize(key_type, key_value)
|
7
8
|
@key_type = key_type
|
8
9
|
@key_value = key_value
|
9
10
|
end
|
10
11
|
|
11
|
-
# get the key type
|
12
|
+
# get the key type (a LeadKeyType enum value)
|
12
13
|
def key_type
|
13
14
|
@key_type
|
14
15
|
end
|
@@ -26,4 +27,4 @@ module Markety
|
|
26
27
|
}
|
27
28
|
end
|
28
29
|
end
|
29
|
-
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'markety/response/response_factory'
|
2
|
+
|
3
|
+
require 'markety/response/generic_response'
|
4
|
+
require 'markety/response/get_lead_response'
|
5
|
+
require 'markety/response/sync_lead_response'
|
6
|
+
require 'markety/response/list_operation_response'
|
7
|
+
|
8
|
+
|
9
|
+
module Markety
|
10
|
+
# Each Command returns a corresponding Response.
|
11
|
+
# All Response classes are derived from GenericResponse,
|
12
|
+
# which contains some common accessor methods.
|
13
|
+
module Response
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Markety
|
2
|
+
module Response
|
3
|
+
|
4
|
+
# Parent class for all response types.
|
5
|
+
#
|
6
|
+
# SOAP requests sent by Markety result in either
|
7
|
+
# a <tt>Savon::Response</tt> or a <tt>Savon::SOAPFault</tt>.
|
8
|
+
# This class hides those boring details from you,
|
9
|
+
# unless you want to use its methods to see them.
|
10
|
+
class GenericResponse
|
11
|
+
#if the response is a <tt>Savon::SOAPFault</tt>, this is its error message
|
12
|
+
attr_reader :error_message
|
13
|
+
|
14
|
+
# * +cmd_type+ - a symbol
|
15
|
+
# * +response+ - a <tt>Savon::Response</tt> or a <tt>Savon::SOAPFault</tt>
|
16
|
+
def initialize(cmd_type,response)
|
17
|
+
@response = response
|
18
|
+
@success = response.is_a? Savon::Response
|
19
|
+
@error_message = @success ? nil : response.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
# True if Marketo's response indicates that the SOAP request
|
23
|
+
# was successful.
|
24
|
+
#
|
25
|
+
# *Note:* This is not the same as the a result from
|
26
|
+
# a Marketo command itself! To see the command's result,
|
27
|
+
# consult the command-specific Response class.
|
28
|
+
def success?
|
29
|
+
@success
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return the xml from the underlying <tt>Savon::Response</tt> or
|
33
|
+
# <tt>Savon::SOAPFault</tt>
|
34
|
+
def to_xml
|
35
|
+
@success ? @response.to_xml : @response.http.raw_body
|
36
|
+
end
|
37
|
+
|
38
|
+
# The underlying <tt>Savon::Response</tt> or
|
39
|
+
# <tt>Savon::SOAPFault</tt>'s content as a hash
|
40
|
+
def to_hash
|
41
|
+
@response.to_hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'markety/lead'
|
2
|
+
require 'markety/response/generic_response'
|
3
|
+
|
4
|
+
module Markety
|
5
|
+
module Response
|
6
|
+
# Response class for Command::GetLead calls.
|
7
|
+
class GetLeadResponse < GenericResponse
|
8
|
+
# Array of leads returned by the GetLead command
|
9
|
+
attr_reader :leads
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
super(:get_lead_response,response)
|
13
|
+
h = self.to_hash
|
14
|
+
@leads = []
|
15
|
+
|
16
|
+
if self.success?
|
17
|
+
count = h[:success_get_lead][:result][:count].to_i
|
18
|
+
lead_hashes = h[:success_get_lead][:result][:lead_record_list][:lead_record]
|
19
|
+
lead_hashes = [lead_hashes] if count==1
|
20
|
+
lead_hashes.each {|leadhash| @leads << ::Markety::Lead.from_hash(leadhash) }
|
21
|
+
else
|
22
|
+
# overwrite super's crap error message with useful one
|
23
|
+
@error_message = h[:fault][:detail][:service_exception][:message]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Convenience shortcut to get first element of #leads (or nil if none).
|
28
|
+
# Appropriate for responses to Command::GetLead#get_lead_by_idnum, which cannot
|
29
|
+
# result in more than one lead.
|
30
|
+
def lead
|
31
|
+
@leads.first
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'markety/response/generic_response'
|
2
|
+
|
3
|
+
module Markety
|
4
|
+
module Response
|
5
|
+
# Response class for Command::ListOperation calls
|
6
|
+
class ListOperationResponse < GenericResponse
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
super(:list_operation_response,response)
|
10
|
+
@list_operation_success = false
|
11
|
+
|
12
|
+
if self.success?
|
13
|
+
h = self.to_hash
|
14
|
+
@list_operation_success = h[:success_list_operation][:result][:success]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Whether the operation was successful.
|
19
|
+
#
|
20
|
+
# *Note:* this is not the same as parent's success? method.
|
21
|
+
# For list operations, success? almost always true
|
22
|
+
# (because Marketo accepted the request and gave you a response).
|
23
|
+
def list_operation_success?
|
24
|
+
@list_operation_success
|
25
|
+
end
|
26
|
+
alias list_op_success? list_operation_success?
|
27
|
+
alias lop_success? list_operation_success?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'markety/response/generic_response'
|
2
|
+
require 'markety/response/get_lead_response'
|
3
|
+
require 'markety/response/sync_lead_response'
|
4
|
+
require 'markety/response/list_operation_response'
|
5
|
+
|
6
|
+
module Markety
|
7
|
+
module Response
|
8
|
+
# Factory that creates the appropriate Response object depending on the command type
|
9
|
+
class ResponseFactory
|
10
|
+
|
11
|
+
# Create the appropriate Response object depending on the command type
|
12
|
+
def self.create_response(cmd_type,savon_response)
|
13
|
+
case cmd_type
|
14
|
+
when :get_lead
|
15
|
+
GetLeadResponse.new(savon_response)
|
16
|
+
when :sync_lead
|
17
|
+
SyncLeadResponse.new(savon_response)
|
18
|
+
when :list_operation
|
19
|
+
ListOperationResponse.new(savon_response)
|
20
|
+
else
|
21
|
+
GenericResponse.new(cmd_type,savon_response)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|