markety 1.4.3 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +3 -0
  3. data/README.md +66 -15
  4. data/Rakefile +1 -1
  5. data/example_xml/get_lead_failure.xml +42 -0
  6. data/example_xml/get_lead_success.xml +63 -0
  7. data/example_xml/get_lead_success_two_leads.xml +88 -0
  8. data/example_xml/list_op_add_to_list_success.xml +42 -0
  9. data/example_xml/list_op_is_member_of_list_failure.xml +50 -0
  10. data/example_xml/list_op_is_member_of_list_success.xml +51 -0
  11. data/example_xml/list_op_remove_from_list_failure.xml +50 -0
  12. data/example_xml/list_op_remove_from_list_success.xml +42 -0
  13. data/example_xml/sync_lead_failure.xml +60 -0
  14. data/example_xml/sync_lead_success.xml +78 -0
  15. data/lib/markety.rb +26 -1
  16. data/lib/markety/authentication_header.rb +3 -3
  17. data/lib/markety/client.rb +35 -141
  18. data/lib/markety/command.rb +11 -0
  19. data/lib/markety/command/get_lead.rb +27 -0
  20. data/lib/markety/command/list_operation.rb +68 -0
  21. data/lib/markety/command/sync_lead.rb +55 -0
  22. data/lib/markety/enums.rb +18 -16
  23. data/lib/markety/lead.rb +69 -0
  24. data/lib/markety/lead_key.rb +5 -4
  25. data/lib/markety/response.rb +15 -0
  26. data/lib/markety/response/generic_response.rb +45 -0
  27. data/lib/markety/response/get_lead_response.rb +35 -0
  28. data/lib/markety/response/list_operation_response.rb +30 -0
  29. data/lib/markety/response/response_factory.rb +27 -0
  30. data/lib/markety/response/sync_lead_response.rb +32 -0
  31. data/lib/markety/version.rb +1 -3
  32. data/spec/markety/lead_key_spec.rb +10 -11
  33. data/spec/markety/{lead_record_spec.rb → lead_spec.rb} +13 -30
  34. data/spec/markety/response/get_lead_response_spec.rb +170 -0
  35. data/spec/markety/response/list_operation_response_spec.rb +76 -0
  36. data/spec/markety/response/sync_lead_response_spec.rb +107 -0
  37. data/spec/spec_helper.rb +4 -0
  38. data/spec/support/savon_helper.rb +14 -0
  39. metadata +61 -33
  40. 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 operations you can do on a marketo list
3
- module ListOperationType
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
- LEADOWNEREMAIL = "LEADOWNEREMAIL"
15
- SFDCACCOUNTID = "SFDCACCOUNTID"
16
- SFDCCONTACTID = "SFDCCONTACTID"
17
- SFDCLEADID = "SFDCLEADID"
18
- SFDCLEADOWNERID = "SFDCLEADOWNERID"
19
- SFDCOPPTYID = "SFDCOPPTYID"
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
@@ -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
@@ -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* normally a string value for the given type
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