markety 1.4.3 → 2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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