activesalesforce 0.4.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/asf_adapter.rb CHANGED
@@ -1,23 +1,23 @@
1
1
  =begin
2
- ActiveSalesforce
3
- Copyright 2006 Doug Chasman
4
-
5
- Licensed under the Apache License, Version 2.0 (the "License");
6
- you may not use this file except in compliance with the License.
7
- You may obtain a copy of the License at
8
-
9
- http://www.apache.org/licenses/LICENSE-2.0
10
-
11
- Unless required by applicable law or agreed to in writing, software
12
- distributed under the License is distributed on an "AS IS" BASIS,
13
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- See the License for the specific language governing permissions and
15
- limitations under the License.
2
+ ActiveSalesforce
3
+ Copyright 2006 Doug Chasman
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
16
  =end
17
17
 
18
18
  require 'rubygems'
19
19
  require_gem 'rails', ">= 1.0.0"
20
-
20
+
21
21
  require 'thread'
22
22
  require 'benchmark'
23
23
 
@@ -29,6 +29,7 @@ require File.dirname(__FILE__) + '/entity_definition'
29
29
  require File.dirname(__FILE__) + '/asf_active_record'
30
30
  require File.dirname(__FILE__) + '/id_resolver'
31
31
  require File.dirname(__FILE__) + '/sid_authentication_filter'
32
+ require File.dirname(__FILE__) + '/recording_binding'
32
33
 
33
34
 
34
35
  class ResultArray < Array
@@ -51,12 +52,19 @@ module ActiveRecord
51
52
  # Default to production system using 7.0 API
52
53
  url = config[:url]
53
54
  url = "https://www.salesforce.com/services/Soap/u/7.0" unless url
54
-
55
+
55
56
  sid = config[:sid]
56
- binding = config[:binding] if config[:binding]
57
+ username = config[:username]
58
+ password = config[:password]
57
59
 
58
- if binding
59
- logger.debug(" via provided binding #{binding}\n")
60
+ # Recording/playback support
61
+ recording_source = config[:recording_source]
62
+ recording = config[:recording]
63
+
64
+ if recording_source
65
+ recording_source = File.open(recording_source, recording ? "w" : "r")
66
+ binding = ActiveSalesforce::RecordingBinding.new(url, nil, recording != nil, recording_source, logger)
67
+ binding.login(username, password) unless sid
60
68
  end
61
69
 
62
70
  if sid
@@ -74,10 +82,7 @@ module ActiveRecord
74
82
  ConnectionAdapters::SalesforceAdapter.new(binding, logger, [url, sid], config)
75
83
  else
76
84
  # Check to insure that the second to last path component is a 'u' for Partner API
