activesalesforce 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -22,3 +22,21 @@
22
22
  =end
23
23
 
24
24
  require 'salesforce_connection_adapter'
25
+
26
+ module ActionView
27
+ module Helpers
28
+ # Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
29
+ # you can use the same format for links in the views that you do in the controller. The different methods are even named
30
+ # synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
31
+ # redirection in redirect_to.
32
+ module UrlHelper
33
+ def link_to_asf(active_record, column)
34
+ if column.reference_to
35
+ link_to(column.reference_to, { :action => 'show', :controller => column.reference_to.pluralize, :id => active_record.send(column.name) } )
36
+ else
37
+ active_record.send(column.name)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -28,12 +28,22 @@ require 'pp'
28
28
 
29
29
 
30
30
  module ActiveRecord
31
+ module StringHelper
32
+ def column_nameize(s)
33
+ #s ? s.first.downcase + s[1 .. s.length] : nil
34
+ s.underscore
35
+ end
36
+ end
37
+
31
38
  module ConnectionAdapters
32
39
  class SalesforceColumn < Column
33
- attr_reader :label, :readonly, :reference_to
40
+ include StringHelper
41
+
42
+ attr_reader :api_name, :label, :readonly, :reference_to
34
43
 
35
44
  def initialize(field)
36
- @name = field[:name]
45
+ @api_name = field[:name]
46
+ @name = column_nameize(@api_name)
37
47
  @type = get_type(field[:type])
38
48
  @limit = field[:length]
39
49
  @label = field[:label]
@@ -82,28 +92,34 @@ module ActiveRecord
82
92
  end
83
93
 
84
94
  class SalesforceRelationship
85
- attr_reader :name, :foreign_key, :label, :reference_to, :one_to_many, :cascade_delete
95
+ include StringHelper
96
+
97
+ attr_reader :name, :api_name, :foreign_key, :label, :reference_to, :one_to_many, :cascade_delete
86
98
 
87
99
  def initialize(source)
88
100
  if source[:childSObject]
89
101
  relationship = source
90
102
 
91
- @name = relationship[:relationshipName] ? relationship[:relationshipName] : relationship[:field].chop.chop
103
+ @api_name = relationship[:relationshipName] ? relationship[:relationshipName] : relationship[:field].chop.chop
92
104
  @one_to_many = relationship[:relationshipName] != nil
93
105
  @cascade_delete = relationship[:cascadeDelete] == "true"
94
106
  @reference_to = relationship[:childSObject]
95
107
  @label = @name
96
- @foreign_key = relationship[:field]
108
+ @foreign_key = column_nameize(relationship[:field])
97
109
  else
98
110
  field = source
99
111
 
100
- @name = field[:name].chop.chop
112
+ @api_name = field[:name].chop.chop
101
113
  @label = field[:label]
102
114
  @readonly = (field[:updateable] != "true" or field[:createable] != "true")
103
115
  @reference_to = field[:referenceTo]
104
116
  @one_to_many = false
105
117
  @cascade_delete = false
118
+ @foreign_key = column_nameize(field[:name])
106
119
  end
120
+
121
+ @name = column_nameize(@api_name)
122
+
107
123
  end
108
124
  end
109
125
 
data/lib/rforce.rb CHANGED
@@ -202,13 +202,16 @@ module RForce
202
202
  request = encode(request)
203
203
 
204
204
  headers = {
205
- 'Accept-Encoding' => 'gzip',
206
205
  'Connection' => 'Keep-Alive',
207
- 'Content-Encoding' => 'gzip',
208
206
  'Content-Type' => 'text/xml',
209
207
  'SOAPAction' => '""',
210
208
  'User-Agent' => 'ActiveSalesforce RForce'
211
209
  }
210
+
211
+ unless $DEBUG
212
+ headers['Accept-Encoding'] = 'gzip'
213
+ headers['Content-Encoding'] = 'gzip'
214
+ end
212
215
 
213
216
  #Send the request to the server and read the response.
214
217
  response = @server.post2(@url.path, request, headers)
@@ -255,6 +258,8 @@ module RForce
255
258
 
256
259
  # encode gzip
