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 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 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.
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, String, :serial => true
25
- property :name, String
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
- def self.salesforce_id_properties
36
- [:id, :account_id]
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, String
42
- property :email, String
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 Engine Yard Employees who helped
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 = DataMapperSalesforce::VERSION
13
- AUTHORS = ["Yehuda Katz", 'Tim Carey-Smith']
14
- EMAIL = "wycats@gmail.com"
15
- HOMEPAGE = "http://www.yehudakatz.com"
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 %{sudo gem install pkg/#{GEM}-#{GEM_VERSION} --no-ri --no-rdoc}
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 = %w(adapter connection models).collect { |dir| Dir["spec/#{dir}/**/*_spec.rb"] }.flatten
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' << '~/.salesforce,gems,spec,config,tmp'
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
- $:.push File.expand_path(File.dirname(__FILE__))
2
-
1
+ require "fileutils"
3
2
  require 'dm-core'
4
3
  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
4
+ require 'soap/wsdlDriver'
5
+ require 'soap/header/simplehandler'
6
+ require "rexml/element"
12
7
 
13
- module DataMapperSalesforce
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 DataMapperSalesforce
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(@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
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
- obj = make_salesforce_obj(resource, resource.dirty_attributes, nil)
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(resource.repository.name).find {|p| p.serial?}
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
- rescue Connection::CreateError => e
72
- populate_errors_for(e.records, resources)
73
- e.successful_records.size
39
+
40
+ rescue Connection::SOAPError => e
41
+ handle_server_outage(e)
74
42
  end
75
43
 
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
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
- rescue Connection::UpdateError => e
86
- populate_errors_for(e.records, arr, query)
87
- e.successful_records.size
49
+
50
+ rescue Connection::SOAPError => e
51
+ handle_server_outage(e)
88
52
  end
89
53
 
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
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 populate_errors_for(records, resources, query = nil)
101
- records.each_with_index do |record,i|
102
- next if record.success
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
- 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
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
- # A dummy method to allow migrations without upsetting any data
133
- def destroy_model_storage(*args)
134
- true
94
+ query.model.load(rows, query)
135
95
  end
136
96
 
137
- # A dummy method to allow auto_migrate! to run
138
- def upgrade_model_storage(*args)
139
- true
140
- end
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
- # A dummy method to allow migrations without upsetting any data
143
- def create_model_storage(*args)
144
- true
107
+ [ execute_query(query).size ]
145
108
  end
146
109
 
147
110
  private
148
- def read(query, &block)
111
+ def execute_query(query)
149
112
  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 (")
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 #{query.fields.map {|f| f.field(repository.name)}.join(", ")} from #{query.model.storage_name(repository.name)}"
126
+ sql = "SELECT #{fields} from #{query.model.storage_name(repository.name)}"
155
127
  sql << " WHERE (#{conditions})" unless conditions.empty?
156
- sql << " ORDER BY #{SQL.order(query.order[0])}" unless query.order.empty?
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
- result = connection.query(sql)
133
+ connection.query(sql)
134
+ end
162
135
 
163
- return unless result.records
136
+ def make_salesforce_obj(from, with_attrs)
137
+ klass_name = from.model.storage_name(from.repository.name)
138
+ values = {}
164
139
 
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
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
- 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)
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 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
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
- require 'soap/wsdlDriver'
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
- klass = SalesforceAPI.const_get(klass_name)
36
- obj = klass.new
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 DataMapperSalesforce
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
@@ -0,0 +1,8 @@
1
+ module DataMapper::Salesforce
2
+ module Resource
3
+ def self.included(model)
4
+ model.send :include, DataMapper::Resource
5
+ model.send :include, DataMapper::Salesforce::Types
6
+ end
7
+ end
8
+ end
@@ -1,15 +1,14 @@
1
- require "fileutils"
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
- def files
47
- ["#{module_name}.rb", "#{module_name}MappingRegistry.rb", "#{module_name}Driver.rb"]
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
- files.all? do |name|
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
@@ -1,59 +1,87 @@
1
- module DataMapperSalesforce
1
+ module DataMapper::Salesforce
2
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
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
- def storage_name(rel, repository)
28
- rel.parent_model.storage_name(repository.name)
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
- def order(direction)
32
- "#{direction.property.field} #{direction.direction.to_s.upcase}"
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
- private
36
- def equality_operator(value)
37
- case value
38
- when Array then "IN #{quote_value(value)}"
39
- else "= #{quote_value(value)}"
40
- end
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
- def inequality_operator(value)
44
- case value
45
- when Array then "NOT IN #{quote_value(value)}"
46
- else "!= #{quote_value(value)}"
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
- 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
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
@@ -0,0 +1,8 @@
1
+ module DataMapper::Salesforce
2
+ module Types
3
+ class Float < Type
4
+ primitive ::Float
5
+ default 0.0
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ module DataMapper::Salesforce
2
+ module Types
3
+ class Integer < Type
4
+ primitive ::Integer
5
+ default 0
6
+
7
+ def self.load(value, property)
8
+ Integer(value)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module DataMapper::Salesforce
2
+ module Types
3
+ class Serial < Type
4
+ primitive ::String
5
+ min 15
6
+ max 15
7
+ serial true
8
+
9
+ def self.dump(value, property)
10
+ value[0..14] unless value.blank?
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
- module DataMapperSalesforce
2
- VERSION = "0.9.12"
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.9.12
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-09-10 00:00:00 -07:00
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.9.8
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.9.8
55
+ version: 0.10.1
55
56
  version:
56
57
  - !ruby/object:Gem::Dependency
57
- name: soap4r
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: 1.5.8
65
+ version: 0.10.1
65
66
  version:
66
67
  - !ruby/object:Gem::Dependency
67
- name: data_objects
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.9.9
75
+ version: 0.10.0
75
76
  version:
76
77
  - !ruby/object:Gem::Dependency
77
- name: do_sqlite3
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: 0.9.9
85
+ version: 1.5.8
85
86
  version:
86
87
  description: A DataMapper adapter to the Salesforce API
87
- email: wycats@gmail.com
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/extensions.rb
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://www.yehudakatz.com
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