77
- raise ConnectionAdapters::SalesforceError.new(logger, "Invalid salesforce server url '#{url}', must be a valid Parter API URL") unless url.match(/\/u\//i)
78
-
79
- username = config[:username]
80
- password = config[:password]
85
+ raise ActiveSalesforce::ASFError.new(logger, "Invalid salesforce server url '#{url}', must be a valid Parter API URL") unless url.match(/\/u\//i)
81
86
 
82
87
  binding = @@cache["#{url}.#{username}.#{password}"] unless binding
83
88
 
@@ -86,7 +91,7 @@ module ActiveRecord
86
91
 
87
92
  seconds = Benchmark.realtime {
88
93
  binding = RForce::Binding.new(url, sid)
89
- binding.login(username, password).result
94
+ binding.login(username, password)
90
95
 
91
96
  @@cache["#{url}.#{username}.#{password}"] = binding
92
97
  }
@@ -98,25 +103,13 @@ module ActiveRecord
98
103
  end
99
104
  end
100
105
  end
101
-
106
+
102
107
 
103
108
  module ConnectionAdapters
104
- class SalesforceError < RuntimeError
105
- attr :fault
106
-
107
- def initialize(logger, message, fault = nil)
108
- super message
109
-
110
- @fault = fault
111
-
112
- logger.debug("\nSalesforceError:\n message='#{message}'\n fault='#{fault}'\n\n")
113
- end
114
- end
115
-
116
109
 
117
110
  class SalesforceAdapter < AbstractAdapter
118
111
  include StringHelper
119
-
112
+
120
113
  MAX_BOXCAR_SIZE = 200
121
114
 
122
115
  attr_accessor :batch_size
@@ -221,7 +214,7 @@ module ActiveRecord
221
214
  unless errors.empty?
222
215
  message = errors.join("\n")
223
216
  fault = (errors.map { |error| error[:message] }).join("\n")
224
- SalesforceError.new(@logger, message, fault)
217
+ ActiveSalesforce::ASFError.new(@logger, message, fault)
225
218
  end
226
219
 
227
220
  result
@@ -293,7 +286,7 @@ module ActiveRecord
293
286
  column_name.sub!(/\w+\./, '')
294
287
 
295
288
  column = columns[column_name]
296
- raise SalesforceError.new(@logger, "Column not found for #{column_name}!") unless column
289
+ raise ActiveSalesforce::ASFError.new(@logger, "Column not found for #{column_name}!") unless column
297
290
 
298
291
  column.api_name
299
292
  end
@@ -440,7 +433,16 @@ module ActiveRecord
440
433
  result = [ result ] unless result.is_a?(Array)
441
434
 
442
435
  # Remove unwanted :type and normalize :Id if required
443
- result.map { |v| v.delete(:type); v[:Id] = v[:Id][0] if v[:Id].is_a? Array; v }
436
+ field_values = []
437
+ result.each do |v|
438
+ v = v.dup
439
+ v.delete(:type)
440
+ v[:Id] = v[:Id][0] if v[:Id].is_a? Array
441
+
442
+ field_values << v
443
+ end
444
+
445
+ field_values
444
446
  }
445
447
  end
446
448
 
@@ -453,7 +455,7 @@ module ActiveRecord
453
455
  if value
454
456
  column = columns[name]
455
457
 
456
- raise SalesforceError.new(@logger, "Column not found for #{name}!") unless column
458
+ raise ActiveSalesforce::ASFError.new(@logger, "Column not found for #{name}!") unless column
457
459
 
458
460
  value.gsub!(/''/, "'") if value.is_a? String
459
461
 
@@ -470,7 +472,7 @@ module ActiveRecord
470
472
  responseName = (method.to_s + "Response").to_sym
471
473
  finalResponse = response[responseName]
472
474
 
473
- raise SalesforceError.new(@logger, response[:Fault][:faultstring], response.fault) unless finalResponse
475
+ raise ActiveSalesforce::ASFError.new(@logger, response[:Fault][:faultstring], response.fault) unless finalResponse
474
476
 
475
477
  result = finalResponse[:result]
476
478
  end
@@ -480,7 +482,7 @@ module ActiveRecord
480
482
  result = [ result ] unless result.is_a?(Array)
481
483
 
482
484
  result.each do |r|
483
- raise SalesforceError.new(@logger, r[:errors], r[:errors][:message]) unless r[:success] == "true"
485
+ raise ActiveSalesforce::ASFError.new(@logger, r[:errors], r[:errors][:message]) unless r[:success] == "true"
484
486
  end
485
487
 
486
488
  result
@@ -494,7 +496,7 @@ module ActiveRecord
494
496
  # Check for the loss of asf AR setup
495
497
  entity_klass = class_from_entity_name(entity_name)
496
498
 
497
- configure_active_record cached_entity_def unless entity_klass.respond_to?(:asf_augmented?)
499
+ configure_active_record(cached_entity_def) unless entity_klass.respond_to?(:asf_augmented?)
498
500
 
499
501
  return cached_entity_def
500
502
  end
@@ -506,7 +508,7 @@ module ActiveRecord
506
508
  begin
507
509
  metadata = get_result(@connection.describeSObject(:sObjectType => entity_name), :describeSObject)
508
510
  custom = false
509
- rescue SalesforceError => e
511
+ rescue ActiveSalesforce::ASFError => e
510
512
  # Fallback and see if we can find a custom object with this name
511
513
  @logger.debug(" Unable to find medata for '#{entity_name}', falling back to custom object name #{entity_name + "__c"}")
512
514
 
@@ -536,8 +538,8 @@ module ActiveRecord
536
538
  key_prefix = metadata[:keyPrefix]
537
539
 
538
540
  entity_def = ActiveSalesforce::EntityDefinition.new(self, entity_name,
539
- cached_columns, cached_relationships, custom, key_prefix)
540
-
541
+ cached_columns, cached_relationships, custom, key_prefix)
542
+
541
543
  @entity_def_map[entity_name] = entity_def
