activerecord-activesalesforce-adapter 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +51 -0
- data/lib/active_record/connection_adapters/activesalesforce.rb +36 -0
- data/lib/active_record/connection_adapters/activesalesforce_adapter.rb +777 -0
- data/lib/active_record/connection_adapters/asf_active_record.rb +40 -0
- data/lib/active_record/connection_adapters/boxcar_command.rb +66 -0
- data/lib/active_record/connection_adapters/column_definition.rb +95 -0
- data/lib/active_record/connection_adapters/entity_definition.rb +59 -0
- data/lib/active_record/connection_adapters/id_resolver.rb +84 -0
- data/lib/active_record/connection_adapters/recording_binding.rb +90 -0
- data/lib/active_record/connection_adapters/relationship_definition.rb +81 -0
- data/lib/active_record/connection_adapters/result_array.rb +31 -0
- data/lib/active_record/connection_adapters/rforce.rb +361 -0
- data/lib/active_record/connection_adapters/sid_authentication_filter.rb +57 -0
- data/test/unit/basic_test.rb +203 -0
- data/test/unit/config.yml +5 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_add_notes_to_contact.recording +1966 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_assignment_rule_id.recording +1621 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_batch_insert.recording +1611 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_client_id.recording +1618 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_count_contacts.recording +1620 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_create_a_contact.recording +1611 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact.recording +1611 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_first_name.recording +3468 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_a_contact_by_id.recording +1664 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_find_addresses.recording +1635 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_get_created_by_from_contact.recording +4307 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_master_detail.recording +1951 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_read_all_content_columns.recording +1611 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_save_a_contact.recording +1611 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_use_default_rule.recording +1618 -0
- data/test/unit/recorded_results/AsfUnitTestsBasicTest.test_use_update_mru.recording +1618 -0
- data/test/unit/recorded_test_case.rb +83 -0
- metadata +105 -0
@@ -0,0 +1,40 @@
|
|
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
|
+
module ActiveSalesforce
|
19
|
+
|
20
|
+
module ActiveRecord
|
21
|
+
|
22
|
+
module Mixin
|
23
|
+
def self.append_features(base) #:nodoc:
|
24
|
+
super
|
25
|
+
|
26
|
+
base.class_eval do
|
27
|
+
class << self
|
28
|
+
def set_table_name(value = nil, &block)
|
29
|
+
super(value, &block)
|
30
|
+
|
31
|
+
connection.set_class_for_entity(self, table_name.singularize)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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 'pp'
|
19
|
+
|
20
|
+
|
21
|
+
module ActiveSalesforce
|
22
|
+
module BoxcarCommand
|
23
|
+
|
24
|
+
class Base
|
25
|
+
attr_reader :verb, :args
|
26
|
+
|
27
|
+
def initialize(connection, verb, args)
|
28
|
+
@connection = connection
|
29
|
+
@verb = verb
|
30
|
+
@args = args
|
31
|
+
end
|
32
|
+
|
33
|
+
def after_execute(result)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class Insert < Base
|
39
|
+
def initialize(connection, sobject, idproxy)
|
40
|
+
super(connection, :create, sobject)
|
41
|
+
@idproxy = idproxy
|
42
|
+
end
|
43
|
+
|
44
|
+
def after_execute(result)
|
45
|
+
id = result[:id]
|
46
|
+
@idproxy << id if id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
class Update < Base
|
52
|
+
def initialize(connection, sobject)
|
53
|
+
super(connection, :update, sobject)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
class Delete < Base
|
59
|
+
def initialize(connection, ids)
|
60
|
+
super(connection, :delete, ids)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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 'pp'
|
19
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
20
|
+
|
21
|
+
|
22
|
+
module ActiveRecord
|
23
|
+
module StringHelper
|
24
|
+
def column_nameize(s)
|
25
|
+
s.underscore
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module ConnectionAdapters
|
30
|
+
class SalesforceColumn < Column
|
31
|
+
include StringHelper
|
32
|
+
|
33
|
+
attr_reader :api_name, :custom, :label, :createable, :updateable, :reference_to
|
34
|
+
|
35
|
+
def initialize(field)
|
36
|
+
@api_name = field[:name]
|
37
|
+
@custom = field[:custom] == "true"
|
38
|
+
@name = column_nameize(@api_name)
|
39
|
+
@type = get_type(field[:type])
|
40
|
+
@limit = field[:length]
|
41
|
+
@label = field[:label]
|
42
|
+
@name_field = field[:nameField] == "true"
|
43
|
+
|
44
|
+
@text = [:string, :text].include? @type
|
45
|
+
@number = [:float, :integer].include? @type
|
46
|
+
|
47
|
+
@createable = field[:createable] == "true"
|
48
|
+
@updateable = field[:updateable] == "true"
|
49
|
+
|
50
|
+
if field[:type] =~ /reference/i
|
51
|
+
@reference_to = field[:referenceTo]
|
52
|
+
@one_to_many = false
|
53
|
+
@cascade_delete = false
|
54
|
+
|
55
|
+
@name = @name.chop.chop << "id__c" if @custom
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def is_name?
|
60
|
+
@name_field
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_type(field_type)
|
64
|
+
case field_type
|
65
|
+
when /int/i
|
66
|
+
:integer
|
67
|
+
when /currency|percent/i
|
68
|
+
:float
|
69
|
+
when /datetime/i
|
70
|
+
:datetime
|
71
|
+
when /date/i
|
72
|
+
:date
|
73
|
+
when /id|string|textarea/i
|
74
|
+
:text
|
75
|
+
when /phone|fax|email|url/i
|
76
|
+
:string
|
77
|
+
when /blob|binary/i
|
78
|
+
:binary
|
79
|
+
when /boolean/i
|
80
|
+
:boolean
|
81
|
+
when /picklist/i
|
82
|
+
:text
|
83
|
+
when /reference/i
|
84
|
+
:text
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def human_name
|
89
|
+
@label
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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 'pp'
|
19
|
+
|
20
|
+
|
21
|
+
module ActiveSalesforce
|
22
|
+
class EntityDefinition
|
23
|
+
attr_reader :name, :asf_class, :columns, :column_name_to_column, :api_name_to_column, :relationships, :key_prefix
|
24
|
+
|
25
|
+
def initialize(connection, name, asf_class, columns, relationships, custom, key_prefix)
|
26
|
+
@connection = connection
|
27
|
+
@name = name
|
28
|
+
@asf_class = asf_class
|
29
|
+
@columns = columns
|
30
|
+
@relationships = relationships
|
31
|
+
@custom = custom
|
32
|
+
@key_prefix = key_prefix
|
33
|
+
|
34
|
+
@column_name_to_column = {}
|
35
|
+
@columns.each { |column| @column_name_to_column[column.name] = column }
|
36
|
+
|
37
|
+
@api_name_to_column = {}
|
38
|
+
@columns.each { |column| @api_name_to_column[column.api_name] = column }
|
39
|
+
end
|
40
|
+
|
41
|
+
def custom?
|
42
|
+
@custom
|
43
|
+
end
|
44
|
+
|
45
|
+
def api_name
|
46
|
+
@custom ? name + "__c" : name
|
47
|
+
end
|
48
|
+
|
49
|
+
def layouts
|
50
|
+
return @layouts if @layouts
|
51
|
+
|
52
|
+
# Lazy load Layout information
|
53
|
+
response = @connection.binding.describeLayout(:sObjectType => api_name)
|
54
|
+
@layouts = @connection.get_result(response, :describeLayout)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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 'pp'
|
19
|
+
|
20
|
+
|
21
|
+
module ActiveSalesforce
|
22
|
+
class IdResolver
|
23
|
+
attr_reader :object_type_to_ids
|
24
|
+
|
25
|
+
def initialize(connection)
|
26
|
+
@connection = connection
|
27
|
+
@object_type_to_ids = {}
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def add(record, columns = nil)
|
32
|
+
if columns
|
33
|
+
columns = columns.to_a.map { |column_name| record.column_for_attribute(column_name) }
|
34
|
+
else
|
35
|
+
columns = record.class.columns
|
36
|
+
end
|
37
|
+
|
38
|
+
columns.each do |column|
|
39
|
+
reference_to = column.reference_to
|
40
|
+
next unless reference_to
|
41
|
+
|
42
|
+
value = record.send(column.name)
|
43
|
+
if value
|
44
|
+
ids = @object_type_to_ids[reference_to] ||= Set.new
|
45
|
+
ids << value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def resolve
|
52
|
+
result = {}
|
53
|
+
|
54
|
+
@object_type_to_ids.each do |object_type, ids|
|
55
|
+
entity_def = @connection.get_entity_def(object_type)
|
56
|
+
|
57
|
+
fields = (entity_def.columns.reject { |column| not column.is_name? }).map { |column| column.api_name }
|
58
|
+
|
59
|
+
# DCHASMAN TODO Boxcar into requests of no more than 200 retrieves per request
|
60
|
+
puts "Resolving references to #{object_type}"
|
61
|
+
field_values = @connection.retrieve_field_values(object_type, fields, ids.to_a, "#{self}.resolve()")
|
62
|
+
|
63
|
+
field_values.each do |field_value|
|
64
|
+
id = field_value.delete(:Id)
|
65
|
+
result[id] = field_value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def serialize
|
74
|
+
YAML.dump @object_type_to_ids
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def deserialize(source)
|
79
|
+
@object_type_to_ids = YAML.load(source)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
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
|
@@ -0,0 +1,81 @@
|
|
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 'pp'
|
19
|
+
|
20
|
+
|
21
|
+
module ActiveRecord
|
22
|
+
module StringHelper
|
23
|
+
def column_nameize(s)
|
24
|
+
s.underscore
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module ConnectionAdapters
|
29
|
+
|
30
|
+
class SalesforceRelationship
|
31
|
+
include StringHelper
|
32
|
+
|
33
|
+
attr_reader :name, :api_name, :custom, :foreign_key, :label, :reference_to, :one_to_many
|
34
|
+
|
35
|
+
def initialize(source, column = nil)
|
36
|
+
if source[:childSObject]
|
37
|
+
relationship = source
|
38
|
+
|
39
|
+
if relationship[:relationshipName]
|
40
|
+
@api_name = relationship[:relationshipName]
|
41
|
+
@one_to_many = true
|
42
|
+
@label = @api_name
|
43
|
+
@name = column_nameize(@api_name)
|
44
|
+
@custom = false
|
45
|
+
else
|
46
|
+
@api_name = relationship[:field]
|
47
|
+
@one_to_many = relationship[:cascadeDelete] == "true"
|
48
|
+
@label = relationship[:childSObject].pluralize
|
49
|
+
@custom = relationship[:childSObject].match(/__c$/)
|
50
|
+
|
51
|
+
name = relationship[:childSObject]
|
52
|
+
name = name.chop.chop.chop if custom
|
53
|
+
|
54
|
+
@name = column_nameize(name.pluralize)
|
55
|
+
@name = @name + "__c" if custom
|
56
|
+
end
|
57
|
+
|
58
|
+
@reference_to = relationship[:childSObject]
|
59
|
+
|
60
|
+
@foreign_key = column_nameize(relationship[:field])
|
61
|
+
@foreign_key = @foreign_key.chop.chop << "id__c" if @foreign_key.match(/__c$/)
|
62
|
+
else
|
63
|
+
field = source
|
64
|
+
|
65
|
+
@api_name = field[:name]
|
66
|
+
@custom = field[:custom] == "true"
|
67
|
+
|
68
|
+
@api_name = @api_name.chop.chop unless @custom
|
69
|
+
|
70
|
+
@label = field[:label]
|
71
|
+
@reference_to = field[:referenceTo]
|
72
|
+
@one_to_many = false
|
73
|
+
|
74
|
+
@foreign_key = column.name
|
75
|
+
@name = column_nameize(@api_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|