257
260
  def encode(request)
261
+ return request if $DEBUG
262
+
258
263
  begin
259
264
  ostream = StringIO.new
260
265
  gzw = Zlib::GzipWriter.new(ostream)
@@ -27,14 +27,8 @@ require_gem 'rails', ">= 1.0.0"
27
27
  require 'thread'
28
28
 
29
29
  require File.dirname(__FILE__) + '/salesforce_login'
30
- require File.dirname(__FILE__) + '/sobject_attributes'
31
- require File.dirname(__FILE__) + '/salesforce_active_record'
32
30
  require File.dirname(__FILE__) + '/column_definition'
33
31
 
34
- ActiveRecord::Base.class_eval do
35
- include ActiveRecord::SalesforceRecord
36
- end
37
-
38
32
 
39
33
  module ActiveRecord
40
34
  class Base
@@ -75,6 +69,8 @@ module ActiveRecord
75
69
  end
76
70
 
77
71
  class SalesforceAdapter < AbstractAdapter
72
+ include StringHelper
73
+
78
74
  attr_accessor :batch_size
79
75
 
80
76
  def initialize(connection, logger, connection_options, config)
@@ -84,7 +80,7 @@ module ActiveRecord
84
80
 
85
81
  @columns_map = {}
86
82
  @columns_name_map = {}
87
-
83
+
88
84
  @relationships_map = {}
89
85
  end
90
86
 
@@ -109,7 +105,8 @@ module ActiveRecord
109
105
  end
110
106
 
111
107
  def quote_column_name(name) #:nodoc:
112
- "`#{name}`"
108
+ # Mark the column name to make it easier to find later
109
+ "@@#{name}"
113
110
  end
114
111
 
115
112
  def quote_string(string) #:nodoc:
@@ -138,45 +135,124 @@ module ActiveRecord
138
135
 
139
136
  # DATABASE STATEMENTS ======================================
140
137
 
141
- def select_all(soql, name = nil) #:nodoc:
138
+ def select_all(sql, name = nil) #:nodoc:
139
+ table_name = table_name_from_sql(sql)
140
+ entity_name = entity_name_from_table(table_name)
141
+ column_names = api_column_names(table_name)
142
+
143
+ soql = sql.sub(/SELECT \* FROM/, "SELECT #{column_names.join(', ')} FROM")
144
+
145
+ # Look for a LIMIT clause
146
+ soql.sub!(/LIMIT 1/, "")
147
+
148
+ # Fixup column references to use api names
149
+ columns = columns_map(table_name)
150
+ while soql =~ /@@(\w+)/
151
+ column = columns[$~[1]]
152
+ soql = $~.pre_match + column.api_name + $~.post_match
153
+ end
154
+
142
155
  log(soql, name)
143
156
 
144
157
  @connection.batch_size = @batch_size if @batch_size
145
158
  @batch_size = nil
146
159
 
147
160
  records = get_result(@connection.query(:queryString => soql), :query).records
148
-
161
+
149
162
  result = []
150
163
  return result unless records
151
164
 
152
165
  records = [ records ] unless records.is_a?(Array)
153
166
 
154
167
  records.each do |record|
155
- attributes = Salesforce::SObjectAttributes.new(columns_map(record[:type]), record)
156
- result << attributes
168
+ row = {}
169
+
170
+ record.each do |name, value|
171
+ name = column_nameize(name.to_s)
172
+
173
+ # Replace nil element with nil
174
+ value = nil if value.respond_to?(:xmlattr_nil) and value.xmlattr_nil
175
+
176
+ # Ids are returned in an array with 2 duplicate entries...
177
+ value = value[0] if name == "id"
178
+
179
+ row[name] = value
180
+ end
181
+
182
+ result << row
157
183
  end
158
184
 
159
185
  result
160
186
  end
161
187
 
162
188
  def select_one(sql, name = nil) #:nodoc:
189
+ @connection.batch_size = 1
163
190
  result = select_all(sql, name)
164
191
  result.nil? ? nil : result.first
165
192
  end
166
193
 
