dm-salesforce 0.9.12 → 0.10.1

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/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