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 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