167
- def create(sobject, name = nil) #:nodoc:
168
- check_result(get_result(@connection.create(sobject), :create))[:id]
169
- end
194
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
195
+ tokens = sql.scan(/[0-9A-Za-z._]+/)
196
+
197
+ # Convert sql to sobject
198
+ table_name = tokens[2]
199
+ entity_name = entity_name_from_table(table_name)
200
+ columns = columns_map(table_name)
201
+
202
+ # Extract array of [column_name, value] pairs
203
+ names = tokens[3 .. tokens.length / 2]
204
+ values = tokens[(tokens.length / 2) + 2 .. tokens.length]
205
+
206
+ fields = {}
207
+ names.length.times do | n |
208
+ name = names[n]
209
+ value = values[n]
210
+ column = columns[name]
211
+
212
+ fields[column.api_name] = value if not column.readonly and value != "NULL"
213
+ end
214
+
215
+ sobject = create_sobject(entity_name, nil, fields)
216
+
217
+ check_result(get_result(@connection.create(:sObjects => sobject), :create))[0][:id]
218
+ end
170
219
 
171
- def update(sobject, name = nil) #:nodoc:
172
- check_result(get_result(@connection.update(sobject), :update))
220
+ def update(sql, name = nil) #:nodoc:
221
+ # Convert sql to sobject
222
+ table_name = sql.match(/UPDATE (\w+) /)[1]
223
+ entity_name = entity_name_from_table(table_name)
224
+ columns = columns_map(table_name)
225
+
226
+ # Extract array of [column_name, value] pairs
227
+ raw_fields = sql.scan(/(\w+) = '([^']*)'/)
228
+
229
+ fields = {}
230
+ raw_fields.each do | name, value |
231
+ column = columns[name]
232
+ fields[column.api_name] = value if not column.readonly or name == "id"
233
+ end
234
+
235
+ id = fields["Id"].match(/[a-zA-Z0-9]+/)[0]
236
+ fields.delete("Id")
237
+
238
+ sobject = create_sobject(entity_name, id, fields)
239
+
240
+ check_result(get_result(@connection.update(:sObjects => sobject), :update))
173
241
  end
242
+
243
+ def delete(sql, name = nil)
244
+ # Extract the ids from the IN () clause
245
+ match = sql.match(/IN \(([^\)]*)\)/)
174
246
 
175
- def delete(ids)
176
- puts "Delete #{ids}"
177
- check_result(get_result(@connection.delete(:ids => ids), :delete))
178
- end
247
+ # If the IN clause was not found fall back to WHERE id = 'blah'
248
+ ids = match ? match[1].scan(/\w+/) : [ sql.match(/WHERE id = '([^\']*)'/)[1] ]
249
+
250
+ ids_element = []
251
+ ids.each { |id| ids_element << :ids << id }
179
252
 
253
+ check_result(get_result(@connection.delete(ids_element), :delete))
254
+ end
255
+
180
256
  def get_result(response, method)
181
257
  responseName = (method.to_s + "Response").to_sym
182
258
  finalResponse = response[responseName]
@@ -187,21 +263,28 @@ module ActiveRecord
187
263
  end
188
264
 
189
265
  def check_result(result)
190
- raise SalesforceError.new(result[:Errors], result[:Errors].Message) unless result[:success] == "true"
266
+ result = [ result ] unless result.is_a?(Array)
267
+
268
+ result.each do |r|
269
+ raise SalesforceError.new(r[:errors], r[:errors].Message) unless r[:success] == "true"
270
+ end
271
+
191
272
  result
192
273
  end
193
-
274
+
194
275
  def columns(table_name, name = nil)
195
- cached_columns = @columns_map[table_name]
276
+ entity_name = entity_name_from_table(table_name)
277
+
278
+ cached_columns = @columns_map[entity_name]
196
279
  return cached_columns if cached_columns
197
280
 
198
281
  cached_columns = []
199
- @columns_map[table_name] = cached_columns
200
-
282
+ @columns_map[entity_name] = cached_columns
283
+
201
284
  cached_relationships = []
