dm-salesforce 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
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
+