dm-salesforce 0.9.10

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Yehuda Katz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,80 @@
1
+ dm-salesforce
2
+ =============
3
+
4
+ A gem that provides a Salesforce Adapter for DataMapper.
5
+
6
+ The wsdl is converted into Ruby classes and stored in ~/.salesforce. This automatically
7
+ happens the first time you use the salesforce adapter, so you don't need to worry about
8
+ generating Ruby code. It just works if you have the wsdl, directions for getting going
9
+ are outlined below.
10
+
11
+ An example of using the adapter:
12
+
13
+ class Account
14
+ include DataMapper::Resource
15
+
16
+ def self.default_repository_name
17
+ :salesforce
18
+ end
19
+
20
+ def self.salesforce_id_properties
21
+ :id
22
+ end
23
+
24
+ property :id, String, :serial => true
25
+ property :name, String
26
+ end
27
+
28
+ class Contact
29
+ include DataMapper::Resource
30
+
31
+ def self.default_repository_name
32
+ :salesforce
33
+ end
34
+
35
+ def self.salesforce_id_properties
36
+ [:id, :account_id]
37
+ end
38
+
39
+ property :id, String, :serial => true
40
+ property :first_name, String
41
+ property :last_name, String
42
+ property :email, String
43
+ property :account_id, String
44
+
45
+ belongs_to :account
46
+ end
47
+
48
+
49
+ To get a test environment going with the free development tools you'll need to follow these steps.
50
+
51
+
52
+ * Get a developer account from http://force.salesforce.com
53
+ * Hit up https://login.salesforce.com, and login with the password they provided in your signup email
54
+ * Remember the password they force you to reset
55
+ * Grab the following from Salesforce's web UI
56
+ * Your Enterprise API WSDL [Click Setup][setup] and [Expand and Save As][getwsdl]
57
+ * Your API Token [Reset if needed][gettoken]
58
+ * Copy the WSDL file you downloaded to config/wsdl.xml
59
+ * Copy and modify config/database.rb-example to use your info. In this case my password is 'skateboards' and my API key is 'f938915c9cdc36ff5498881b':
60
+
61
+ DataMapper.setup(:salesforce, {:adapter => 'salesforce',
62
+ :username => 'salesforceuser@mydomain.com',
63
+ :password => 'skateboardsf938915c9cdc36ff5498881b',
64
+ :path => File.expand_path(File.dirname(__FILE__)+'/wsdl.xml'),
65
+ :host => ''})
66
+
67
+ VALID_USER = DataMapperSalesforce::UserDetails.new('salesforceuser@mydomain.com', 'skateboardsf938915c9cdc36ff5498881b')
68
+ VALID_SELF_SERVICE_USER = DataMapperSalesforce::UserDetails.new("quentin@example.com", "foo")
69
+ * Run 'bin/irb' and you should have access to the Account and Contact models
70
+
71
+ Special Thanks to Engine Yard Employees who helped
72
+ ==================================================
73
+ * Corey Donohoe
74
+ * Andy Delcambre
75
+ * Ben Burkert
76
+ * Larry Diehl
77
+
78
+ [setup]: http://img.skitch.com/20090204-gaxdfxbi1emfita5dax48ids4m.jpg "Click on Setup"
79
+ [getwsdl]: http://img.skitch.com/20090204-nhurnuxwf5g3ufnjk2xkfjc5n4.jpg "Expand and Save"
80
+ [gettoken]: http://img.skitch.com/20090204-mnt182ce7bc4seecqbrjjxjbef.jpg "You can reset your token here"
data/Rakefile ADDED
@@ -0,0 +1,101 @@
1
+ require File.dirname(__FILE__) + '/vendor/gems/environments/default'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+
6
+ require File.dirname(__FILE__) + '/lib/dm-salesforce'
7
+ require 'bundler'
8
+
9
+ GEM = "dm-salesforce"
10
+ GEM_VERSION = DataMapperSalesforce::VERSION
11
+ AUTHORS = ["Yehuda Katz", 'Tim Carey-Smith']
12
+ EMAIL = "wycats@gmail.com"
13
+ HOMEPAGE = "http://www.yehudakatz.com"
14
+ SUMMARY = "A DataMapper adapter to the Salesforce API"
15
+
16
+ @spec = Gem::Specification.new do |s|
17
+ s.name = GEM
18
+ s.version = GEM_VERSION
19
+ s.platform = Gem::Platform::RUBY
20
+ s.has_rdoc = true
21
+ s.extra_rdoc_files = ["README.markdown", "LICENSE"]
22
+ s.summary = SUMMARY
23
+ s.description = s.summary
24
+ s.authors = AUTHORS
25
+ s.email = EMAIL
26
+ s.homepage = HOMEPAGE
27
+
28
+ manifest = Bundler::ManifestFile.load(File.dirname(__FILE__) + '/Gemfile')
29
+ manifest.dependencies.each do |d|
30
+ next unless d.in?(:release)
31
+ s.add_dependency(d.name, d.version)
32
+ end
33
+
34
+ s.require_path = 'lib'
35
+ s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("lib/**/*")
36
+ end
37
+
38
+ Rake::GemPackageTask.new(@spec) do |pkg|
39
+ pkg.gem_spec = @spec
40
+ end
41
+
42
+ desc "install the gem locally"
43
+ task :install => [:package] do
44
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION} --no-ri --no-rdoc}
45
+ end
46
+
47
+ task :default => 'spec'
48
+ require 'spec'
49
+ require 'spec/rake/spectask'
50
+ desc "Run specs"
51
+ Spec::Rake::SpecTask.new(:spec) do |t|
52
+ t.spec_opts << %w(-fs --color) << %w(-O spec/spec.opts)
53
+ t.spec_opts << '--loadby' << 'random'
54
+ t.spec_files = %w(adapter connection models).collect { |dir| Dir["spec/#{dir}/**/*_spec.rb"] }.flatten
55
+ t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
56
+ t.rcov_opts << '--exclude' << '~/.salesforce,gems,spec,config,tmp'
57
+ t.rcov_opts << '--text-summary'
58
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
59
+ end
60
+
61
+ desc "Release the version"
62
+ task :release => :repackage do
63
+ version = DataMapperSalesforce::VERSION
64
+ puts "Releasing #{version}"
65
+
66
+ `git show-ref tags/v#{version}`
67
+ unless $?.success?
68
+ abort "There is no tag for v#{version}"
69
+ end
70
+
71
+ `git show-ref heads/releasing`
72
+ if $?.success?
73
+ abort "Remove the releasing branch, we need it!"
74
+ end
75
+
76
+ puts "Checking out to the releasing branch as the tag"
77
+ system("git", "checkout", "-b", "releasing", "tags/v#{version}")
78
+
79
+ puts "Reseting back to master"
80
+ system("git", "checkout", "master")
81
+ system("git", "branch", "-d", "releasing")
82
+
83
+ current = @spec.version.to_s + ".0"
84
+ next_version = Gem::Version.new(current).bump
85
+
86
+ puts "Changing the version to #{next_version}."
87
+
88
+ version_file = File.dirname(__FILE__)+"/lib/#{GEM}/version.rb"
89
+ File.open(version_file, "w") do |f|
90
+ f.puts <<-EOT
91
+ module DataMapperSalesforce
92
+ VERSION = "#{next_version}"
93
+ end
94
+ EOT
95
+ end
96
+
97
+ puts "Committing the version change"
98
+ system("git", "commit", version_file, "-m", "Next version: #{next_version}")
99
+
100
+ puts "Push the commit up! if you don't, you'll be hunted down"
101
+ end
@@ -0,0 +1,15 @@
1
+ $:.push File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'dm-core'
4
+ require 'dm-validations'
5
+ require 'dm-salesforce/sql'
6
+ require 'dm-salesforce/extensions'
7
+ require 'dm-salesforce/adapter'
8
+ require 'dm-salesforce/connection'
9
+ require 'dm-salesforce/version'
10
+
11
+ DataMapper::Adapters::SalesforceAdapter = DataMapperSalesforce::Adapter
12
+
13
+ module DataMapperSalesforce
14
+ UserDetails = Struct.new(:username, :password)
15
+ end
@@ -0,0 +1,199 @@
1
+ module DataMapperSalesforce
2
+ class Adapter < DataMapper::Adapters::AbstractAdapter
3
+ def initialize(name, uri_or_options)
4
+ super
5
+ @resource_naming_convention = proc do |value|
6
+ klass = Extlib::Inflection.constantize(value)
7
+ if klass.respond_to?(:salesforce_class)
8
+ klass.salesforce_class
9
+ else
10
+ value.split("::").last
11
+ end
12
+ end
13
+ @field_naming_convention = proc do |property|
14
+ connection.field_name_for(property.model.storage_name(name), property.name.to_s)
15
+ end
16
+ end
17
+
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
+ def connection
39
+ @connection ||= Connection.new(@uri.user, @uri.password, @uri.host + @uri.path)
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
54
+ end
55
+
56
+ def create(resources)
57
+ arr = resources.map do |resource|
58
+ obj = make_salesforce_obj(resource, resource.dirty_attributes, nil)
59
+ end
60
+
61
+ result = connection.create(arr)
62
+ result.each_with_index do |record, i|
63
+ resource = resources[i]
64
+ id_field = resource.class.key(resource.repository.name).find {|p| p.serial?}
65
+ if id_field
66
+ normalized_value = normalize_id_value(resource.class, id_field, record.id)
67
+ id_field.set!(resource, normalized_value)
68
+ end
69
+ end
70
+ result.size
71
+ rescue Connection::CreateError => e
72
+ populate_errors_for(e.records, resources)
73
+ e.successful_records.size
74
+ end
75
+
76
+ def update(attributes, query)
77
+ arr = if key_condition = query.conditions.find {|op,prop,val| prop.key?}
78
+ [ make_salesforce_obj(query, attributes, key_condition.last) ]
79
+ else
80
+ read_many(query).map do |obj|
81
+ obj = make_salesforce_obj(query, attributes, x.key)
82
+ end
83
+ end
84
+ connection.update(arr).size
85
+ rescue Connection::UpdateError => e
86
+ populate_errors_for(e.records, arr, query)
87
+ e.successful_records.size
88
+ end
89
+
90
+ def delete(query)
91
+ keys = if key_condition = query.conditions.find {|op,prop,val| prop.key?}
92
+ [key_condition.last]
93
+ else
94
+ query.read_many.map {|r| r.key}
95
+ end
96
+
97
+ connection.delete(keys).size
98
+ end
99
+
100
+ def populate_errors_for(records, resources, query = nil)
101
+ records.each_with_index do |record,i|
102
+ next if record.success
103
+
104
+ if resources[i].is_a?(DataMapper::Resource)
105
+ resource = resources[i]
106
+ elsif resources[i].is_a?(SalesforceAPI::SObject)
107
+ resource = query.repository.identity_map(query.model)[[resources[i].id]]
108
+ else
109
+ resource = query.repository.identity_map(query.model)[[resources[i]]]
110
+ end
111
+
112
+ resource.class.send(:include, SalesforceExtensions)
113
+ record.errors.each do |error|
114
+ case error.statusCode
115
+ when "DUPLICATE_VALUE"
116
+ if error.message =~ /duplicate value found: (.*) duplicates/
117
+ resource.add_salesforce_error_for($1, error.message)
118
+ end
119
+ when "REQUIRED_FIELD_MISSING", "INVALID_EMAIL_ADDRESS"
120
+ error.fields.each do |field|
121
+ resource.add_salesforce_error_for(field, error.message)
122
+ end
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
128
+ end
129
+ end
130
+ end
131
+
132
+ # A dummy method to allow migrations without upsetting any data
133
+ def destroy_model_storage(*args)
134
+ true
135
+ end
136
+
137
+ # A dummy method to allow auto_migrate! to run
138
+ def upgrade_model_storage(*args)
139
+ true
140
+ end
141
+
142
+ # A dummy method to allow migrations without upsetting any data
143
+ def create_model_storage(*args)
144
+ true
145
+ end
146
+
147
+ private
148
+ def read(query, &block)
149
+ repository = query.repository
150
+ properties = query.fields
151
+ properties_with_indexes = Hash[*properties.zip((0...properties.size).to_a).flatten]
152
+ conditions = query.conditions.map {|c| SQL.from_condition(c, repository)}.compact.join(") AND (")
153
+
154
+ sql = "SELECT #{query.fields.map {|f| f.field(repository.name)}.join(", ")} from #{query.model.storage_name(repository.name)}"
155
+ sql << " WHERE (#{conditions})" unless conditions.empty?
156
+ sql << " ORDER BY #{SQL.order(query.order[0])}" unless query.order.empty?
157
+ sql << " LIMIT #{query.limit}" if query.limit
158
+
159
+ DataMapper.logger.debug sql
160
+
161
+ result = connection.query(sql)
162
+
163
+ return unless result.records
164
+
165
+ result.records.each do |record|
166
+ accum = []
167
+ properties_with_indexes.each do |(property, idx)|
168
+ meth = connection.field_name_for(property.model.storage_name(repository.name), property.field(repository.name))
169
+ accum[idx] = normalize_id_value(query.model, property, record.send(meth))
170
+ end
171
+ yield accum
172
+ end
173
+ end
174
+
175
+ def make_salesforce_obj(query, attrs, key)
176
+ klass_name = query.model.storage_name(query.repository.name)
177
+ values = {}
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)
181
+ end
182
+
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
+ connection.make_object(klass_name, values)
188
+ end
189
+
190
+ def normalize_id_value(klass, property, value)
191
+ return value unless value
192
+ if klass.respond_to?(:salesforce_id_properties)
193
+ properties = Array(klass.salesforce_id_properties).map {|p| p.to_sym}
194
+ return value[0..14] if properties.include?(property.name)
195
+ end
196
+ value
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,145 @@
1
+ require 'soap/wsdlDriver'
2
+ require 'soap/header/simplehandler'
3
+ require "rexml/element"
4
+ require 'dm-salesforce/soap_wrapper'
5
+
6
+ module DataMapperSalesforce
7
+ class Connection
8
+ class HeaderHandler < SOAP::Header::SimpleHandler
9
+ def initialize(tag, value)
10
+ super(XSD::QName.new('urn:enterprise.soap.sforce.com', tag))
11
+ @tag = tag
12
+ @value = value
13
+ end
14
+ def on_simple_outbound
15
+ @value
16
+ end
17
+ end
18
+
19
+ def initialize(username, password, wsdl_path, organization_id = nil)
20
+ @wrapper = SoapWrapper.new("SalesforceAPI", "Soap", wsdl_path, api_dir)
21
+ @username, @password, @organization_id = URI.unescape(username), password, organization_id
22
+ login
23
+ end
24
+ attr_reader :user_id, :user_details
25
+
26
+ def wsdl_path
27
+ @wrapper.wsdl_path
28
+ end
29
+
30
+ def organization_id
31
+ @user_details && @user_details.organizationId
32
+ end
33
+
34
+ def make_object(klass_name, values)
35
+ klass = SalesforceAPI.const_get(klass_name)
36
+ obj = klass.new
37
+ values.each do |property,value|
38
+ field = field_name_for(klass_name, property)
39
+ if value.nil? or value == ""
40
+ obj.fieldsToNull.push(field)
41
+ else
42
+ obj.send("#{field}=", value)
43
+ end
44
+ end
45
+ obj
46
+ end
47
+
48
+ def field_name_for(klass_name, column)
49
+ klass = SalesforceAPI.const_get(klass_name)
50
+ fields = [column, column.camel_case, "#{column}__c".downcase]
51
+ options = /^(#{fields.join("|")})$/i
52
+ matches = klass.instance_methods(false).grep(options)
53
+ if matches.any?
54
+ matches.first
55
+ else
56
+ raise FieldNotFound,
57
+ "You specified #{column} as a field, but neither #{fields.join(" or ")} exist. " \
58
+ "Either manually specify the field name with :field, or check to make sure you have " \
59
+ "provided a correct field name."
60
+ end
61
+ end
62
+
63
+ def query(string)
64
+ with_reconnection do
65
+ driver.query(:queryString => string).result
66
+ end
67
+ rescue SOAP::FaultError => e
68
+ raise QueryError.new(e.message, [])
69
+ end
70
+
71
+ def create(objects)
72
+ call_api(:create, CreateError, "creating", objects)
73
+ end
74
+
75
+ def update(objects)
76
+ call_api(:update, UpdateError, "updating", objects)
77
+ end
78
+
79
+ def delete(keys)
80
+ call_api(:delete, DeleteError, "deleting", keys)
81
+ end
82
+
83
+ private
84
+ def api_dir
85
+ ENV["SALESFORCE_DIR"] || "#{ENV["HOME"]}/.salesforce"
86
+ end
87
+
88
+ def driver
89
+ @wrapper.driver
90
+ end
91
+
92
+ def login
93
+ driver
94
+ if @organization_id
95
+ driver.headerhandler << HeaderHandler.new("LoginScopeHeader", :organizationId => @organization_id)
96
+ end
97
+
98
+ begin
99
+ result = driver.login(:username => @username, :password => @password).result
100
+ rescue SOAP::FaultError => error
101
+ if error.faultcode.to_obj == "sf:INVALID_LOGIN"
102
+ raise LoginFailed, "Could not login to Salesforce; #{error.faultstring.text}"
103
+ else
104
+ raise
105
+ end
106
+ end
107
+ driver.endpoint_url = result.serverUrl
108
+ driver.headerhandler << HeaderHandler.new("SessionHeader", "sessionId" => result.sessionId)
109
+ driver.headerhandler << HeaderHandler.new("CallOptions", "client" => "client")
110
+ @user_id = result.userId
111
+ @user_details = result.userInfo
112
+ driver
113
+ end
114
+
115
+ def call_api(method, exception_class, message, args)
116
+ with_reconnection do
117
+ result = driver.send(method, args)
118
+ if result.all? {|r| r.success}
119
+ result
120
+ else
121
+ raise exception_class.new("Got some errors while #{message} Salesforce objects", result)
122
+ end
123
+ end
124
+ end
125
+
126
+ def with_reconnection(&block)
127
+ yield
128
+ rescue SOAP::FaultError => error
129
+ retry_count ||= 0
130
+ if error.faultcode.text == "sf:INVALID_SESSION_ID"
131
+ $stderr.puts "Got a invalid session id; reconnecting"
132
+ @driver = nil
133
+ login
134
+ retry_count += 1
135
+ retry unless retry_count > 5
136
+ else
137
+ raise
138
+ end
139
+
140
+ raise SessionTimeout, "The Salesforce session could not be established"
141
+ end
142
+ end
143
+ end
144
+
145
+ require 'dm-salesforce/connection/errors'
@@ -0,0 +1,44 @@
1
+ module DataMapperSalesforce
2
+ class Connection
3
+ class Error < StandardError; end
4
+ class FieldNotFound < Error; end
5
+ class LoginFailed < Error; end
6
+ class SessionTimeout < Error; end
7
+ class UnknownStatusCode < Error; end
8
+ class ServerUnavailable < Error; end
9
+
10
+
11
+ class SOAPError < Error
12
+ def initialize(message, result)
13
+ @result = result
14
+ super("#{message}: #{result_message}")
15
+ end
16
+
17
+ def records
18
+ @result.to_a
19
+ end
20
+
21
+ def failed_records
22
+ @result.reject {|r| r.success}
23
+ end
24
+
25
+ def successful_records
26
+ @result.select {|r| r.success}
27
+ end
28
+
29
+ def result_message
30
+ failed_records.map do |r|
31
+ message_for_record(r)
32
+ end.join("; ")
33
+ end
34
+
35
+ def message_for_record(record)
36
+ record.errors.map {|e| "#{e.statusCode}: #{e.message}"}.join(", ")
37
+ end
38
+ end
39
+ class CreateError < SOAPError; end
40
+ class QueryError < SOAPError; end
41
+ class DeleteError < SOAPError; end
42
+ class UpdateError < SOAPError; end
43
+ end
44
+ end
@@ -0,0 +1,45 @@
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
@@ -0,0 +1,53 @@
1
+ require "fileutils"
2
+
3
+ module DataMapperSalesforce
4
+ class SoapWrapper
5
+ class ClassesFailedToGenerate < StandardError; end
6
+
7
+ def initialize(module_name, driver_name, wsdl_path, api_dir)
8
+ @module_name, @driver_name, @wsdl_path, @api_dir = module_name, driver_name, File.expand_path(wsdl_path), File.expand_path(api_dir)
9
+ generate_soap_classes
10
+ driver
11
+ end
12
+ attr_reader :module_name, :driver_name, :wsdl_path, :api_dir
13
+
14
+ def driver
15
+ @driver ||= Object.const_get(module_name).const_get(driver_name).new
16
+ end
17
+
18
+ def generate_soap_classes
19
+ unless File.file?(wsdl_path)
20
+ raise Errno::ENOENT, "Could not find the WSDL at #{wsdl_path}"
21
+ end
22
+
23
+ unless File.directory?(wsdl_api_dir)
24
+ FileUtils.mkdir_p wsdl_api_dir
25
+ end
26
+
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
+ FileUtils.rm Dir["*Client.rb"]
36
+ end
37
+ end
38
+
39
+ $:.push wsdl_api_dir
40
+ require "#{module_name}Driver"
41
+ end
42
+
43
+ def files_exist?
44
+ %w( .rb Driver.rb MappingRegistry.rb ).all? do |suffix|
45
+ File.exist?("#{wsdl_api_dir}/#{module_name}#{suffix}")
46
+ end
47
+ end
48
+
49
+ def wsdl_api_dir
50
+ "#{api_dir}/#{File.basename(wsdl_path)}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,60 @@
1
+ module DataMapperSalesforce
2
+ module SQL
3
+ class << self
4
+ def from_condition(condition, repository)
5
+ op, prop, value = condition
6
+ operator = case op
7
+ when String then operator
8
+ when :eql, :in then equality_operator(value)
9
+ when :not then inequality_operator(value)
10
+ when :like then "LIKE #{quote_value(value)}"
11
+ when :gt then "> #{quote_value(value)}"
12
+ when :gte then ">= #{quote_value(value)}"
13
+ when :lt then "< #{quote_value(value)}"
14
+ when :lte then "<= #{quote_value(value)}"
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
26
+
27
+ def storage_name(rel, repository)
28
+ rel.parent_model.storage_name(repository.name)
29
+ end
30
+
31
+ def order(direction)
32
+ "#{direction.property.field} #{direction.direction.to_s.upcase}"
33
+ end
34
+
35
+ private
36
+ def equality_operator(value)
37
+ case value
38
+ when Array then "IN #{quote_value(value)}"
39
+ else "= #{quote_value(value)}"
40
+ end
41
+ end
42
+
43
+ def inequality_operator(value)
44
+ case value
45
+ when Array then "NOT IN #{quote_value(value)}"
46
+ else "!= #{quote_value(value)}"
47
+ end
48
+ end
49
+
50
+ def quote_value(value)
51
+ case value
52
+ when Array then "(#{value.map {|v| quote_value(v)}.join(", ")})"
53
+ when NilClass then "NULL"
54
+ when String then "'#{value.gsub(/'/, "\\'").gsub(/\\/, %{\\\\})}'"
55
+ else "#{value}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module DataMapperSalesforce
2
+ VERSION = "0.9.10"
3
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-salesforce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.10
5
+ platform: ruby
6
+ authors:
7
+ - Yehuda Katz
8
+ - Tim Carey-Smith
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-08-06 00:00:00 -06:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: httpclient
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 2.1.2
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: extlib
28
+ type: :runtime
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 0.9.9
35
+ version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: dm-core
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 0.9.8
45
+ version:
46
+ - !ruby/object:Gem::Dependency
47
+ name: dm-validations
48
+ type: :runtime
49
+ version_requirement:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.8
55
+ version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: soap4r
58
+ type: :runtime
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: 1.5.8
65
+ version:
66
+ - !ruby/object:Gem::Dependency
67
+ name: data_objects
68
+ type: :runtime
69
+ version_requirement:
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ version: 0.9.9
75
+ version:
76
+ - !ruby/object:Gem::Dependency
77
+ name: do_sqlite3
78
+ type: :runtime
79
+ version_requirement:
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ~>
83
+ - !ruby/object:Gem::Version
84
+ version: 0.9.9
85
+ version:
86
+ description: A DataMapper adapter to the Salesforce API
87
+ email: wycats@gmail.com
88
+ executables: []
89
+
90
+ extensions: []
91
+
92
+ extra_rdoc_files:
93
+ - README.markdown
94
+ - LICENSE
95
+ files:
96
+ - LICENSE
97
+ - README.markdown
98
+ - Rakefile
99
+ - lib/dm-salesforce/adapter.rb
100
+ - lib/dm-salesforce/connection/errors.rb
101
+ - lib/dm-salesforce/connection.rb
102
+ - lib/dm-salesforce/extensions.rb
103
+ - lib/dm-salesforce/soap_wrapper.rb
104
+ - lib/dm-salesforce/sql.rb
105
+ - lib/dm-salesforce/version.rb
106
+ - lib/dm-salesforce.rb
107
+ has_rdoc: true
108
+ homepage: http://www.yehudakatz.com
109
+ licenses: []
110
+
111
+ post_install_message:
112
+ rdoc_options: []
113
+
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: "0"
121
+ version:
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: "0"
127
+ version:
128
+ requirements: []
129
+
130
+ rubyforge_project:
131
+ rubygems_version: 1.3.5
132
+ signing_key:
133
+ source:
134
+ specification_version: 3
135
+ summary: A DataMapper adapter to the Salesforce API
136
+ test_files: []
137
+