dm-salesforce 0.9.12 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +33 -17
- data/Rakefile +8 -7
- data/lib/dm-salesforce.rb +19 -10
- data/lib/dm-salesforce/adapter.rb +91 -130
- data/lib/dm-salesforce/connection.rb +9 -16
- data/lib/dm-salesforce/connection/errors.rb +7 -1
- data/lib/dm-salesforce/resource.rb +8 -0
- data/lib/dm-salesforce/soap_wrapper.rb +25 -20
- data/lib/dm-salesforce/sql.rb +74 -46
- data/lib/dm-salesforce/types.rb +14 -0
- data/lib/dm-salesforce/types/boolean.rb +23 -0
- data/lib/dm-salesforce/types/float.rb +8 -0
- data/lib/dm-salesforce/types/integer.rb +12 -0
- data/lib/dm-salesforce/types/serial.rb +14 -0
- data/lib/dm-salesforce/version.rb +2 -2
- metadata +19 -13
- data/lib/dm-salesforce/extensions.rb +0 -45
data/README.markdown
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
dm-salesforce
|
2
2
|
=============
|
3
3
|
|
4
|
-
A gem that provides a Salesforce Adapter for DataMapper.
|
4
|
+
A gem that provides a Salesforce Adapter for DataMapper 0.10.x.
|
5
|
+
There are older versions of dm-salesforce specifically for 0.9.x.
|
5
6
|
|
6
|
-
The wsdl is converted into Ruby classes
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
The wsdl is automatically converted into Ruby classes upon the first
|
8
|
+
invocation of the dm-salesforce adapter. The classes in turn get
|
9
|
+
cached locally in one of the following locations, in order of
|
10
|
+
precedence:
|
11
|
+
|
12
|
+
:repositories:salesforce:apidir (see included database.yml-example)
|
13
|
+
ENV["SALESFORCE_DIR"]
|
14
|
+
~/.salesforce/
|
15
|
+
|
16
|
+
It just works if you have the wsdl, directions for getting going are
|
17
|
+
outlined below.
|
10
18
|
|
11
19
|
An example of using the adapter:
|
12
20
|
|
@@ -17,12 +25,20 @@ An example of using the adapter:
|
|
17
25
|
:salesforce
|
18
26
|
end
|
19
27
|
|
28
|
+
# One way to define which properties are SalesForce-style.
|
20
29
|
def self.salesforce_id_properties
|
21
30
|
:id
|
22
31
|
end
|
23
32
|
|
24
|
-
property :id,
|
25
|
-
property :name,
|
33
|
+
property :id, String, :key => true
|
34
|
+
property :name, String
|
35
|
+
property :description, String
|
36
|
+
property :fax, String
|
37
|
+
property :phone, String
|
38
|
+
property :type, String
|
39
|
+
property :website, String
|
40
|
+
|
41
|
+
has 0..n, :contacts
|
26
42
|
end
|
27
43
|
|
28
44
|
class Contact
|
@@ -32,15 +48,12 @@ An example of using the adapter:
|
|
32
48
|
:salesforce
|
33
49
|
end
|
34
50
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
property :id, String, :serial => true
|
51
|
+
# Alternatively, specify the :salesforce_id option.
|
52
|
+
property :id, String, :serial => true, :salesforce_id => true
|
40
53
|
property :first_name, String
|
41
|
-
property :last_name,
|
42
|
-
property :email,
|
43
|
-
property :account_id, String
|
54
|
+
property :last_name, String
|
55
|
+
property :email, String
|
56
|
+
property :account_id, String, :salesforce_id => true
|
44
57
|
|
45
58
|
belongs_to :account
|
46
59
|
end
|
@@ -48,7 +61,6 @@ An example of using the adapter:
|
|
48
61
|
|
49
62
|
To get a test environment going with the free development tools you'll need to follow these steps.
|
50
63
|
|
51
|
-
|
52
64
|
* Get a developer account from http://force.salesforce.com
|
53
65
|
* Hit up https://login.salesforce.com, and login with the password they provided in your signup email
|
54
66
|
* Remember the password they force you to reset
|
@@ -68,12 +80,16 @@ To get a test environment going with the free development tools you'll need to f
|
|
68
80
|
VALID_SELF_SERVICE_USER = DataMapperSalesforce::UserDetails.new("quentin@example.com", "foo")
|
69
81
|
* Run 'bin/irb' and you should have access to the Account and Contact models
|
70
82
|
|
71
|
-
Special Thanks to
|
83
|
+
Special Thanks to those who helped
|
72
84
|
==================================================
|
85
|
+
* Yehuda Katz
|
73
86
|
* Corey Donohoe
|
87
|
+
* Tim Carey-Smith
|
74
88
|
* Andy Delcambre
|
75
89
|
* Ben Burkert
|
76
90
|
* Larry Diehl
|
91
|
+
* Jordan Ritter
|
92
|
+
* Martin Emde
|
77
93
|
|
78
94
|
[setup]: http://img.skitch.com/20090204-gaxdfxbi1emfita5dax48ids4m.jpg "Click on Setup"
|
79
95
|
[getwsdl]: http://img.skitch.com/20090204-nhurnuxwf5g3ufnjk2xkfjc5n4.jpg "Expand and Save"
|
data/Rakefile
CHANGED
@@ -4,15 +4,16 @@ require 'rubygems/specification'
|
|
4
4
|
require 'bundler'
|
5
5
|
require 'date'
|
6
6
|
require 'pp'
|
7
|
+
require 'tmpdir'
|
7
8
|
|
8
9
|
Bundler.require_env
|
9
10
|
require File.dirname(__FILE__) + '/lib/dm-salesforce'
|
10
11
|
|
11
12
|
GEM = "dm-salesforce"
|
12
|
-
GEM_VERSION =
|
13
|
-
AUTHORS = ["Yehuda Katz", 'Tim Carey-Smith']
|
14
|
-
EMAIL = "
|
15
|
-
HOMEPAGE = "http://
|
13
|
+
GEM_VERSION = DataMapper::Salesforce::VERSION
|
14
|
+
AUTHORS = ["Yehuda Katz", 'Tim Carey-Smith', 'Corey Donohoe']
|
15
|
+
EMAIL = "ninja@engineyard.com"
|
16
|
+
HOMEPAGE = "http://github.com/halorgium/dm-salesforce"
|
16
17
|
SUMMARY = "A DataMapper adapter to the Salesforce API"
|
17
18
|
|
18
19
|
@spec = Gem::Specification.new do |s|
|
@@ -43,7 +44,7 @@ end
|
|
43
44
|
|
44
45
|
desc "install the gem locally"
|
45
46
|
task :install => [:package] do
|
46
|
-
sh %{
|
47
|
+
sh %{gem install pkg/#{GEM}-#{GEM_VERSION} --no-ri --no-rdoc}
|
47
48
|
end
|
48
49
|
|
49
50
|
task :default => 'spec'
|
@@ -53,9 +54,9 @@ desc "Run specs"
|
|
53
54
|
Spec::Rake::SpecTask.new(:spec) do |t|
|
54
55
|
t.spec_opts << %w(-fs --color) << %w(-O spec/spec.opts)
|
55
56
|
t.spec_opts << '--loadby' << 'random'
|
56
|
-
t.spec_files =
|
57
|
+
t.spec_files = Dir["spec/**/*_spec.rb"]
|
57
58
|
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
58
|
-
t.rcov_opts << '--exclude' <<
|
59
|
+
t.rcov_opts << '--exclude' << "~/.salesforce,gems,vendor,/var/folders,spec,config,tmp"
|
59
60
|
t.rcov_opts << '--text-summary'
|
60
61
|
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
61
62
|
end
|
data/lib/dm-salesforce.rb
CHANGED
@@ -1,15 +1,24 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require "fileutils"
|
3
2
|
require 'dm-core'
|
4
3
|
require 'dm-validations'
|
5
|
-
require '
|
6
|
-
require '
|
7
|
-
require
|
8
|
-
require 'dm-salesforce/connection'
|
9
|
-
require 'dm-salesforce/version'
|
10
|
-
|
11
|
-
DataMapper::Adapters::SalesforceAdapter = DataMapperSalesforce::Adapter
|
4
|
+
require 'soap/wsdlDriver'
|
5
|
+
require 'soap/header/simplehandler'
|
6
|
+
require "rexml/element"
|
12
7
|
|
13
|
-
module
|
8
|
+
module DataMapper::Salesforce
|
14
9
|
UserDetails = Struct.new(:username, :password)
|
15
10
|
end
|
11
|
+
|
12
|
+
dir = File.expand_path(File.join(File.dirname(__FILE__), 'dm-salesforce'))
|
13
|
+
|
14
|
+
require dir / :resource
|
15
|
+
require dir / :connection
|
16
|
+
require dir / :connection / :errors
|
17
|
+
require dir / :soap_wrapper
|
18
|
+
require dir / :sql
|
19
|
+
require dir / :types
|
20
|
+
require dir / :version
|
21
|
+
|
22
|
+
require dir / :adapter
|
23
|
+
|
24
|
+
DataMapper::Adapters::SalesforceAdapter = DataMapper::Salesforce::Adapter
|
@@ -1,5 +1,7 @@
|
|
1
|
-
module
|
1
|
+
module DataMapper::Salesforce
|
2
2
|
class Adapter < DataMapper::Adapters::AbstractAdapter
|
3
|
+
include SQL
|
4
|
+
|
3
5
|
def initialize(name, uri_or_options)
|
4
6
|
super
|
5
7
|
@resource_naming_convention = proc do |value|
|
@@ -15,185 +17,144 @@ module DataMapperSalesforce
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
def normalize_uri(uri_or_options)
|
19
|
-
if uri_or_options.kind_of?(Addressable::URI)
|
20
|
-
return uri_or_options
|
21
|
-
end
|
22
|
-
|
23
|
-
if uri_or_options.kind_of?(String)
|
24
|
-
uri_or_options = Addressable::URI.parse(uri_or_options)
|
25
|
-
end
|
26
|
-
|
27
|
-
adapter = uri_or_options.delete(:adapter).to_s
|
28
|
-
user = uri_or_options.delete(:username)
|
29
|
-
password = uri_or_options.delete(:password)
|
30
|
-
host = uri_or_options.delete(:host) || "."
|
31
|
-
path = uri_or_options.delete(:path)
|
32
|
-
query = uri_or_options.to_a.map { |pair| pair * '=' } * '&'
|
33
|
-
query = nil if query == ''
|
34
|
-
|
35
|
-
return Addressable::URI.new({:adapter => adapter, :user => user, :password => password, :host => host, :path => path, :query => query})
|
36
|
-
end
|
37
|
-
|
38
20
|
def connection
|
39
|
-
@connection ||= Connection.new(
|
40
|
-
end
|
41
|
-
|
42
|
-
def read_many(query)
|
43
|
-
::DataMapper::Collection.new(query) do |set|
|
44
|
-
read(query) do |result|
|
45
|
-
set.load(result)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def read_one(query)
|
51
|
-
read(query) do |result|
|
52
|
-
return query.model.load(result, query)
|
53
|
-
end
|
21
|
+
@connection ||= Connection.new(options["username"], options["password"], options["path"], options["apidir"])
|
54
22
|
end
|
55
23
|
|
56
24
|
def create(resources)
|
57
25
|
arr = resources.map do |resource|
|
58
|
-
|
26
|
+
make_salesforce_obj(resource, resource.dirty_attributes)
|
59
27
|
end
|
60
28
|
|
61
29
|
result = connection.create(arr)
|
62
30
|
result.each_with_index do |record, i|
|
63
31
|
resource = resources[i]
|
64
|
-
id_field = resource.class.key
|
65
|
-
if id_field
|
32
|
+
if id_field = resource.class.key.find {|p| p.serial?}
|
66
33
|
normalized_value = normalize_id_value(resource.class, id_field, record.id)
|
67
34
|
id_field.set!(resource, normalized_value)
|
68
35
|
end
|
69
36
|
end
|
37
|
+
|
70
38
|
result.size
|
71
|
-
|
72
|
-
|
73
|
-
e
|
39
|
+
|
40
|
+
rescue Connection::SOAPError => e
|
41
|
+
handle_server_outage(e)
|
74
42
|
end
|
75
43
|
|
76
|
-
def update(attributes,
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
read_many(query).map do |obj|
|
81
|
-
obj = make_salesforce_obj(query, attributes, x.key)
|
82
|
-
end
|
83
|
-
end
|
44
|
+
def update(attributes, collection)
|
45
|
+
query = collection.query
|
46
|
+
arr = collection.map { |obj| make_salesforce_obj(query, attributes) }
|
47
|
+
|
84
48
|
connection.update(arr).size
|
85
|
-
|
86
|
-
|
87
|
-
e
|
49
|
+
|
50
|
+
rescue Connection::SOAPError => e
|
51
|
+
handle_server_outage(e)
|
88
52
|
end
|
89
53
|
|
90
|
-
def delete(
|
91
|
-
|
92
|
-
|
93
|
-
else
|
94
|
-
query.read_many.map {|r| r.key}
|
95
|
-
end
|
54
|
+
def delete(collection)
|
55
|
+
query = collection.query
|
56
|
+
keys = collection.map { |r| r.key }.flatten.uniq
|
96
57
|
|
97
58
|
connection.delete(keys).size
|
59
|
+
|
60
|
+
rescue Connection::SOAPError => e
|
61
|
+
handle_server_outage(e)
|
98
62
|
end
|
99
63
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
64
|
+
def handle_server_outage(error)
|
65
|
+
if error.server_unavailable?
|
66
|
+
raise Connection::ServerUnavailable, "The salesforce server is currently unavailable"
|
67
|
+
else
|
68
|
+
raise error
|
69
|
+
end
|
70
|
+
end
|
103
71
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
when "SERVER_UNAVAILABLE"
|
124
|
-
raise Connection::ServerUnavailable, "The salesforce server is currently unavailable"
|
125
|
-
else
|
126
|
-
raise Connection::UnknownStatusCode, "Got an unknown statusCode: #{error.statusCode.inspect}"
|
127
|
-
end
|
72
|
+
# Reading responses back from SELECTS:
|
73
|
+
# In the typical case, response.size reflects the # of records returned.
|
74
|
+
# In the aggregation case, response.size reflects the count.
|
75
|
+
#
|
76
|
+
# Interpretation of this field requires knowledge of whether we
|
77
|
+
# are expecting an aggregate result, thus the response is
|
78
|
+
# processed differently depending on invocation.
|
79
|
+
def read(query)
|
80
|
+
properties = query.fields
|
81
|
+
repository = query.repository
|
82
|
+
|
83
|
+
response = execute_query(query)
|
84
|
+
return [] unless response.records
|
85
|
+
|
86
|
+
rows = response.records.inject([]) do |results, record|
|
87
|
+
results << properties.inject({}) do |result, property|
|
88
|
+
meth = connection.field_name_for(property.model.storage_name(repository.name), property.field)
|
89
|
+
result[property] = normalize_id_value(query.model, property, record.send(meth))
|
90
|
+
result
|
128
91
|
end
|
129
92
|
end
|
130
|
-
end
|
131
93
|
|
132
|
-
|
133
|
-
def destroy_model_storage(*args)
|
134
|
-
true
|
94
|
+
query.model.load(rows, query)
|
135
95
|
end
|
136
96
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
97
|
+
# http://www.salesforce.com/us/developer/docs/api90/Content/sforce_api_calls_soql.htm
|
98
|
+
# SOQL doesn't support anything but count(), so we catch it here
|
99
|
+
# and interpret the result.
|
100
|
+
def aggregate(query)
|
101
|
+
query.fields.each do |f|
|
102
|
+
unless f.target == :all && f.operator == :count
|
103
|
+
raise ArgumentError, %{Aggregate function #{f.operator} not supported in SOQL}
|
104
|
+
end
|
105
|
+
end
|
141
106
|
|
142
|
-
|
143
|
-
def create_model_storage(*args)
|
144
|
-
true
|
107
|
+
[ execute_query(query).size ]
|
145
108
|
end
|
146
109
|
|
147
110
|
private
|
148
|
-
def
|
111
|
+
def execute_query(query)
|
149
112
|
repository = query.repository
|
150
|
-
|
151
|
-
|
152
|
-
|
113
|
+
conditions = query.conditions.map {|c| from_condition(c, repository)}.compact.join(") AND (")
|
114
|
+
|
115
|
+
fields = query.fields.map do |f|
|
116
|
+
case f
|
117
|
+
when DataMapper::Property
|
118
|
+
f.field
|
119
|
+
when DataMapper::Query::Operator
|
120
|
+
%{#{f.operator}()}
|
121
|
+
else
|
122
|
+
raise ArgumentError, "Unknown query field #{f.class}: #{f.inspect}"
|
123
|
+
end
|
124
|
+
end.join(", ")
|
153
125
|
|
154
|
-
sql = "SELECT #{
|
126
|
+
sql = "SELECT #{fields} from #{query.model.storage_name(repository.name)}"
|
155
127
|
sql << " WHERE (#{conditions})" unless conditions.empty?
|
156
|
-
sql << " ORDER BY #{
|
128
|
+
sql << " ORDER BY #{order(query.order[0])}" unless query.order.empty?
|
157
129
|
sql << " LIMIT #{query.limit}" if query.limit
|
158
130
|
|
159
|
-
DataMapper.logger.debug sql
|
131
|
+
DataMapper.logger.debug sql if DataMapper.logger
|
160
132
|
|
161
|
-
|
133
|
+
connection.query(sql)
|
134
|
+
end
|
162
135
|
|
163
|
-
|
136
|
+
def make_salesforce_obj(from, with_attrs)
|
137
|
+
klass_name = from.model.storage_name(from.repository.name)
|
138
|
+
values = {}
|
164
139
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
accum[idx] = normalize_id_value(query.model, property, record.send(meth))
|
170
|
-
end
|
171
|
-
yield accum
|
140
|
+
# FIXME: query.conditions is potentially a tree now
|
141
|
+
if from.is_a?(::DataMapper::Query)
|
142
|
+
key_value = from.conditions.find { |c| c.subject.key? }.value
|
143
|
+
values["id"] = normalize_id_value(from.model, from.model.properties[:id], key_value)
|
172
144
|
end
|
173
|
-
end
|
174
145
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
if key
|
179
|
-
key_value = query.conditions.find {|op,prop,val| prop.key?}.last
|
180
|
-
values["id"] = normalize_id_value(query.model, query.model.properties[:id], key_value)
|
146
|
+
with_attrs.each do |property, value|
|
147
|
+
next if property.serial? || property.key? and value.nil?
|
148
|
+
values[property.field] = normalize_id_value(from.model, property, value)
|
181
149
|
end
|
182
150
|
|
183
|
-
attrs.each do |property,value|
|
184
|
-
normalized_value = normalize_id_value(query.model, property, value)
|
185
|
-
values[property.field(query.repository.name)] = normalized_value
|
186
|
-
end
|
187
151
|
connection.make_object(klass_name, values)
|
188
152
|
end
|
189
153
|
|
190
154
|
def normalize_id_value(klass, property, value)
|
191
|
-
return
|
192
|
-
|
193
|
-
|
194
|
-
return value[0..14] if properties.include?(property.name)
|
195
|
-
end
|
196
|
-
value
|
155
|
+
return nil unless value
|
156
|
+
properties = Array(klass.send(:salesforce_id_properties)).map { |p| p.to_sym } rescue []
|
157
|
+
return properties.include?(property.name) ? value[0..14] : value
|
197
158
|
end
|
198
159
|
end
|
199
160
|
end
|
@@ -1,9 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'soap/header/simplehandler'
|
3
|
-
require "rexml/element"
|
4
|
-
require 'dm-salesforce/soap_wrapper'
|
5
|
-
|
6
|
-
module DataMapperSalesforce
|
1
|
+
module DataMapper::Salesforce
|
7
2
|
class Connection
|
8
3
|
class HeaderHandler < SOAP::Header::SimpleHandler
|
9
4
|
def initialize(tag, value)
|
@@ -16,7 +11,7 @@ module DataMapperSalesforce
|
|
16
11
|
end
|
17
12
|
end
|
18
13
|
|
19
|
-
def initialize(username, password, wsdl_path, organization_id = nil)
|
14
|
+
def initialize(username, password, wsdl_path, api_dir, organization_id = nil)
|
20
15
|
@wrapper = SoapWrapper.new("SalesforceAPI", "Soap", wsdl_path, api_dir)
|
21
16
|
@username, @password, @organization_id = URI.unescape(username), password, organization_id
|
22
17
|
login
|
@@ -27,14 +22,17 @@ module DataMapperSalesforce
|
|
27
22
|
@wrapper.wsdl_path
|
28
23
|
end
|
29
24
|
|
25
|
+
def api_dir
|
26
|
+
@wrapper.api_dir
|
27
|
+
end
|
28
|
+
|
30
29
|
def organization_id
|
31
30
|
@user_details && @user_details.organizationId
|
32
31
|
end
|
33
32
|
|
34
33
|
def make_object(klass_name, values)
|
35
|
-
|
36
|
-
|
37
|
-
values.each do |property,value|
|
34
|
+
obj = SalesforceAPI.const_get(klass_name).new
|
35
|
+
values.each do |property, value|
|
38
36
|
field = field_name_for(klass_name, property)
|
39
37
|
if value.nil? or value == ""
|
40
38
|
obj.fieldsToNull.push(field)
|
@@ -81,9 +79,6 @@ module DataMapperSalesforce
|
|
81
79
|
end
|
82
80
|
|
83
81
|
private
|
84
|
-
def api_dir
|
85
|
-
ENV["SALESFORCE_DIR"] || "#{ENV["HOME"]}/.salesforce"
|
86
|
-
end
|
87
82
|
|
88
83
|
def driver
|
89
84
|
@wrapper.driver
|
@@ -98,7 +93,7 @@ module DataMapperSalesforce
|
|
98
93
|
begin
|
99
94
|
result = driver.login(:username => @username, :password => @password).result
|
100
95
|
rescue SOAP::FaultError => error
|
101
|
-
if error.faultcode.to_obj == "sf:INVALID_LOGIN"
|
96
|
+
if error.faultcode.to_obj == "sf:INVALID_LOGIN" || error.faultcode.to_obj == "INVALID_LOGIN"
|
102
97
|
raise LoginFailed, "Could not login to Salesforce; #{error.faultstring.text}"
|
103
98
|
else
|
104
99
|
raise
|
@@ -141,5 +136,3 @@ module DataMapperSalesforce
|
|
141
136
|
end
|
142
137
|
end
|
143
138
|
end
|
144
|
-
|
145
|
-
require 'dm-salesforce/connection/errors'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module DataMapper::Salesforce
|
2
2
|
class Connection
|
3
3
|
class Error < StandardError; end
|
4
4
|
class FieldNotFound < Error; end
|
@@ -35,6 +35,12 @@ module DataMapperSalesforce
|
|
35
35
|
def message_for_record(record)
|
36
36
|
record.errors.map {|e| "#{e.statusCode}: #{e.message}"}.join(", ")
|
37
37
|
end
|
38
|
+
|
39
|
+
def server_unavailable?
|
40
|
+
failed_records.any? do |record|
|
41
|
+
record.errors.any? {|e| e.statusCode == "SERVER_UNAVAILABLE"}
|
42
|
+
end
|
43
|
+
end
|
38
44
|
end
|
39
45
|
class CreateError < SOAPError; end
|
40
46
|
class QueryError < SOAPError; end
|
@@ -1,15 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module DataMapperSalesforce
|
1
|
+
module DataMapper::Salesforce
|
4
2
|
class SoapWrapper
|
5
3
|
class ClassesFailedToGenerate < StandardError; end
|
6
4
|
|
5
|
+
attr_reader :module_name, :driver_name, :wsdl_path, :api_dir
|
6
|
+
|
7
7
|
def initialize(module_name, driver_name, wsdl_path, api_dir)
|
8
8
|
@module_name, @driver_name, @wsdl_path, @api_dir = module_name, driver_name, File.expand_path(wsdl_path), File.expand_path(api_dir)
|
9
9
|
generate_soap_classes
|
10
10
|
driver
|
11
11
|
end
|
12
|
-
attr_reader :module_name, :driver_name, :wsdl_path, :api_dir
|
13
12
|
|
14
13
|
def driver
|
15
14
|
@driver ||= Object.const_get(module_name).const_get(driver_name).new
|
@@ -24,31 +23,37 @@ module DataMapperSalesforce
|
|
24
23
|
FileUtils.mkdir_p wsdl_api_dir
|
25
24
|
end
|
26
25
|
|
27
|
-
unless files_exist?
|
28
|
-
soap4r = Gem.loaded_specs['soap4r']
|
29
|
-
wsdl2ruby = File.expand_path(File.join(soap4r.full_gem_path, soap4r.bindir, "wsdl2ruby.rb"))
|
30
|
-
Dir.chdir(wsdl_api_dir) do
|
31
|
-
old_args = ARGV.dup
|
32
|
-
ARGV.replace %W(--wsdl #{wsdl_path} --module_path #{module_name} --classdef #{module_name} --type client)
|
33
|
-
load wsdl2ruby
|
34
|
-
ARGV.replace old_args
|
35
|
-
(Dir["*.rb"] - files).each do |filename|
|
36
|
-
FileUtils.rm(filename)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
26
|
+
generate_files unless files_exist?
|
40
27
|
|
41
28
|
$:.push wsdl_api_dir
|
42
29
|
require "#{module_name}Driver"
|
43
30
|
$:.delete wsdl_api_dir
|
44
31
|
end
|
45
32
|
|
46
|
-
|
47
|
-
|
33
|
+
# Good candidate for shipping out into a Rakefile.
|
34
|
+
def generate_files
|
35
|
+
require 'wsdl/soap/wsdl2ruby'
|
36
|
+
|
37
|
+
wsdl2ruby = WSDL::SOAP::WSDL2Ruby.new
|
38
|
+
wsdl2ruby.logger = $LOG if $LOG
|
39
|
+
wsdl2ruby.location = wsdl_path
|
40
|
+
wsdl2ruby.basedir = wsdl_api_dir
|
41
|
+
|
42
|
+
wsdl2ruby.opt.merge!({
|
43
|
+
'classdef' => module_name,
|
44
|
+
'module_path' => module_name,
|
45
|
+
'mapping_registry' => nil,
|
46
|
+
'driver' => nil,
|
47
|
+
'client_skelton' => nil,
|
48
|
+
})
|
49
|
+
|
50
|
+
wsdl2ruby.run
|
51
|
+
|
52
|
+
raise ClassesFailedToGenerate unless files_exist?
|
48
53
|
end
|
49
54
|
|
50
55
|
def files_exist?
|
51
|
-
|
56
|
+
["#{module_name}.rb", "#{module_name}MappingRegistry.rb", "#{module_name}Driver.rb"].all? do |name|
|
52
57
|
File.exist?("#{wsdl_api_dir}/#{name}")
|
53
58
|
end
|
54
59
|
end
|
data/lib/dm-salesforce/sql.rb
CHANGED
@@ -1,59 +1,87 @@
|
|
1
|
-
module
|
1
|
+
module DataMapper::Salesforce
|
2
2
|
module SQL
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
else raise "CAN HAS CRASH?"
|
16
|
-
end
|
17
|
-
case prop
|
18
|
-
when DataMapper::Property
|
19
|
-
"#{prop.field} #{operator}"
|
20
|
-
when DataMapper::Query::Path
|
21
|
-
rels = prop.relationships
|
22
|
-
names = rels.map {|r| storage_name(r, repository) }.join(".")
|
23
|
-
"#{names}.#{prop.field} #{operator}"
|
24
|
-
end
|
25
|
-
end
|
3
|
+
def from_condition(condition, repository)
|
4
|
+
slug = condition.class.slug
|
5
|
+
condition = case condition
|
6
|
+
when DataMapper::Query::Conditions::AbstractOperation then condition.operands.first
|
7
|
+
when DataMapper::Query::Conditions::AbstractComparison
|
8
|
+
if condition.subject.kind_of?(DataMapper::Associations::Relationship)
|
9
|
+
foreign_key_conditions(condition)
|
10
|
+
else
|
11
|
+
condition
|
12
|
+
end
|
13
|
+
else raise("Unkown condition type #{condition.class}: #{condition.inspect}")
|
14
|
+
end
|
26
15
|
|
27
|
-
|
28
|
-
|
16
|
+
value = condition.value
|
17
|
+
prop = condition.subject
|
18
|
+
operator = case slug
|
19
|
+
when String then operator
|
20
|
+
when :eql, :in then equality_operator(value)
|
21
|
+
when :not then inequality_operator(value)
|
22
|
+
when :like then "LIKE #{quote_value(value)}"
|
23
|
+
when :gt then "> #{quote_value(value)}"
|
24
|
+
when :gte then ">= #{quote_value(value)}"
|
25
|
+
when :lt then "< #{quote_value(value)}"
|
26
|
+
when :lte then "<= #{quote_value(value)}"
|
27
|
+
else raise "CAN HAS CRASH?"
|
28
|
+
end
|
29
|
+
case prop
|
30
|
+
when DataMapper::Property
|
31
|
+
"#{prop.field} #{operator}"
|
32
|
+
when DataMapper::Query::Path
|
33
|
+
rels = prop.relationships
|
34
|
+
names = rels.map {|r| storage_name(r, repository) }.join(".")
|
35
|
+
"#{names}.#{prop.field} #{operator}"
|
29
36
|
end
|
37
|
+
end
|
30
38
|
|
31
|
-
|
32
|
-
|
39
|
+
def foreign_key_conditions(condition)
|
40
|
+
subject = case condition.subject
|
41
|
+
when DataMapper::Associations::ManyToOne::Relationship
|
42
|
+
condition.subject.child_key.first
|
43
|
+
else
|
44
|
+
condition.subject.parent_key.first
|
45
|
+
end
|
46
|
+
|
47
|
+
case condition.value
|
48
|
+
when Array, DataMapper::Collection
|
49
|
+
value = condition.send(:expected).flatten
|
50
|
+
DataMapper::Query::Conditions::InclusionComparison.new(subject, value)
|
51
|
+
else
|
52
|
+
value = condition.send(:expected)
|
53
|
+
DataMapper::Query::Conditions::EqualToComparison.new(subject, value)
|
33
54
|
end
|
55
|
+
end
|
34
56
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
57
|
+
def storage_name(rel, repository)
|
58
|
+
rel.parent_model.storage_name(repository.name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def order(direction)
|
62
|
+
"#{direction.target.field} #{direction.operator.to_s.upcase}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def equality_operator(value)
|
66
|
+
case value
|
67
|
+
when Array then "IN #{quote_value(value)}"
|
68
|
+
else "= #{quote_value(value)}"
|
41
69
|
end
|
70
|
+
end
|
42
71
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
72
|
+
def inequality_operator(value)
|
73
|
+
case value
|
74
|
+
when Array then "NOT IN #{quote_value(value)}"
|
75
|
+
else "!= #{quote_value(value)}"
|
48
76
|
end
|
77
|
+
end
|
49
78
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
79
|
+
def quote_value(value)
|
80
|
+
case value
|
81
|
+
when Array then "(#{value.map {|v| quote_value(v)}.join(", ")})"
|
82
|
+
when NilClass then "NULL"
|
83
|
+
when String then "'#{value.gsub(/'/, "\\'").gsub(/\\/, %{\\\\})}'"
|
84
|
+
else "#{value}"
|
57
85
|
end
|
58
86
|
end
|
59
87
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module DataMapper::Salesforce
|
2
|
+
class Type < ::DataMapper::Type
|
3
|
+
end
|
4
|
+
|
5
|
+
module Types
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
dir = File.expand_path(File.dirname(__FILE__) / :types)
|
10
|
+
|
11
|
+
require dir / :serial
|
12
|
+
require dir / :boolean
|
13
|
+
require dir / :integer
|
14
|
+
require dir / :float
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DataMapper::Salesforce
|
2
|
+
module Types
|
3
|
+
class Boolean < Type
|
4
|
+
primitive String
|
5
|
+
default false
|
6
|
+
|
7
|
+
def self.dump(value, property)
|
8
|
+
case value
|
9
|
+
when nil, false then '0'
|
10
|
+
else value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load(value, property)
|
15
|
+
case value
|
16
|
+
when TrueClass then value
|
17
|
+
when '1', 'true' then true
|
18
|
+
else false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
module
|
2
|
-
VERSION = "0.
|
1
|
+
module DataMapper::Salesforce
|
2
|
+
VERSION = "0.10.1"
|
3
3
|
end
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dm-salesforce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yehuda Katz
|
8
8
|
- Tim Carey-Smith
|
9
|
+
- Corey Donohoe
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
13
|
|
13
|
-
date: 2009-
|
14
|
+
date: 2009-12-17 00:00:00 -08:00
|
14
15
|
default_executable:
|
15
16
|
dependencies:
|
16
17
|
- !ruby/object:Gem::Dependency
|
@@ -41,7 +42,7 @@ dependencies:
|
|
41
42
|
requirements:
|
42
43
|
- - ~>
|
43
44
|
- !ruby/object:Gem::Version
|
44
|
-
version: 0.
|
45
|
+
version: 0.10.1
|
45
46
|
version:
|
46
47
|
- !ruby/object:Gem::Dependency
|
47
48
|
name: dm-validations
|
@@ -51,40 +52,40 @@ dependencies:
|
|
51
52
|
requirements:
|
52
53
|
- - ~>
|
53
54
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
55
|
+
version: 0.10.1
|
55
56
|
version:
|
56
57
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
58
|
+
name: dm-types
|
58
59
|
type: :runtime
|
59
60
|
version_requirement:
|
60
61
|
version_requirements: !ruby/object:Gem::Requirement
|
61
62
|
requirements:
|
62
63
|
- - ~>
|
63
64
|
- !ruby/object:Gem::Version
|
64
|
-
version:
|
65
|
+
version: 0.10.1
|
65
66
|
version:
|
66
67
|
- !ruby/object:Gem::Dependency
|
67
|
-
name:
|
68
|
+
name: do_sqlite3
|
68
69
|
type: :runtime
|
69
70
|
version_requirement:
|
70
71
|
version_requirements: !ruby/object:Gem::Requirement
|
71
72
|
requirements:
|
72
73
|
- - ~>
|
73
74
|
- !ruby/object:Gem::Version
|
74
|
-
version: 0.
|
75
|
+
version: 0.10.0
|
75
76
|
version:
|
76
77
|
- !ruby/object:Gem::Dependency
|
77
|
-
name:
|
78
|
+
name: soap4r
|
78
79
|
type: :runtime
|
79
80
|
version_requirement:
|
80
81
|
version_requirements: !ruby/object:Gem::Requirement
|
81
82
|
requirements:
|
82
83
|
- - ~>
|
83
84
|
- !ruby/object:Gem::Version
|
84
|
-
version:
|
85
|
+
version: 1.5.8
|
85
86
|
version:
|
86
87
|
description: A DataMapper adapter to the Salesforce API
|
87
|
-
email:
|
88
|
+
email: ninja@engineyard.com
|
88
89
|
executables: []
|
89
90
|
|
90
91
|
extensions: []
|
@@ -99,13 +100,18 @@ files:
|
|
99
100
|
- lib/dm-salesforce/adapter.rb
|
100
101
|
- lib/dm-salesforce/connection/errors.rb
|
101
102
|
- lib/dm-salesforce/connection.rb
|
102
|
-
- lib/dm-salesforce/
|
103
|
+
- lib/dm-salesforce/resource.rb
|
103
104
|
- lib/dm-salesforce/soap_wrapper.rb
|
104
105
|
- lib/dm-salesforce/sql.rb
|
106
|
+
- lib/dm-salesforce/types/boolean.rb
|
107
|
+
- lib/dm-salesforce/types/float.rb
|
108
|
+
- lib/dm-salesforce/types/integer.rb
|
109
|
+
- lib/dm-salesforce/types/serial.rb
|
110
|
+
- lib/dm-salesforce/types.rb
|
105
111
|
- lib/dm-salesforce/version.rb
|
106
112
|
- lib/dm-salesforce.rb
|
107
113
|
has_rdoc: true
|
108
|
-
homepage: http://
|
114
|
+
homepage: http://github.com/halorgium/dm-salesforce
|
109
115
|
licenses: []
|
110
116
|
|
111
117
|
post_install_message:
|
@@ -1,45 +0,0 @@
|
|
1
|
-
module DataMapperSalesforce
|
2
|
-
module SalesforceExtensions
|
3
|
-
def self.included(mod)
|
4
|
-
mod.extend(ClassMethods)
|
5
|
-
end
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
def properties_with_salesforce_validation
|
9
|
-
@properties_with_salesforce_validation ||= []
|
10
|
-
end
|
11
|
-
|
12
|
-
def add_salesforce_validation_for(property)
|
13
|
-
unless properties_with_salesforce_validation.include?(property)
|
14
|
-
validates_with_block property.name do
|
15
|
-
if message = salesforce_errors[property]
|
16
|
-
[false, message]
|
17
|
-
else
|
18
|
-
true
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
properties_with_salesforce_validation << property
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def add_salesforce_error_for(field, message)
|
27
|
-
if property = property_for_salesforce_field(field)
|
28
|
-
self.class.add_salesforce_validation_for(property)
|
29
|
-
salesforce_errors[property] = message
|
30
|
-
else
|
31
|
-
raise "Field not found"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def property_for_salesforce_field(name)
|
36
|
-
self.class.properties.find do |p|
|
37
|
-
p.field.downcase == name.downcase
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def salesforce_errors
|
42
|
-
@salesforce_errors ||= {}
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|