202
- @relationships_map[table_name] = cached_relationships
203
-
204
- metadata = get_result(@connection.describeSObject(:sObjectType => table_name), :describeSObject)
285
+ @relationships_map[entity_name] = cached_relationships
286
+
287
+ metadata = get_result(@connection.describeSObject(:sObjectType => entity_name), :describeSObject)
205
288
 
206
289
  metadata.fields.each do |field|
207
290
  column = SalesforceColumn.new(field)
@@ -209,54 +292,109 @@ module ActiveRecord
209
292
 
210
293
  cached_relationships << SalesforceRelationship.new(field) if field[:type] =~ /reference/i
211
294
  end
212
-
295
+
213
296
  if metadata.childRelationships
214
297
  metadata.childRelationships.each do |relationship|
215
- cached_relationships << SalesforceRelationship.new(relationship)
298
+
299
+ if relationship[:childSObject].casecmp(entity_name) == 0
300
+ r = SalesforceRelationship.new(relationship)
301
+ cached_relationships << r
302
+ end
216
303
  end
217
304
  end
218
-
305
+
306
+ configure_active_record entity_name
307
+
219
308
  cached_columns
220
309
  end
221
310
 
311
+ def configure_active_record(entity_name)
312
+ klass = entity_name.constantize
313
+
314
+ klass.table_name = entity_name
315
+ klass.pluralize_table_names = false
316
+ klass.set_inheritance_column nil
317
+ klass.lock_optimistically = false
318
+ klass.record_timestamps = false
319
+ klass.default_timezone = :utc
320
+
321
+ # Create relationships for any reference field
322
+ @relationships_map[entity_name].each do |relationship|
323
+ referenceName = relationship.name
324
+ unless self.respond_to? referenceName.to_sym or relationship.reference_to == "Profile"
325
+ one_to_many = relationship.one_to_many
326
+ foreign_key = relationship.foreign_key
327
+
328
+ if one_to_many
329
+ klass.has_many referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => foreign_key, :dependent => false
330
+ else
331
+ klass.belongs_to referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => foreign_key, :dependent => false
332
+ end
333
+
334
+ puts "Created one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{entity_name} to #{relationship.reference_to} using #{foreign_key}"
335
+
336
+ end
337
+ end
338
+
339
+ end
340
+
222
341
  def relationships(table_name)
223
- cached_relationships = @relationships_map[table_name]
224
- return cached_relationships if cached_relationships
225
-
226
- # This will load column and relationship metadata
227
- columns(table_name)
228
-
229
- @relationships_map[table_name]
342
+ entity_name = entity_name_from_table(table_name)
343
+
344
+ cached_relationships = @relationships_map[entity_name]
345
+
346
+ unless cached_relationships
347
+ # This will load column and relationship metadata
348
+ columns(table_name)
349
+ end
350
+
351
+ @relationships_map[entity_name]
230
352
  end
231
353
 
232
354
  def columns_map(table_name, name = nil)
233
- columns_map = @columns_name_map[table_name]
234
- return columns_map if columns_map
355
+ entity_name = entity_name_from_table(table_name)
235
356
 
236
- columns_map = {}
237
- @columns_name_map[table_name] = columns_map
238
-
239
- columns(table_name).each { |column| columns_map[column.name] = column }
357
+ columns_map = @columns_name_map[entity_name]
358
+ unless columns_map
359
+ columns_map = {}
360
+ @columns_name_map[entity_name] = columns_map
361
+
362
+ columns(entity_name).each { |column| columns_map[column.name] = column }
363
+ end
240
364
 
241
365
  columns_map
242
366
  end
243
367
 
244
- private
368
+ def entity_name_from_table(table_name)
369
+ return table_name.singularize.camelize
370
+ end
371
+
245
372
 
246
- def select(sql, name = nil)
247
- puts "select(#{sql}, (#{name}))"
248
- @connection.query_with_result = true
249
- result = execute(sql, name)
250
- rows = []
251
- if @null_values_in_each_hash
252
- result.each_hash { |row| rows << row }
253
- else
254
- all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
255
- result.each_hash { |row| rows << all_fields.dup.update(row) }
373
+ def create_sobject(entity_name, id, fields)
374
+ sobj = [ 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }', entity_name ]
375
+ sobj << 'Id { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << id if id
376
+
377
+ # now add any changed fields
378
+ fieldValues = {}
379
+ fields.each do | name, value |
380
+ sobj << name.to_sym << value if value
256
381
  end