542
544
  @keyprefix_to_entity_def_map[key_prefix] = entity_def
543
545
 
@@ -558,6 +560,9 @@ module ActiveRecord
558
560
  end
559
561
  end
560
562
 
563
+ # Add support for SID-based authentication
564
+ ActiveSalesforce::SessionIDAuthenticationFilter.register(klass)
565
+
561
566
  klass.set_inheritance_column nil
562
567
  klass.set_primary_key "id"
563
568
  klass.lock_optimistically = false
@@ -625,12 +630,12 @@ module ActiveRecord
625
630
 
626
631
 
627
632
  def class_from_entity_name(entity_name)
628
- entity_klass = @class_to_entity_map[entity_name.upcase]
629
- @logger.debug("Found matching class '#{entity_klass}' for entity '#{entity_name}'") if entity_klass
630
-
631
- entity_klass = entity_name.constantize unless entity_klass
632
-
633
- entity_klass
633
+ entity_klass = @class_to_entity_map[entity_name.upcase]
634
+ @logger.debug("Found matching class '#{entity_klass}' for entity '#{entity_name}'") if entity_klass
635
+
636
+ entity_klass = entity_name.constantize unless entity_klass
637
+
638
+ entity_klass
634
639
  end
635
640
 
636
641
 
data/lib/id_resolver.rb CHANGED
@@ -23,6 +23,7 @@ require 'pp'
23
23
 
24
24
  module ActiveSalesforce
25
25
  class IdResolver
26
+ attr_reader :object_type_to_ids
26
27
 
27
28
  def initialize(connection)
28
29
  @connection = connection
@@ -43,13 +44,7 @@ module ActiveSalesforce
43
44
 
44
45
  value = record.send(column.name)
45
46
  if value
46
- ids = @object_type_to_ids[reference_to]
47
-
48
- unless ids
49
- ids = Set.new
50
- @object_type_to_ids[reference_to] = ids
51
- end
52
-
47
+ ids = @object_type_to_ids[reference_to] ||= Set.new
53
48
  ids << value
54
49
  end
55
50
  end
@@ -0,0 +1,90 @@
1
+ =begin
2
+ ActiveSalesforce
3
+ Copyright 2006 Doug Chasman
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ =end
17
+
18
+ require 'yaml'
19
+
20
+
21
+ module ActiveSalesforce
22
+
23
+ class ASFError < RuntimeError
24
+ attr :fault
25
+
26
+ def initialize(logger, message, fault = nil)
27
+ super message
28
+
29
+ @fault = fault
30
+
31
+ logger.debug("\nASFError:\n message='#{message}'\n fault='#{fault}'\n\n")
32
+ end
33
+ end
34
+
35
+
36
+ class ASFRecordingBindingError < ASFError
37
+ def initialize(logger, message)
38
+ super(logger, message)
39
+ end
40
+ end
41
+
42
+
43
+ class RecordingBinding < RForce::Binding
44
+
45
+ attr_reader :recorded_responses
46
+
47
+ def initialize(url, sid, recording, recording_source, logger)
48
+ super(url, sid)
49
+
50
+ @recording = recording
51
+ @recorded_responses = {}
52
+ @recording_source = recording_source
53
+ @logger = logger
54
+
55
+ unless @recording
56
+ YAML.load_documents(recording_source) do |recorded_response|
57
+ @recorded_responses.merge!(recorded_response)
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ #Call a method on the remote server. Arguments can be
64
+ #a hash or (if order is important) an array of alternating
65
+ #keys and values.
66
+ def call_remote(method, args)
67
+ # Blank out username and password
68
+ safe_args = args.inject([]) {|memo, v| memo << ((memo.last == :username or memo.last == :password) ? "" : v) }
69
+ key = "#{method}(#{safe_args.join(':')})"
70
+
71
+ if @recording
72
+ response = super(method, args)
73
+
74
+ unless @recorded_responses[key]
75
+ # track this { key => request } to avoid duplication in the YAML
76
+ @recorded_responses[key] = true
77
+
78
+ YAML.dump({ key, response }, @recording_source)
79
+ end
80
+ else
81
+ response = @recorded_responses[key]
82
+ raise ASFRecordingBindingError.new(@logger, "Unable to find matching response for recorded request '#{key}'") unless response
83
+ end
84
+
85
+ response
86
+ end
87
+
88
+ end
89
+
90
+ end
data/lib/rforce.rb CHANGED
@@ -180,6 +180,8 @@ module RForce
180
180
  #Log in to the server and remember the session ID
