marketo-api-ruby 0.8

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.
@@ -0,0 +1,150 @@
1
+ require_relative 'client_proxy'
2
+ require_relative 'lead'
3
+
4
+ # Implements Lead operations for Marketo.
5
+ class MarketoAPI::Leads < MarketoAPI::ClientProxy
6
+ # Implements
7
+ # {+getLead+}[http://developers.marketo.com/documentation/soap/getlead/],
8
+ # returning a MarketoAPI::Lead object.
9
+ #
10
+ # :call-seq:
11
+ # get(lead_key)
12
+ # get(key_type, key_value)
13
+ def get(type_or_key, value = nil)
14
+ key = case type_or_key
15
+ when Hash
16
+ if lk = type_or_key[:lead_key]
17
+ if MarketoAPI::Lead.send(:key_type, lk[:key_type])
18
+ type_or_key
19
+ end
20
+ end
21
+ when MarketoAPI::Lead
22
+ transform_param(__method__, type_or_key)
23
+ else
24
+ MarketoAPI::Lead.key(type_or_key, value)
25
+ end
26
+
27
+ unless key
28
+ raise ArgumentError, ':type_or_key is not a valid lead key'
29
+ end
30
+ extract_from_response(call(:get_lead, key), :lead_record_list) { |record|
31
+ MarketoAPI::Lead.from_soap_hash(record[:lead_record]) do |lead|
32
+ lead.proxy = self
33
+ end
34
+ }
35
+ end
36
+
37
+ # Implements
38
+ # {+syncLead+}[http://developers.marketo.com/documentation/soap/synclead/],
39
+ # returning a MarketoAPI::Lead object.
40
+ def sync(lead_record)
41
+ extract_from_response(
42
+ call(:sync_lead, transform_param(__method__, lead_record)),
43
+ ) { |record|
44
+ MarketoAPI::Lead.from_soap_hash(record[:lead_record]) do |lead|
45
+ lead.proxy = self
46
+ end
47
+ }
48
+ end
49
+
50
+ def get_multiple(selector) #:nodoc:
51
+ raise NotImplementedError
52
+ end
53
+
54
+ # Implements
55
+ # {+syncMultipleLeads+}[http://developers.marketo.com/documentation/soap/syncmultipleleads/],
56
+ # returning an array of MarketoAPI::Lead objects.
57
+ #
58
+ # May optionally disable de-duplication by passing <tt>dedup_enabled:
59
+ # false</tt>.
60
+ #
61
+ # :call-seq:
62
+ # sync_multiple(leads)
63
+ # sync_multiple(leads, dedup_enabled: false)
64
+ def sync_multiple(leads, options = { dedup_enabled: true })
65
+ response = call(
66
+ :sync_multiple_leads,
67
+ dedup_enabled: options[:dedup_enabled],
68
+ lead_record_list: transform_param_list(:sync, leads)
69
+ )
70
+ extract_from_response(response, :lead_record_list) do |list|
71
+ list.each do |record|
72
+ MarketoAPI::Lead.from_soap_hash(record[:lead_record]) do |lead|
73
+ lead.proxy = self
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def merge(winning_key, losing_keys) #:nodoc:
80
+ raise NotImplementedError
81
+ end
82
+
83
+ def activity(lead_key, options = {}) #:nodoc:
84
+ raise NotImplementedError
85
+ end
86
+
87
+ def changes(start_position, options = {}) #:nodoc:
88
+ raise NotImplementedError
89
+ end
90
+
91
+ ##
92
+ # :method: get_by_id
93
+ # :call-seq: get_by_id(marketo_id)
94
+ #
95
+ # Gets the Lead by the provided Marketo ID.
96
+
97
+ ##
98
+ # :method: get_by_cookie
99
+ # :call-seq: get_by_cookie(cookie)
100
+ #
101
+ # Gets the Lead by the provided Marketo Munchkin cookie.
102
+
103
+ ##
104
+ # :method: get_by_email
105
+ # :call-seq: get_by_email(email)
106
+ #
107
+ # Gets the Lead by the provided lead email.
108
+
109
+ ##
110
+ # :method: get_by_lead_owner_email
111
+ # :call-seq: get_by_lead_owner_email(lead_owner_email)
112
+ #
113
+ # Gets the Lead by the provided Lead Owner email.
114
+
115
+ ##
116
+ # :method: get_by_salesforce_account_id
117
+ # :call-seq: get_by_salesforce_account_id(salesforce_account_id)
118
+ #
119
+ # Gets the Lead by the provided SFDC Account ID.
120
+
121
+ ##
122
+ # :method: get_by_salesforce_contact_id
123
+ # :call-seq: get_by_salesforce_contact_id(salesforce_contact_id)
124
+ #
125
+ # Gets the Lead by the provided SFDC Contact ID.
126
+
127
+ ##
128
+ # :method: get_by_salesforce_lead_id
129
+ # :call-seq: get_by_salesforce_lead_id(salesforce_lead_id)
130
+ #
131
+ # Gets the Lead by the provided SFDC Lead ID.
132
+
133
+ ##
134
+ # :method: get_by_salesforce_lead_owner_id
135
+ # :call-seq: get_by_salesforce_lead_owner_id(salesforce_lead_owner_id)
136
+ #
137
+ # Gets the Lead by the provided SFDC Lead Owner ID.
138
+
139
+ ##
140
+ # :method: get_by_salesforce_opportunity_id
141
+ # :call-seq: get_by_salesforce_opportunity_id(salesforce_opportunity_id)
142
+ #
143
+ # Gets the Lead by the provided SFDC Opportunity ID.
144
+
145
+ MarketoAPI::Lead::NAMED_KEYS.each_pair { |name, key|
146
+ define_method(:"get_by_#{name}") do |value|
147
+ get(key, value)
148
+ end
149
+ }
150
+ end
@@ -0,0 +1,109 @@
1
+ require_relative 'client_proxy'
2
+
3
+ # Marketo list operations.
4
+ class MarketoAPI::Lists < MarketoAPI::ClientProxy
5
+ NAMED_TYPES = { #:nodoc:
6
+ name: :MKTOLISTNAME,
7
+ sales_user_id: :MKTOSALESUSERID,
8
+ salesforce_lead_owner_id: :SFDCLEADOWNERID
9
+ }.freeze
10
+
11
+ TYPES = NAMED_TYPES.values.freeze
12
+
13
+ ##
14
+ # :method: add
15
+ # :call-seq:
16
+ # add(list_key, options)
17
+ #
18
+ # === Options
19
+ #
20
+ # +leads+:: Required. An array of Lead objects or lead keys. If both
21
+ # +leads+ and +lead+ are provided, they will be merged.
22
+ # +lead+:: An alias for +leads+.
23
+ # +strict+:: If +true+, the entire operation fails if any subset fails.
24
+ # Non-strict mode will complete everything it can and return
25
+ # errors for anything that failed.
26
+ #
27
+ # Add leads to a Marketo list.
28
+
29
+ ##
30
+ # :method: remove
31
+ # :call-seq:
32
+ # remove(list_key, options)
33
+ #
34
+ # === Options
35
+ #
36
+ # +leads+:: Required. An array of Lead objects or lead keys. If both
37
+ # +leads+ and +lead+ are provided, they will be merged.
38
+ # +lead+:: An alias for +leads+.
39
+ # +strict+:: If +true+, the entire operation fails if any subset fails.
40
+ # Non-strict mode will complete everything it can and return
41
+ # errors for anything that failed.
42
+ #
43
+ # Add leads to a Marketo list.
44
+
45
+ ##
46
+ # :method: member?
47
+ # :call-seq:
48
+ # member?(list_key, options)
49
+ #
50
+ # === Options
51
+ #
52
+ # +leads+:: Required. An array of Lead objects or lead keys. If both
53
+ # +leads+ and +lead+ are provided, they will be merged.
54
+ # +lead+:: An alias for +leads+.
55
+ # +strict+:: If +true+, the entire operation fails if any subset fails.
56
+ # Non-strict mode will complete everything it can and return
57
+ # errors for anything that failed.
58
+ #
59
+ # Add leads to a Marketo list.
60
+
61
+ {
62
+ add: :ADDTOLIST,
63
+ remove: :REMOVEFROMLIST,
64
+ member?: :ISMEMBEROFLIST,
65
+ }.each do |name, operation|
66
+ define_method(name) do |list_key, options = {}|
67
+ list_operation(operation, list_key, options)
68
+ end
69
+ end
70
+
71
+ class << self
72
+ def key(type, value)
73
+ {
74
+ list_key: {
75
+ key_type: key_type(type),
76
+ key_value: value
77
+ }
78
+ }
79
+ end
80
+
81
+ private
82
+ def key_type(key)
83
+ res = if TYPES.include? key
84
+ key
85
+ else
86
+ NAMED_TYPES[key]
87
+ end
88
+ raise ArgumentError, "Invalid key #{key}" unless res
89
+ res
90
+ end
91
+ end
92
+
93
+ private
94
+ def list_operation(operation, list_key, options = {})
95
+ leads = MarketoAPI.array(options.delete(:leads)) +
96
+ MarketoAPI.array(options.delete(:lead))
97
+ if leads.empty?
98
+ raise ArgumentError, ':lead or :leads must be provided'
99
+ end
100
+
101
+ call(
102
+ :list_operation,
103
+ list_operation: operation,
104
+ list_key: list_key,
105
+ strict: false,
106
+ list_member_list: transform_param_list(:get, leads)
107
+ )
108
+ end
109
+ end
@@ -0,0 +1,272 @@
1
+ # A representation of Marketo object (MObject) records as well as key
2
+ # representations for getting, syncing, or deleting those records.
3
+ class MarketoAPI::MObject
4
+ DELETE_TYPES = #:nodoc:
5
+ MarketoAPI.freeze(:Opportunity, :OpportunityPersonRole)
6
+ GET_TYPES = #:nodoc:
7
+ MarketoAPI.freeze(*DELETE_TYPES, :Program)
8
+ DESCRIBE_TYPES = #:nodoc:
9
+ MarketoAPI.freeze(*DELETE_TYPES, :ActivityRecord, :LeadRecord )
10
+ ALL_TYPES = #:nodoc:
11
+ MarketoAPI.freeze(*[ GET_TYPES, DESCRIBE_TYPES ].flatten.uniq)
12
+
13
+ # The type of Marketo object. Will be one of:
14
+ #
15
+ # - Opportunity
16
+ # - OpportunityPersonRole
17
+ # - Program
18
+ # - ActivityRecord
19
+ # - LeadRecord
20
+ #
21
+ # In general, only the first three can be interacted with through the SOAP
22
+ # API.
23
+ attr_reader :type
24
+ # The ID of the Marketo object.
25
+ attr_accessor :id
26
+
27
+ # Associated objects.
28
+ attr_reader :associations
29
+ # The stream position for paged queries.
30
+ attr_accessor :stream_position
31
+
32
+ ##
33
+ # :attr_accessor: include_details
34
+ # When getting a Marketo Program, the details will be included if this is
35
+ # +true+.
36
+
37
+ # The detailed attributes of the Marketo object.
38
+ attr_reader :attributes
39
+ # The detailed types of the Marketo object.
40
+ attr_reader :types
41
+
42
+ def initialize(type, id = nil)
43
+ @type = ensure_valid_type!(type)
44
+ @id = id
45
+ @attributes = {}
46
+ @criteria = []
47
+ @associations = []
48
+ @stream_position = nil
49
+ @include_details = false
50
+ @types = Hash.new { |h, k| h[k] = {} }
51
+ yield self if block_given?
52
+ end
53
+
54
+ def include_details
55
+ @include_details
56
+ end
57
+
58
+ def include_details=(value) #:nodoc:
59
+ @include_details= !!value
60
+ end
61
+
62
+ # Adds query criteria for use with MarketoAPI::MObjects#get.
63
+ #
64
+ # === Name
65
+ #
66
+ # Name:: Name of the MObject
67
+ # Role:: The role associated with an
68
+ # OpportunityPersonRole object
69
+ # Type:: The type of an Opportunity object
70
+ # Stage:: The stage of an Opportunity object
71
+ # CRM Id: The CRM ID could refer to the ID of the
72
+ # Salesforce campaign connected to a Marketo
73
+ # program
74
+ # Created At:: The date the MObject was created. Can be used
75
+ # with the comparisons EQ, NE, LT, LE, GT, and
76
+ # GE. Two “created dates” can be specified to
77
+ # create a date range.
78
+ # Updated At or Tag Type:: (Only one can be specified) Can be used with
79
+ # the comparisons EQ, NE, LT, LE, GT, and GE. Two
80
+ # “updated dates” can be specified to create a
81
+ # date range.
82
+ # Tag Value: (Only one can be specified)
83
+ # Workspace Name: (Only one can be specified)
84
+ # Workspace Id: (Only one can be specified)
85
+ # Include Archive: Applicable only with Program MObject. Set it to
86
+ # true if you wish to include archived programs.
87
+ #
88
+ # === Comparison
89
+ #
90
+ # EQ:: Equals
91
+ # NE:: Not Equals
92
+ # LT:: Less Than
93
+ # LE:: Less Than or Equals
94
+ # GT:: Greater Than
95
+ # GE:: Greater Than or Equals
96
+ def criteria(name = nil, value = nil, comparison = nil)
97
+ @criteria << Criteria.new(name, value, comparison) if name
98
+ @criteria
99
+ end
100
+
101
+ # Add association criteria for use with MarketoAPI::MObjects#get or
102
+ # MarketoAPI::MOBjects#sync (not yet implemented).
103
+ #
104
+ # Type +type+ must be one of +Lead+, +Company+, or +Opportunity+. It must
105
+ # be accompanied with one of the following parameters:
106
+ #
107
+ # id:: The Marketo ID of the associated object.
108
+ # external:: The custom attribute value of the associated object. Can
109
+ # also be accessed as +external_key+.
110
+ def association(type, options = {})
111
+ @associations << Association.new(type, options)
112
+ @associations
113
+ end
114
+
115
+ def params_for_delete #:nodoc:
116
+ ensure_valid_type!(type, DELETE_TYPES)
117
+ raise ArgumentError, ":id cannot be nil" if id.nil?
118
+ { type: type, id: id }
119
+ end
120
+
121
+ def params_for_get #:nodoc:
122
+ ensure_valid_type!(type, GET_TYPES)
123
+ {
124
+ type: type,
125
+ id: id,
126
+ include_details: include_details,
127
+ m_obj_criteria_list: criteria.compact.uniq.map(&:to_h),
128
+ m_obj_association_list: associations.compact.uniq.map(&:to_h),
129
+ stream_position: stream_position
130
+ }.delete_if(&MarketoAPI::MINIMIZE_HASH)
131
+ end
132
+
133
+ def ==(other)
134
+ type == other.type &&
135
+ include_details == other.include_details &&
136
+ id == other.id &&
137
+ stream_position == other.stream_position &&
138
+ attributes == other.attributes &&
139
+ types == other.types &&
140
+ criteria == other.criteria &&
141
+ associations == other.associations
142
+ end
143
+
144
+ class << self
145
+ # Creates a new MObject from a SOAP response hash (from MObjects#get or
146
+ # MObjects#sync).
147
+ def from_soap_hash(hash) #:nodoc:
148
+ new(hash[:type], hash[:id]) do |mobj|
149
+ obj = hash[:attrib_list][:attrib]
150
+ MarketoAPI.array(obj).each do |attrib|
151
+ mobj.attributes[attrib[:name].to_sym] = attrib[:value]
152
+ end
153
+
154
+ obj = hash[:type_attrib_list][:type_attrib]
155
+ MarketoAPI.array(obj).each do |type|
156
+ MarketoAPI.array(type[:attr_list][:attrib]).each do |attrib|
157
+ mobj.types[type[:attr_type].to_sym][attrib[:name].to_sym] =
158
+ attrib[:value]
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ ALL_TYPES.each do |type|
165
+ define_method(type.downcase) do |id = nil, &block|
166
+ new(type, id, &block)
167
+ end
168
+ end
169
+ end
170
+
171
+ class Criteria #:nodoc:
172
+ TYPES = { #:nodoc:
173
+ name: "Name",
174
+ role: "Role",
175
+ type: "Type",
176
+ stage: "Stage",
177
+ crm_id: "CRM Id",
178
+ created_at: "Created At",
179
+ updated_at: "Updated At",
180
+ tag_type: "Tag Type",
181
+ tag_value: "Tag Value",
182
+ workspace_name: "Workspace Name",
183
+ workspace_id: "Workspace Id",
184
+ include_archive: "Include Archive"
185
+ }.freeze
186
+ TYPES.values.map(&:freeze)
187
+
188
+ CMP = [ #:nodoc:
189
+ :EQ, :NE, :LT, :LE, :GT, :GE
190
+ ]
191
+
192
+ attr_reader :name, :value, :comparison
193
+
194
+ def initialize(name, value, comparison)
195
+ name = if TYPES.has_key?(name.to_sym)
196
+ TYPES[name.to_sym]
197
+ elsif TYPES.values.include?(name)
198
+ TYPES.values[TYPES.values.index(name)]
199
+ else
200
+ raise ArgumentError, "Invalid type name [#{name}]"
201
+ end
202
+
203
+ unless CMP.include?(comparison.to_s.to_sym.upcase)
204
+ raise ArgumentError, "Invalid comparison [#{comparison}]"
205
+ end
206
+
207
+ @name, @value, @comparison = name, value, comparison.to_sym.upcase
208
+ end
209
+
210
+ def ==(other)
211
+ name.equal?(other.name) && comparison.equal?(other.comparison) &&
212
+ value == other.value?
213
+ end
214
+
215
+ def to_h
216
+ {
217
+ attr_name: name,
218
+ attr_value: value,
219
+ comparison: comparison
220
+ }
221
+ end
222
+ end
223
+
224
+ class Association #:nodoc:
225
+ TYPES = [ #:nodoc:
226
+ :Lead, :Company, :Opportunity
227
+ ]
228
+
229
+ attr_reader :type, :id, :external_key
230
+ alias_method :external, :external_key
231
+
232
+ def initialize(type, options = {})
233
+ unless TYPES.include?(type.to_s.capitalize.to_sym)
234
+ raise ArgumentError, "Invalid type #{type}"
235
+ end
236
+
237
+ @type = TYPES[TYPES.index(type.to_s.capitalize.to_sym)]
238
+
239
+ options.fetch(:id) {
240
+ options.fetch(:external) {
241
+ options.fetch(:external_key) {
242
+ raise KeyError, "Must have one of :id or :external"
243
+ }
244
+ }
245
+ }
246
+
247
+ @id = options[:id]
248
+ @external_key = options[:external] || options[:external_key]
249
+ end
250
+
251
+ def ==(other)
252
+ type.equal?(other.type) && id == other.id &&
253
+ external_key == other.external_key
254
+ end
255
+
256
+ def to_h
257
+ {
258
+ m_obj_type: type,
259
+ id: id,
260
+ external_key: external_key,
261
+ }
262
+ end
263
+ end
264
+
265
+ private
266
+ def ensure_valid_type!(type, list = ALL_TYPES)
267
+ unless list.include? type.to_sym
268
+ raise ArgumentError, ":type must be one of #{list.join(", ")}"
269
+ end
270
+ type.to_sym
271
+ end
272
+ end