257
- result.free
258
- rows
382
+
383
+ sobj
384
+ end
385
+
386
+ def table_name_from_sql(sql)
387
+ sql.match(/FROM (\w+) /)[1]
388
+ end
389
+
390
+ def column_names(table_name)
391
+ columns(table_name).map { |column| column.name }
392
+ end
393
+
394
+ def api_column_names(table_name)
395
+ columns(table_name).map { |column| column.api_name }
259
396
  end
260
397
  end
398
+
261
399
  end
262
400
  end
@@ -8,85 +8,102 @@ class AccountTest < Test::Unit::TestCase
8
8
  ActiveRecord::Base.allow_concurrency = true
9
9
  end
10
10
 
11
-
11
+ if false
12
+ def test_create_account
13
+ dutchCo = Account.new
14
+ dutchCo.name = "DutchCo"
15
+ dutchCo.website = "www.dutchco.com"
16
+ dutchCo.save
17
+
18
+ dutchCo2 = Account.new(:name => "DutchCo2", :website => "www.dutchco2.com")
19
+ dutchCo2.save
20
+
21
+ dutchCo3 = Account.create(:name => "DutchCo3", :website => "www.dutchco3.com")
22
+
23
+ accounts = Account.create([
24
+ { :name => "DutchCo4", :website => "www.dutchco4.com" },
25
+ { :name => "DutchCo5", :website => "www.dutchco5.com" }])
26
+ end
27
+
28
+ def test_create_a_contact
29
+ contact = Contact.find_by_id("0033000000B1LKpAAN")
30
+ contact.first_name = "DutchieBoy"
31
+ contact.save
32
+ end
33
+
34
+
35
+ def test_create_a_contact
36
+ contact = Contact.new
37
+ end
38
+
39
+
12
40
  def test_get_a_case_comment
13
- comment = CaseComment.find_by_ParentId('500300000011inJAAQ')
41
+ comment = CaseComment.find_by_parent_id('500300000011inJAAQ')
14
42
  end
15
43
 
16
-
44
+
17
45
  def test_one_to_many_relationship
18
- contact = Contact.find_by_Id('0033000000B1LKrAAN')
46
+ contact = Contact.find_by_id('0033000000B1LKrAAN')
19
47
 
20
- cases = contact.Cases
48
+ cases = contact.cases
21
49
 
22
50
  cases.each do |c|
23
- puts "Case('#{c.Id}', '#{c.Subject}')"
24
-
25
- comments = c.CaseComments
51
+ puts "Case('#{c.id}', '#{c.subject}')"
26
52
 
53
+ comments = c.case_comments
27
54
  comments.each do |comment|
28
- puts " CaseComment('#{comment.Id}', '#{comment.CommentBody}')"
55
+ puts " CaseComment('#{comment.id}', '#{comment.comment_body}')"
29
56
  end
30
-
31
57
  end
32
58
  end
33
-
34
-
59
+
35
60
  def test_get_account
36
61
  accounts = Account.find(:all)
37
-
38
- #accounts.each { |account| puts "#{account.Name}, #{account.Id}, #{account.LastModifiedById}" }
39
-
40
- acme = Account.find(:first, :conditions => ["Name = 'Acme'"])
41
-
42
- acme = Account.find_by_Id(acme.Id)
43
-
44
- acme = Account.find_by_Name_and_LastModifiedById('salesforce.com', acme.LastModifiedById)
62
+
63
+ accounts.each { |account| puts "#{account.name}, #{account.id}, #{account.last_modified_by_id}" }
64
+
65
+ acme = Account.find(:first, :conditions => ["name = 'Acme'"])
66
+
67
+ acme = Account.find_by_id(acme.id)
68
+
69
+ acme = Account.find_by_name_and_last_modified_by_id('salesforce.com', acme.last_modified_by_id)
45
70
  end
46
-
47
-
71
+
48
72
  def test_update_account
