activesalesforce 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/asf_adapter.rb +189 -187
- data/test/unit/profiler_results.txt +1072 -0
- data/test/unit/profiler_results_live.txt +1347 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_add_notes_to_contact.recording +558 -558
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_count_contacts.recording +555 -555
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_create_a_contact.recording +463 -463
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact.recording +460 -460
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_first_name.recording +473 -473
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_id.recording +433 -433
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_get_created_by_from_contact.recording +2291 -2291
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_master_detail.recording +735 -735
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_read_all_content_columns.recording +463 -463
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_save_a_contact.recording +444 -444
- data/test/unit/recorded_test_case.rb +2 -0
- metadata +4 -2
data/lib/asf_adapter.rb
CHANGED
@@ -25,6 +25,7 @@ require 'rubygems'
|
|
25
25
|
require_gem 'rails', ">= 1.0.0"
|
26
26
|
|
27
27
|
require 'thread'
|
28
|
+
require 'benchmark'
|
28
29
|
|
29
30
|
require File.dirname(__FILE__) + '/rforce'
|
30
31
|
require File.dirname(__FILE__) + '/column_definition'
|
@@ -44,30 +45,29 @@ module ActiveRecord
|
|
44
45
|
|
45
46
|
# Establishes a connection to the database that's used by all Active Record objects.
|
46
47
|
def self.activesalesforce_connection(config) # :nodoc:
|
47
|
-
|
48
|
+
logger.debug("\nUsing ActiveSalesforce connection\n")
|
48
49
|
|
49
50
|
url = config[:url]
|
50
51
|
sid = config[:sid]
|
51
52
|
binding = config[:binding] if config[:binding]
|
52
53
|
|
53
54
|
if binding
|
54
|
-
|
55
|
-
#pp binding
|
55
|
+
logger.debug(" via provided binding #{binding}\n")
|
56
56
|
end
|
57
57
|
|
58
58
|
if sid
|
59
59
|
binding = @@cache["sid=#{sid}"] unless binding
|
60
60
|
|
61
61
|
unless binding
|
62
|
-
|
62
|
+
logger.debug("Establishing new connection for [sid='#{sid}']")
|
63
63
|
|
64
64
|
binding = RForce::Binding.new(url, sid)
|
65
65
|
@@cache["sid=#{sid}"] = binding
|
66
66
|
|
67
|
-
|
67
|
+
logger.debug("Created new connection for [sid='#{sid}']")
|
68
68
|
end
|
69
69
|
|
70
|
-
ConnectionAdapters::SalesforceAdapter.new(
|
70
|
+
ConnectionAdapters::SalesforceAdapter.new(binding, logger, [url, sid], config)
|
71
71
|
else
|
72
72
|
# Default to production system using 7.0 API
|
73
73
|
url = "https://www.salesforce.com/services/Soap/u/7.0" unless url
|
@@ -81,14 +81,16 @@ module ActiveRecord
|
|
81
81
|
binding = @@cache["#{url}.#{username}.#{password}"] unless binding
|
82
82
|
|
83
83
|
unless binding
|
84
|
-
|
84
|
+
logger.debug("Establishing new connection for ['#{url}', '#{username}']")
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
86
|
+
seconds = Benchmark.realtime {
|
87
|
+
binding = RForce::Binding.new(url, sid)
|
88
|
+
binding.login(username, password).result
|
89
|
+
|
90
|
+
@@cache["#{url}.#{username}.#{password}"] = binding
|
91
|
+
}
|
90
92
|
|
91
|
-
|
93
|
+
logger.debug("Created new connection for ['#{url}', '#{username}'] in #{seconds} seconds")
|
92
94
|
end
|
93
95
|
|
94
96
|
ConnectionAdapters::SalesforceAdapter.new(binding, logger, [url, username, password, sid], config)
|
@@ -106,7 +108,7 @@ module ActiveRecord
|
|
106
108
|
|
107
109
|
@fault = fault
|
108
110
|
|
109
|
-
logger.debug("\nSalesforceError:\n message='#{message}'\n fault='#{fault}'\n\n")
|
111
|
+
logger.debug("\nSalesforceError:\n message='#{message}'\n fault='#{fault}'\n\n")
|
110
112
|
end
|
111
113
|
end
|
112
114
|
|
@@ -190,166 +192,162 @@ module ActiveRecord
|
|
190
192
|
# DATABASE STATEMENTS ======================================
|
191
193
|
|
192
194
|
def select_all(sql, name = nil) #:nodoc:
|
193
|
-
log(sql, name)
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
raw_table_name = sql.match(/FROM (\w+)/i)[1]
|
202
|
-
table_name = raw_table_name.singularize
|
203
|
-
entity_name = entity_name_from_table(table_name)
|
204
|
-
entity_def = get_entity_def(entity_name)
|
205
|
-
|
206
|
-
column_names = api_column_names(table_name)
|
207
|
-
|
208
|
-
# Always (unless COUNT*)'ing) select all columns (required for the AR attributes mechanism to work correctly
|
209
|
-
soql = sql.sub(/SELECT .+ FROM/i, "SELECT #{column_names.join(', ')} FROM") unless selectCountMatch
|
210
|
-
|
211
|
-
soql.sub!(/ FROM \w+/i, " FROM #{entity_def.api_name}")
|
212
|
-
|
213
|
-
# Look for a LIMIT clause
|
214
|
-
soql.sub!(/LIMIT 1/i, "")
|
215
|
-
|
216
|
-
# Look for an OFFSET clause
|
217
|
-
soql.sub!(/\d+ OFFSET \d+/i, "")
|
218
|
-
|
219
|
-
# Fixup column references to use api names
|
220
|
-
columns = columns_map(table_name)
|
221
|
-
while soql =~ /\w+\.(\w+)/i
|
222
|
-
column_name = $~[1]
|
195
|
+
log(sql, name) {
|
196
|
+
# Check for SELECT COUNT(*) FROM query
|
197
|
+
selectCountMatch = sql.match(/SELECT COUNT\(\*\) FROM/i)
|
198
|
+
if selectCountMatch
|
199
|
+
soql = "SELECT id FROM#{selectCountMatch.post_match}"
|
200
|
+
end
|
223
201
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
# Update table name references
|
229
|
-
soql.sub!(/#{raw_table_name}\./i, "#{entity_def.api_name}.")
|
230
|
-
|
231
|
-
log(soql, name)
|
232
|
-
|
233
|
-
@connection.batch_size = @batch_size if @batch_size
|
234
|
-
@batch_size = nil
|
235
|
-
|
236
|
-
queryResult = get_result(@connection.query(:queryString => soql), :query)
|
237
|
-
records = queryResult[:records]
|
238
|
-
|
239
|
-
result = ResultArray.new(queryResult[:size].to_i)
|
240
|
-
return result unless records
|
241
|
-
|
242
|
-
records = [ records ] unless records.is_a?(Array)
|
243
|
-
|
244
|
-
records.each do |record|
|
245
|
-
row = {}
|
202
|
+
raw_table_name = sql.match(/FROM (\w+)/i)[1]
|
203
|
+
table_name = raw_table_name.singularize
|
204
|
+
entity_name = entity_name_from_table(table_name)
|
205
|
+
entity_def = get_entity_def(entity_name)
|
246
206
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
207
|
+
column_names = api_column_names(table_name)
|
208
|
+
|
209
|
+
# Always (unless COUNT*)'ing) select all columns (required for the AR attributes mechanism to work correctly
|
210
|
+
soql = sql.sub(/SELECT .+ FROM/i, "SELECT #{column_names.join(', ')} FROM") unless selectCountMatch
|
211
|
+
|
212
|
+
soql.sub!(/ FROM \w+/i, " FROM #{entity_def.api_name}")
|
213
|
+
|
214
|
+
# Look for a LIMIT clause
|
215
|
+
soql.sub!(/LIMIT 1/i, "")
|
216
|
+
|
217
|
+
# Look for an OFFSET clause
|
218
|
+
soql.sub!(/\d+ OFFSET \d+/i, "")
|
219
|
+
|
220
|
+
# Fixup column references to use api names
|
221
|
+
columns = columns_map(table_name)
|
222
|
+
while soql =~ /\w+\.(\w+)/i
|
223
|
+
column_name = $~[1]
|
224
|
+
|
225
|
+
column = columns[column_name]
|
226
|
+
soql = $~.pre_match + column.api_name + $~.post_match
|
227
|
+
end
|
228
|
+
|
229
|
+
# Update table name references
|
230
|
+
soql.sub!(/#{raw_table_name}\./i, "#{entity_def.api_name}.")
|
231
|
+
|
232
|
+
@connection.batch_size = @batch_size if @batch_size
|
233
|
+
@batch_size = nil
|
234
|
+
|
235
|
+
queryResult = get_result(@connection.query(:queryString => soql), :query)
|
236
|
+
records = queryResult[:records]
|
237
|
+
|
238
|
+
result = ResultArray.new(queryResult[:size].to_i)
|
239
|
+
return result unless records
|
240
|
+
|
241
|
+
records = [ records ] unless records.is_a?(Array)
|
242
|
+
|
243
|
+
records.each do |record|
|
244
|
+
row = {}
|
245
|
+
|
246
|
+
record.each do |name, value|
|
247
|
+
if name != :type
|
248
|
+
# Ids may be returned in an array with 2 duplicate entries...
|
249
|
+
value = value[0] if name == :Id && value.is_a?(Array)
|
250
|
+
|
251
|
+
column = entity_def.api_name_to_column[name.to_s]
|
252
|
+
attribute_name = column.name
|
253
|
+
|
254
|
+
if column.type == :boolean
|
255
|
+
row[attribute_name] = (value.casecmp("true") == 0)
|
256
|
+
else
|
257
|
+
row[attribute_name] = value
|
258
|
+
end
|
259
259
|
end
|
260
|
-
end
|
261
|
-
|
260
|
+
end
|
261
|
+
|
262
|
+
result << row
|
263
|
+
end
|
262
264
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
result
|
270
|
-
end
|
265
|
+
if selectCountMatch
|
266
|
+
[{ :count => result.actual_size }]
|
267
|
+
else
|
268
|
+
result
|
269
|
+
end
|
270
|
+
}
|
271
271
|
end
|
272
272
|
|
273
273
|
|
274
274
|
def select_one(sql, name = nil) #:nodoc:
|
275
275
|
self.batch_size = 1
|
276
|
-
|
277
|
-
log(sql, name)
|
278
|
-
|
276
|
+
|
279
277
|
result = select_all(sql, name)
|
280
|
-
|
278
|
+
|
281
279
|
result.nil? ? nil : result.first
|
282
280
|
end
|
283
281
|
|
284
282
|
|
285
283
|
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
286
|
-
log(sql, name)
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
values.map! { |v| v[7] }
|
301
|
-
|
302
|
-
fields = get_fields(columns, names, values, :createable)
|
303
|
-
|
304
|
-
sobject = create_sobject(entity_name, nil, fields)
|
284
|
+
log(sql, name) {
|
285
|
+
# Convert sql to sobject
|
286
|
+
table_name = sql.match(/INSERT INTO (\w+) /i)[1].singularize
|
287
|
+
entity_name = entity_name_from_table(table_name)
|
288
|
+
columns = columns_map(table_name)
|
289
|
+
|
290
|
+
# Extract array of column names
|
291
|
+
names = sql.match(/\((.+)\) VALUES/i)[1].scan(/\w+/i)
|
292
|
+
|
293
|
+
# Extract arrays of values
|
294
|
+
values = sql.match(/VALUES\s*\((.+)\)/i)[1]
|
295
|
+
values = values.scan(/(((NULL))|((TRUE))|((FALSE))|'(([^']|'')*)'),*/mi)
|
296
|
+
|
297
|
+
values.map! { |v| v[7] }
|
305
298
|
|
306
|
-
|
299
|
+
fields = get_fields(columns, names, values, :createable)
|
300
|
+
|
301
|
+
sobject = create_sobject(entity_name, nil, fields)
|
302
|
+
|
303
|
+
check_result(get_result(@connection.create(:sObjects => sobject), :create))[0][:id]
|
304
|
+
}
|
307
305
|
end
|
308
306
|
|
309
307
|
|
310
308
|
def update(sql, name = nil) #:nodoc:
|
311
|
-
log(sql, name)
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
309
|
+
log(sql, name) {
|
310
|
+
# Convert sql to sobject
|
311
|
+
table_name = sql.match(/UPDATE (\w+) /i)[1].singularize
|
312
|
+
entity_name = entity_name_from_table(table_name)
|
313
|
+
columns = columns_map(table_name)
|
314
|
+
|
315
|
+
match = sql.match(/SET\s+(.+)\s+WHERE/mi)[1]
|
316
|
+
names = match.scan(/(\w+)\s*=\s*('|NULL|TRUE|FALSE)/i)
|
317
|
+
names.map! { |v| v[0] }
|
318
|
+
|
319
|
+
values = match.scan(/=\s*(((NULL))|((TRUE))|((FALSE))|'(([^']|'')*)'),*/mi)
|
320
|
+
values.map! { |v| v[7] }
|
321
|
+
|
322
|
+
fields = get_fields(columns, names, values, :updateable)
|
323
|
+
|
324
|
+
id = sql.match(/WHERE\s+id\s*=\s*'(\w+)'/i)[1]
|
325
|
+
|
326
|
+
sobject = create_sobject(entity_name, id, fields)
|
327
|
+
|
328
|
+
check_result(get_result(@connection.update(:sObjects => sobject), :update))
|
329
|
+
}
|
332
330
|
end
|
333
331
|
|
334
332
|
|
335
333
|
def delete(sql, name = nil)
|
336
|
-
log(sql, name)
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
334
|
+
log(sql, name) {
|
335
|
+
# Extract the id
|
336
|
+
match = sql.match(/WHERE\s+id\s*=\s*'(\w+)'/mi)
|
337
|
+
|
338
|
+
if match
|
339
|
+
ids = [ match[1] ]
|
340
|
+
else
|
341
|
+
# Check for the form id IN ('x', 'y')
|
342
|
+
match = sql.match(/WHERE\s+id\s+IN\s*\((.+)\)/mi)[1]
|
343
|
+
ids = match.scan(/\w+/)
|
344
|
+
end
|
345
|
+
|
346
|
+
ids_element = []
|
347
|
+
ids.each { |id| ids_element << :ids << id }
|
348
|
+
|
349
|
+
check_result(get_result(@connection.delete(ids_element), :delete))
|
350
|
+
}
|
353
351
|
end
|
354
352
|
|
355
353
|
|
@@ -398,44 +396,48 @@ module ActiveRecord
|
|
398
396
|
def get_entity_def(entity_name)
|
399
397
|
cached_entity_def = @entity_def_map[entity_name]
|
400
398
|
return cached_entity_def if cached_entity_def
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
begin
|
406
|
-
metadata = get_result(@connection.describeSObject(:sObjectType => entity_name), :describeSObject)
|
407
|
-
custom = false
|
408
|
-
rescue SalesforceError => e
|
409
|
-
# Fallback and see if we can find a custom object with this name
|
410
|
-
metadata = get_result(@connection.describeSObject(:sObjectType => entity_name + "__c"), :describeSObject)
|
411
|
-
custom = true
|
412
|
-
end
|
413
|
-
|
414
|
-
metadata[:fields].each do |field|
|
415
|
-
column = SalesforceColumn.new(field)
|
416
|
-
cached_columns << column
|
399
|
+
|
400
|
+
log("Retrieving metadata for '#{entity_name}'", "get_entity_def()") {
|
401
|
+
cached_columns = []
|
402
|
+
cached_relationships = []
|
417
403
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
404
|
+
begin
|
405
|
+
metadata = get_result(@connection.describeSObject(:sObjectType => entity_name), :describeSObject)
|
406
|
+
custom = false
|
407
|
+
rescue SalesforceError => e
|
408
|
+
# Fallback and see if we can find a custom object with this name
|
409
|
+
@logger.info(" Unable to find medata for '#{entity_name}', falling back to custom object name #{entity_name + "__c"}")
|
410
|
+
|
411
|
+
metadata = get_result(@connection.describeSObject(:sObjectType => entity_name + "__c"), :describeSObject)
|
412
|
+
custom = true
|
413
|
+
end
|
414
|
+
|
415
|
+
metadata[:fields].each do |field|
|
416
|
+
column = SalesforceColumn.new(field)
|
417
|
+
cached_columns << column
|
418
|
+
|
419
|
+
cached_relationships << SalesforceRelationship.new(field, column) if field[:type] =~ /reference/i
|
420
|
+
end
|
421
|
+
|
422
|
+
relationships = metadata[:childRelationships]
|
423
|
+
if relationships
|
424
|
+
relationships = [ relationships ] unless relationships.is_a? Array
|
425
|
+
|
426
|
+
relationships.each do |relationship|
|
427
|
+
if relationship[:cascadeDelete] == "true"
|
428
|
+
r = SalesforceRelationship.new(relationship)
|
429
|
+
cached_relationships << r
|
430
|
+
end
|
429
431
|
end
|
430
432
|
end
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
433
|
+
|
434
|
+
entity_def = EntityDefinition.new(entity_name, cached_columns, cached_relationships, custom)
|
435
|
+
@entity_def_map[entity_name] = entity_def
|
436
|
+
|
437
|
+
configure_active_record entity_def
|
438
|
+
|
439
|
+
entity_def
|
440
|
+
}
|
439
441
|
end
|
440
442
|
|
441
443
|
|
@@ -459,7 +461,7 @@ module ActiveRecord
|
|
459
461
|
# DCHASMAN TODO Figure out how to handle polymorphic refs (e.g. Note.parent can refer to
|
460
462
|
# Account, Contact, Opportunity, Contract, Asset, Product2, <CustomObject1> ... <CustomObject(n)>
|
461
463
|
if reference_to.is_a? Array
|
462
|
-
|
464
|
+
@logger.info(" Skipping unsupported polymophic one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{entity_name} to [#{relationship.reference_to.join(', ')}] using #{foreign_key}")
|
463
465
|
next
|
464
466
|
end
|
465
467
|
|
@@ -470,7 +472,7 @@ module ActiveRecord
|
|
470
472
|
referenced_klass = reference_to.constantize
|
471
473
|
rescue NameError => e
|
472
474
|
# Automatically create a least a stub for the referenced entity
|
473
|
-
|
475
|
+
@logger.info(" Creating ActiveRecord stub for the referenced entity '#{reference_to}'")
|
474
476
|
|
475
477
|
referenced_klass = klass.class_eval("::#{reference_to} = Class.new(ActiveRecord::Base)")
|
476
478
|
|
@@ -483,7 +485,7 @@ module ActiveRecord
|
|
483
485
|
klass.belongs_to referenceName.to_sym, :class_name => reference_to, :foreign_key => foreign_key, :dependent => false
|
484
486
|
end
|
485
487
|
|
486
|
-
|
488
|
+
@logger.info(" Created one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{entity_name} to #{relationship.reference_to} using #{foreign_key}")
|
487
489
|
|
488
490
|
end
|
489
491
|
end
|