marketo-api-ruby 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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