181
181
  #returned to us by SalesForce.
182
182
  def login(user, password)
183
+ puts "\n\nIn login(#{user})\n\n"
184
+
183
185
  @user = user
184
186
  @password = password
185
187
 
@@ -16,6 +16,7 @@
16
16
  =end
17
17
 
18
18
  require 'rubygems'
19
+ require 'set'
19
20
  require_gem 'rails', ">= 1.0.0"
20
21
 
21
22
  require 'pp'
@@ -24,17 +25,37 @@ require 'pp'
24
25
  module ActiveSalesforce
25
26
 
26
27
  class SessionIDAuthenticationFilter
28
+ @@klasses = Set.new
27
29
 
30
+
31
+ def self.register(klass)
32
+ @@klasses.add(klass)
33
+ end
34
+
35
+
28
36
  def self.filter(controller)
29
37
  # Look to see if a SID was passed in the URL
30
38
  params = controller.params
31
39
  sid = params[:sid]
40
+
41
+ puts "Checking for the presence of a SID for [#{@@klasses.to_a.join(', ')}]"
42
+
32
43
  if sid
33
44
  api_server_url = params[:api_server_url]
34
45
  api_server_url = 'http://na1-api.salesforce.com/services/Soap/u/7.0' unless api_server_url
35
46
 
36
47
  puts "asf_sid_authenticate(:sid => '#{sid}', :url => '#{api_server_url}')"
37
- Contact.establish_connection(:adapter => 'activesalesforce', :sid => sid, :url => api_server_url) if sid
48
+
49
+ # Iterate over all classes that have registered for SID auth support
50
+ connection = nil
51
+ @@klasses.each do |klass|
52
+ unless connection
53
+ klass.establish_connection(:adapter => 'activesalesforce', :sid => sid, :url => api_server_url)
54
+ connection = klass.connection
55
+ else
56
+ klass = connection
57
+ end
58
+ end
38
59
  end
39
60
  end
40
61
 
@@ -31,9 +31,7 @@ module Asf
31
31
  LOGGER = Logger.new(STDOUT)
32
32
  @@config = YAML.load_file(File.dirname(__FILE__) + '/config.yml').symbolize_keys
33
33
 
34
- attr_reader :connection
35
-
36
-
34
+
37
35
  def recording?
38
36
  @recording
39
37
  end
@@ -66,36 +64,20 @@ module Asf
66
64
 
67
65
  @recording = (((not File.exists?(recording_file_name)) or config[:recording]) or @force_recording.include?(method_name.to_sym))
68
66
 
69
- @connection = MockBinding.new(url, nil, recording?)
67
+ action = { :adapter => 'activesalesforce', :username => config[:username],
68
+ :password => config[:password], :recording_source => recording_file_name, }
69
+
70
+ action[:recording] = true if @recording
70
71
 
71
72
  ActiveRecord::Base.logger = LOGGER
72
73
  ActiveRecord::Base.clear_connection_cache!
73
74
  ActiveRecord::Base.reset_column_information_and_inheritable_attributes_for_all_subclasses