49
73
  acme = Account.new
50
- acme.Name = "Acme"
74
+ acme.name = "Acme"
51
75
  acme.save
52
76
 
53
- acme = Account.find_by_Name('Acme')
54
-
55
- acme.Website = "http://www.dutchforce.com/#{Time.now}.jpg"
56
- acme.LastModifiedDate = Time.now
77
+ acme = Account.find_by_name('Acme')
78
+
79
+ acme.website = "http://www.dutchforce.com/#{Time.now}.jpg"
80
+ acme.last_modified_date = Time.now
57
81
 
58
82
  acme.save
59
83
  end
60
-
61
-
62
- def test_create_account
63
- dutchCo = Account.new
64
- dutchCo.Name = "DutchCo"
65
- dutchCo.Website = "www.dutchco.com"
66
- dutchCo.save
67
-
68
- dutchCo2 = Account.new(:Name => "DutchCo2", :Website => "www.dutchco2.com")
69
- dutchCo2.save
70
-
71
- dutchCo3 = Account.create(:Name => "DutchCo3", :Website => "www.dutchco3.com")
72
-
73
- accounts = Account.create([
74
- { :Name => "DutchCo4", :Website => "www.dutchco4.com" },
75
- { :Name => "DutchCo5", :Website => "www.dutchco5.com" }])
76
- end
77
-
84
+ end
85
+
78
86
  def test_destroy_account
79
- account = Account.create(:Name => "DutchADelete", :Website => "www.dutchcodelete.com")
80
- account = Account.find_by_Id(account.Id)
87
+ Account.new
81
88
 
82
- pp account.Parent
89
+ account = Account.create(:name => "DutchADelete", :website => "www.dutchcodelete.com")
90
+ account2 = Account.create(:name => "DutchADelete2", :website => "www.dutchcodelete2.com")
91
+
92
+ #pp account
93
+
94
+ account = Account.find_by_id(account.id)
95
+
96
+ pp account.parent
83
97
 
84
98
  puts "Getting CreatedBy"
85
- createdBy = account.CreatedBy
86
- createdBy = User.find_by_Id(account.CreatedById);
87
- puts createdBy.Email
88
-
89
- Account.delete(account.Id)
99
+ createdBy = account.created_by
100
+ createdBy = User.find_by_id(account.created_by_id);
101
+ puts createdBy.email
102
+
103
+ Account.delete([account.id, account2.id])
104
+
105
+ account3 = Account.create(:name => "DutchADelete3", :website => "www.dutchcodelete3.com")
106
+ account3.destroy
90
107
  end
91
-
108
+
92
109
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: activesalesforce
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2006-01-30 00:00:00 -05:00
6
+ version: 0.1.2
7
+ date: 2006-02-02 00:00:00 -05:00
8
8
  summary: ActiveSalesforce is an extension to the Rails Framework that allows for the dynamic creation and management of ActiveRecord objects through the use of Salesforce meta-data and uses a Salesforce.com organization as the backing store.
9
9
  require_paths:
10
10
  - lib
@@ -29,14 +29,11 @@ authors:
29
29
  - Doug Chasman
30
30
  files:
31
31
  - lib/salesforce_login.rb
32
- - lib/salesforce_active_record.rb
33
32
  - lib/column_definition.rb
34
33
  - lib/active_salesforce.rb
35
34
  - lib/rforce.rb
36
- - lib/sobject_attributes.rb
37
35
  - lib/salesforce_connection_adapter.rb
38
36
  - test/unit
39
- - test/unit/sobject_attributes_test.rb
40
37
  - test/unit/account_test.rb
41
38
  - README
42
39
  test_files: []
