activesalesforce 0.2.8 → 0.2.9

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.
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
- puts "\nUsing ActiveSalesforce connection\n"
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
- puts " via provided binding #{binding}\n"
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
- puts "Establishing new connection for [sid='#{sid}']"
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
- puts "Created new connection for [sid='#{sid}']"
67
+ logger.debug("Created new connection for [sid='#{sid}']")
68
68
  end
69
69
 
70
- ConnectionAdapters::SalesforceAdapter.new(connection, logger, [url, sid], config)
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
- puts "Establishing new connection for ['#{url}', '#{username}']"
84
+ logger.debug("Establishing new connection for ['#{url}', '#{username}']")
85
85
 
86
- binding = RForce::Binding.new(url, sid)
87
- binding.login(username, password).result
88
-
89
- @@cache["#{url}.#{username}.#{password}"] = binding
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
- puts "Created new connection for ['#{url}', '#{username}']"
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") if logger
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
- # Check for SELECT COUNT(*) FROM query
196
- selectCountMatch = sql.match(/SELECT COUNT\(\*\) FROM/i)
197
- if selectCountMatch
198
- soql = "SELECT id FROM#{selectCountMatch.post_match}"
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
- column = columns[column_name]
225
- soql = $~.pre_match + column.api_name + $~.post_match
226
- end
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
- record.each do |name, value|
248
- if name != :type
249
- # Ids may be returned in an array with 2 duplicate entries...
250
- value = value[0] if name == :Id && value.is_a?(Array)
251
-
252
- column = entity_def.api_name_to_column[name.to_s]
253
- attribute_name = column.name
254
-
255
- if column.type == :boolean
256
- row[attribute_name] = (value.casecmp("true") == 0)
257
- else
258
- row[attribute_name] = value
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
- end
260
+ end
261
+
262
+ result << row
263
+ end
262
264
 
263
- result << row
264
- end
265
-
266
- if selectCountMatch
267
- [{ :count => result.actual_size }]
268
- else
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
- # Convert sql to sobject
289
- table_name = sql.match(/INSERT INTO (\w+) /i)[1].singularize
290
- entity_name = entity_name_from_table(table_name)
291
- columns = columns_map(table_name)
292
-
293
- # Extract array of column names
294
- names = sql.match(/\((.+)\) VALUES/i)[1].scan(/\w+/i)
295
-
296
- # Extract arrays of values
297
- values = sql.match(/VALUES\s*\((.+)\)/i)[1]
298
- values = values.scan(/(((NULL))|((TRUE))|((FALSE))|'(([^']|'')*)'),*/mi)
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
- check_result(get_result(@connection.create(:sObjects => sobject), :create))[0][:id]
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
- # Convert sql to sobject
314
- table_name = sql.match(/UPDATE (\w+) /i)[1].singularize
315
- entity_name = entity_name_from_table(table_name)
316
- columns = columns_map(table_name)
317
-
318
- match = sql.match(/SET\s+(.+)\s+WHERE/mi)[1]
319
- names = match.scan(/(\w+)\s*=\s*('|NULL|TRUE|FALSE)/i)
320
- names.map! { |v| v[0] }
321
-
322
- values = match.scan(/=\s*(((NULL))|((TRUE))|((FALSE))|'(([^']|'')*)'),*/mi)
323
- values.map! { |v| v[7] }
324
-
325
- fields = get_fields(columns, names, values, :updateable)
326
-
327
- id = sql.match(/WHERE\s+id\s*=\s*'(\w+)'/i)[1]
328
-
329
- sobject = create_sobject(entity_name, id, fields)
330
-
331
- check_result(get_result(@connection.update(:sObjects => sobject), :update))
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
- # Extract the id
339
- match = sql.match(/WHERE\s+id\s*=\s*'(\w+)'/mi)
340
-
341
- if match
342
- ids = [ match[1] ]
343
- else
344
- # Check for the form id IN ('x', 'y')
345
- match = sql.match(/WHERE\s+id\s+IN\s*\((.+)\)/mi)[1]
346
- ids = match.scan(/\w+/)
347
- end
348
-
349
- ids_element = []
350
- ids.each { |id| ids_element << :ids << id }
351
-
352
- check_result(get_result(@connection.delete(ids_element), :delete))
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
- cached_columns = []
403
- cached_relationships = []
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
- cached_relationships << SalesforceRelationship.new(field, column) if field[:type] =~ /reference/i
419
- end
420
-
421
- relationships = metadata[:childRelationships]
422
- if relationships
423
- relationships = [ relationships ] unless relationships.is_a? Array
424
-
425
- relationships.each do |relationship|
426
- if relationship[:cascadeDelete] == "true"
427
- r = SalesforceRelationship.new(relationship)
428
- cached_relationships << r
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
- end
432
-
433
- entity_def = EntityDefinition.new(entity_name, cached_columns, cached_relationships, custom)
434
- @entity_def_map[entity_name] = entity_def
435
-
436
- configure_active_record entity_def
437
-
438
- entity_def
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
- puts " Skipping unsupported polymophic one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{entity_name} to [#{relationship.reference_to.join(', ')}] using #{foreign_key}"
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
- puts " Creating ActiveRecord stub for the referenced entity '#{reference_to}'"
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
- puts " Created one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{entity_name} to #{relationship.reference_to} using #{foreign_key}"
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