activesalesforce 0.1.1 → 0.1.2
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/lib/active_salesforce.rb +18 -0
- data/lib/column_definition.rb +22 -6
- data/lib/rforce.rb +7 -2
- data/lib/salesforce_connection_adapter.rb +196 -58
- data/test/unit/account_test.rb +71 -54
- metadata +2 -5
- data/lib/salesforce_active_record.rb +0 -151
- data/lib/sobject_attributes.rb +0 -132
- data/test/unit/sobject_attributes_test.rb +0 -38
data/lib/active_salesforce.rb
CHANGED
@@ -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
|
data/lib/column_definition.rb
CHANGED
@@ -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
|
-
|
40
|
+
include StringHelper
|
41
|
+
|
42
|
+
attr_reader :api_name, :label, :readonly, :reference_to
|
34
43
|
|
35
44
|
def initialize(field)
|
36
|
-
@
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
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(
|
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
|
-
|
156
|
-
|
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
|
168
|
-
|
169
|
-
|
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(
|
172
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
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
|
-
|
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[
|
200
|
-
|
282
|
+
@columns_map[entity_name] = cached_columns
|
283
|
+
|
201
284
|
cached_relationships = []
|
202
|
-
@relationships_map[
|
203
|
-
|
204
|
-
metadata = get_result(@connection.describeSObject(:sObjectType =>
|
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
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
234
|
-
return columns_map if columns_map
|
355
|
+
entity_name = entity_name_from_table(table_name)
|
235
356
|
|
236
|
-
columns_map =
|
237
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
368
|
+
def entity_name_from_table(table_name)
|
369
|
+
return table_name.singularize.camelize
|
370
|
+
end
|
371
|
+
|
245
372
|
|
246
|
-
def
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
258
|
-
|
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
|
data/test/unit/account_test.rb
CHANGED
@@ -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.
|
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.
|
46
|
+
contact = Contact.find_by_id('0033000000B1LKrAAN')
|
19
47
|
|
20
|
-
cases = contact.
|
48
|
+
cases = contact.cases
|
21
49
|
|
22
50
|
cases.each do |c|
|
23
|
-
puts "Case('#{c.
|
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.
|
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
|
-
|
39
|
-
|
40
|
-
acme = Account.find(:first, :conditions => ["
|
41
|
-
|
42
|
-
acme = Account.
|
43
|
-
|
44
|
-
acme = Account.
|
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.
|
74
|
+
acme.name = "Acme"
|
51
75
|
acme.save
|
52
76
|
|
53
|
-
acme = Account.
|
54
|
-
|
55
|
-
acme.
|
56
|
-
acme.
|
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
|
-
|
80
|
-
account = Account.find_by_Id(account.Id)
|
87
|
+
Account.new
|
81
88
|
|
82
|
-
|
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.
|
86
|
-
createdBy = User.
|
87
|
-
puts createdBy.
|
88
|
-
|
89
|
-
Account.delete(account.
|
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.
|
7
|
-
date: 2006-
|
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
|
data/lib/sobject_attributes.rb
DELETED
@@ -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
|