@@ -1,151 +0,0 @@
1
- =begin
2
- ActiveSalesforce
3
- Copyright (c) 2006 Doug Chasman
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
- =end
23
-
24
- require File.dirname(__FILE__) + '/sobject_attributes'
25
-
26
-
27
- module ActiveRecord
28
- # Active Records will automatically record creation and/or update timestamps of database objects
29
- # if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
30
- # automatically included, so you don't need to do that manually.
31
- #
32
- # This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
33
- # This behavior can use GMT by setting <tt>ActiveRecord::Base.timestamps_gmt = true</tt>
34
- module SalesforceRecord
35
-
36
- def self.append_features(base) # :nodoc:
37
- super
38
-
39
- base.class_eval do
40
- alias_method :create, :create_with_sforce_api
41
- alias_method :update, :update_with_sforce_api
42
- end
43
- end
44
-
45
- def create_with_sforce_api
46
- return if not @attributes.changed?
47
- puts "create_with_sforce_api creating #{self.class}"
48
- id = connection.create(:sObjects => create_sobject())
49
- self.Id = id
50
- @attributes.clear_changed!
51
- end
52
-
53
- def update_with_sforce_api
54
- return if not @attributes.changed?
55
- puts "update_with_sforce_api updating #{self.class}('#{self.Id}')"
56
- connection.update(:sObjects => create_sobject())
57
- @attributes.clear_changed!
58
- end
59
-
60
- def create_sobject()
61
- fields = @attributes.changed_fields
62
-
63
- sobj = [ 'type { :xmlns => "urn:sobject.partner.soap.sforce.com" }', self.class.table_name ]
64
- sobj << 'Id { :xmlns => "urn:sobject.partner.soap.sforce.com" }' << self.Id if self.Id
65
-
66
- # now add any changed fields
67
- fieldValues = {}
68
- fields.each do |fieldName|
69
- value = @attributes[fieldName]
70
- sobj << fieldName.to_sym << value if value
71
- end
72
-
73
- sobj
74
- end
75
-
76
- end
77
-
78
- class Base
79
- set_inheritance_column nil
80
- lock_optimistically = false
81
- record_timestamps = false
82
- default_timezone = :utc
83
-
84
- def after_initialize()
85
- sfdcObjectName = self.class.table_name
86
- if not @attributes.is_a?(Salesforce::SObjectAttributes)
87
- # Insure that SObjectAttributes is always used for our attributes
88
- originalAttributes = @attributes
89
-
90
- @attributes = Salesforce::SObjectAttributes.new(connection.columns_map(sfdcObjectName))
91
-
92
- originalAttributes.each { |name, value| self[name] = value }
93
- end
94
-
95
- # Create relationships for any reference field
96
- connection.relationships(sfdcObjectName).each do |relationship|
97
- referenceName = relationship.name
98
- unless self.respond_to? referenceName.to_sym or relationship.reference_to == "Profile"
99
- one_to_many = relationship.one_to_many
100
-
101
- puts "Creating one-to-#{one_to_many ? 'many' : 'one' } relationship '#{referenceName}' from #{sfdcObjectName} to #{relationship.reference_to}"
102
-
103
- if one_to_many
104
- self.class.has_many referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => relationship.foreign_key, :dependent => false
105
- else
106
- self.class.belongs_to referenceName.to_sym, :class_name => relationship.reference_to, :foreign_key => relationship.name, :dependent => false
107
- end
108
- end
109
- end
110
- end
111
-
112
- def self.table_name
113
- # Undo weird camilization that messes with custom object names
114
- name = self.name
115
- name.last(6) == "Custom" ? name.first(name.length - 6) << "__c" : name
116
- end
117
-
118
- def self.primary_key
119
- "Id"
120
- end
121
-
122
- def self.construct_finder_sql(options)
123
- soql = "SELECT #{column_names.join(', ')} FROM #{table_name} "
124
- add_conditions!(soql, options[:conditions])
125
- soql
126
- end
127
-
128
- def self.construct_conditions_from_arguments(attribute_names, arguments)
129
- conditions = []
130
- attribute_names.each_with_index { |name, idx| conditions << "#{name} #{attribute_condition(arguments[idx])} " }
131
- [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
132
- end
133
-
134
- def self.count(conditions = nil, joins = nil)
135
- soql = "SELECT Id FROM #{table_name} "
136
- add_conditions!(soql, conditions)
137
-
138
- count_by_sql(soql)
139
- end
140
-
141
- def self.count_by_sql(soql)
142
- connection.batch_size = 1
143
- connection.select_all(soql, "#{name} Count").length
144
- end
145
-
146
- def self.delete(ids)
147
- connection.delete(ids)
148
- end
149
-
150
- end
151
- end
@@ -1,132 +0,0 @@
1
- =begin
2
- ActiveSalesforce
3
- Copyright (c) 2006 Doug Chasman
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
- =end
23
-
24
- require 'set'
25
-
26
-
27
- module Salesforce
28
-
29
- class SObjectAttributes
30
- include Enumerable
31
-
32
- def initialize(columns, record = nil)
33
- @columns = columns
34
- @values = {}
35
-
36
- if record
37
- record.each do |name, value|
38
- # Replace nil element with nil
39
- value = nil if value.respond_to?(:xmlattr_nil) and value.xmlattr_nil
40
-
41
- # Ids are returned in an array with 2 duplicate entries...
42
- value = value[0] if name == :Id
43
-
44
- self[name.to_s] = value
45
- end
46
- else
47
- columns.values.each { |column| self[column.name] = nil }
48
- end
49
-
50
- clear_changed!
51
- end
52
-
53
- def [](key)
54
- @values[key].freeze
55
- end
56
-
57
- def []=(key, value)
58
- column = @columns[key]
59
- return unless column
60
-
61
- value = nil if value == ""
62
-
63
- return if @values[key] == value and @values.include?(key)
64
-
65
- originalClass = value.class
66
- originalValue = value
67
-
68
- if value
69
- # Convert strings representation of dates and datetimes to date and time objects
70
- case column.type
71
- when :date
72
- value = value.is_a?(Date) ? value : Date.parse(value)
73
- when :datetime
74
- value = value.is_a?(Time) ? value : Time.parse(value)
75
- else
76
- value = column.type_cast(value)
77
- end
78
- end
79
-
80
- @values[key] = value
81
-
82
- #puts "setting #{key} = #{value} [#{originalValue}] (#{originalClass}, #{value.class})"
83
-
84
- if not column.readonly
85
- @changed = Set.new unless @changed
86
- @changed.add(key)
87
- end
88
- end
89
-
90
- def include?(key)
91
- @values.include?(key)
92
- end
93
-
94
- def has_key?(key)
95
- @values.has_key?(key)
96
- end
97
-
98
- def length
99
- @values.length
100
- end
101
-
102
- def keys
103
- @values.keys
104
- end
105
-
106
- def clear
107
- @values.clear
108
- clear_changed
109
- end
110
-
111
- def clear_changed!
112
- @changed = nil
113
- end
114
-
115
- def changed?
116
- @changed != nil
117
- end
118
-
119
- def changed_fields
120
- @changed
121
- end
122
-
123
-
124
- # Enumerable support
125
-
126
- def each(&block)
127
- @values.each(&block)
128
- end
129
-
130
- end
131
-
132
- end
@@ -1,38 +0,0 @@
1
- puts "Yahoo"
2
-
3
- require 'test/unit'
4
- require File.dirname(__FILE__) + '/../../src/sobject_attributes'
5
-
6
- class SobjectAttributesTest < Test::Unit::TestCase
7
-
8
- def setup
9
- @attributes = Salesforce::SObjectAttributes.new
10
- end
11
-
12
- def test_add_values()
13
- assert((not @attributes.changed?))
14
-
15
- @attributes['name'] = 'value'
16
- assert(@attributes.changed?)
17
-
18
- assert_equal('value', @attributes['name'])
19
-
20
- assert_equal(Set.new('name'), @attributes.changed_fields)
21
-
22
- @attributes.clear_changed!
23
- assert((not @attributes.changed?))
24
-
25
- assert_equal('value', @attributes['name'])
26
- end
27
-
28
- def test_enumeration
29
- 10.times { |n| @attributes["name_#{n}"] = "value_#{n}" }
30
-
31
- assert_equal(10, @attributes.length)
32
-
33
- 5.times { |n| @attributes["name_#{n + 10}"] = "value_#{n + 10}" }
34
-
35
- @attributes.each { |name, value| assert_equal(name[/_\d/], value[/_\d/]) }
36
- end
37
-
38
- end