74
- ActiveRecord::Base.establish_connection(:adapter => 'activesalesforce', :username => config[:username],
75
- :password => config[:password], :binding => connection)
76
-
77
- unless recording?
78
- File.open(recording_file_name) do |f|
79
- puts "Opening recorded binding #{recording_file_name}"
80
- connection.load(f)
81
- end
82
- end
75
+ ActiveRecord::Base.establish_connection(action)
83
76
 
84
- response = connection.login(config[:username], config[:password])
77
+ @connection = ActiveRecord::Base.connection
85
78
  end
86
79
 
87
80
 
88
- def teardown
89
- if recording?
90
- puts "Saving recorded binding #{recording_file_name}"
91
-
92
- File.open(recording_file_name, "w") do |f|
93
- connection.save(f)
94
- end
95
- end
96
- end
97
-
98
-
99
81
  def recording_file_name
100
82
  File.dirname(__FILE__) + "/recorded_results/#{self.class.name.gsub('::', '')}.#{method_name}.recording"
101
83
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: activesalesforce
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.0
7
- date: 2006-03-02 00:00:00 -05:00
6
+ version: 0.4.2
7
+ date: 2006-03-03 00:00:00 -05:00
8
8
  summary: ActiveSalesforce (ASF) is a Rails connection adapter that provides direct access to Salesforce.com hosted data and metadata via the ActiveRecord model layer. Objects, fields, and relationships are all auto surfaced as active record attributes and rels.
9
9
  require_paths:
10
10
  - lib
@@ -29,10 +29,10 @@ authors:
29
29
  - Doug Chasman
30
30
  files:
31
31
  - lib/boxcar_command.rb
32
+ - lib/recording_binding.rb
32
33
  - lib/column_definition.rb
33
34
  - lib/activesalesforce.rb
34
35
  - lib/entity_definition.rb
35
- - lib/mock_binding.rb
36
36
  - lib/rforce.rb
37
37
  - lib/asf_adapter.rb
38
38
  - lib/sid_authentication_filter.rb
data/lib/mock_binding.rb DELETED
@@ -1,70 +0,0 @@
1
- =begin
2
- ActiveSalesforce
3
- Copyright 2006 Doug Chasman
4
-
5
- Licensed under the Apache License, Version 2.0 (the "License");
6
- you may not use this file except in compliance with the License.
7
- You may obtain a copy of the License at
8
-
9
- http://www.apache.org/licenses/LICENSE-2.0
10
-
11
- Unless required by applicable law or agreed to in writing, software
12
- distributed under the License is distributed on an "AS IS" BASIS,
13
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- See the License for the specific language governing permissions and
15
- limitations under the License.
16
- =end
17
-
18
- require 'yaml'
19
- require File.dirname(__FILE__) + '/rforce'
20
-
21
-
22
- class MockBinding < RForce::Binding
23
- attr_reader :recorded_responses
24
-
25
- #Connect to the server securely.
26
- def initialize(url, sid, recording)
27
- @recording = recording
28
- @recorded_responses = {}
29
-
30
- super(url, sid) if @recording
31
- end
32
-
33
-
34
- def save(f)
35
- YAML.dump(@recorded_responses, f)
36
- end
37
-
38
-
39
- def load(f)
40
- @recorded_responses = YAML.load(f)
41
- end
42
-
43
-
44
- #Call a method on the remote server. Arguments can be
45
- #a hash or (if order is important) an array of alternating
46
- #keys and values.
47
- def call_remote(method, args)
48
- # Blank out username and password
49
- safe_args = args.inject([]) {|memo, v| memo << ((memo.last == :username or memo.last == :password) ? "" : v) }
50
- key = "#{method}(#{safe_args.join(':')})"
51
-
52
- if @recording
53
- response = super(method, args)
54
- @recorded_responses[key] = response
55
- else
56
- response = @recorded_responses[key]
57
-
58
- unless response
59
- @recorded_responses.each do |request, reponse|
60
- #pp request
61
- end
62
-
63
- raise "Unable to find matching response for recorded request '#{key}'"
64
- end
65
- end
66
-
67
- response
68
- end
69
-
70
- end