salesforce_ar_sync 2.0.2 → 5.2.0
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.
- checksums.yaml +5 -5
- data/README.md +129 -64
- data/app/controllers/salesforce_ar_sync/soap_message_controller.rb +10 -13
- data/lib/salesforce_ar_sync/engine.rb +28 -19
- data/lib/salesforce_ar_sync/extenders/salesforce_syncable.rb +4 -0
- data/lib/salesforce_ar_sync/jobs/base_job.rb +15 -0
- data/lib/salesforce_ar_sync/jobs/delete_object_job.rb +9 -0
- data/lib/salesforce_ar_sync/jobs/salesforce_object_sync_job.rb +15 -0
- data/lib/salesforce_ar_sync/jobs/sync_object_job.rb +9 -0
- data/lib/salesforce_ar_sync/salesforce_sync.rb +73 -53
- data/lib/salesforce_ar_sync/soap_handler/base.rb +20 -17
- data/lib/salesforce_ar_sync/soap_handler/delete.rb +4 -1
- data/lib/salesforce_ar_sync/version.rb +3 -1
- data/lib/salesforce_ar_sync.rb +5 -3
- metadata +51 -36
- data/lib/salesforce_ar_sync/railtie.rb +0 -7
- data/lib/salesforce_ar_sync/salesforce_object_sync.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5a366e664f19f09c3ad3f31088f75d90e4d11ba5b2708d59a5a8e470ebc0276c
|
4
|
+
data.tar.gz: 2f37638a57e5769698051c8cbbdcaa49dce50de4eae90d12da6a59dabc5b0765
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 401722023c03c42c8dd070a8259dd0e5ddeec6306414ebd6d66d23de4055857a5fac177943d61bc8f81e2e5be508295c5cd22673e9a7be1adcd1e66adcdf1fad
|
7
|
+
data.tar.gz: 60529516c0d6350377dd7f9b229cd6642a6025dc9c72bf107331dd323232fa8526453b7c727a8dd57de754576f1687e1f72f7790643dcdeab43772148614659a
|
data/README.md
CHANGED
@@ -1,7 +1,57 @@
|
|
1
1
|
# SalesforceArSync
|
2
2
|
|
3
3
|
SalesforceARSync allows you to sync models and fields with Salesforce through a combination of
|
4
|
-
Outbound Messaging, SOAP and
|
4
|
+
Outbound Messaging, SOAP and restforce.
|
5
|
+
|
6
|
+
### Contents
|
7
|
+
|
8
|
+
- [Installation](#installation)
|
9
|
+
- [Requirements](#requirements)
|
10
|
+
- [Salesforce Setup](#salesforce-setup)
|
11
|
+
- [1. Setup Remote Access](#1-setup-remote-access)
|
12
|
+
- [2. Setup Outbound Messaging](#2-setup-outbound-messaging)
|
13
|
+
- [restforce](#restforce)
|
14
|
+
- [Gem Installation](#gem-installation)
|
15
|
+
- [Application Setup](#application-setup)
|
16
|
+
- [Usage](#usage)
|
17
|
+
- [Configuration Options](#configuration-options)
|
18
|
+
- [Model Options](#model-options)
|
19
|
+
- [salesforce\_sync\_enabled](#salesforce_sync_enabled)
|
20
|
+
- [sync\_attributes](#sync_attributes)
|
21
|
+
- [async\_attributes](#async_attributes)
|
22
|
+
- [default\_attributes\_for\_create](#default_attributes_for_create)
|
23
|
+
- [salesforce\_id\_attribute\_name](#salesforce_id_attribute_name)
|
24
|
+
- [web\_id\_attribute\_name](#web_id_attribute_name)
|
25
|
+
- [activerecord\_web\_id\_attribute\_name](#activerecord_web_id_attribute_name)
|
26
|
+
- [salesforce\_sync\_web\_id](#salesforce_sync_web_id)
|
27
|
+
- [additional\_lookup\_fields](#additional_lookup_fields)
|
28
|
+
- [web\_class\_name](#web_class_name)
|
29
|
+
- [salesforce\_object\_name](#salesforce_object_name)
|
30
|
+
- [except](#except)
|
31
|
+
- [save\_method](#save_method)
|
32
|
+
- [unscoped\_updates](#unscoped_updates)
|
33
|
+
- [readonly\_fields](#readonly_fields)
|
34
|
+
- [Stopping the Sync](#stopping-the-sync)
|
35
|
+
- [Manual Sync](#manual-sync)
|
36
|
+
- [Examples](#examples)
|
37
|
+
- [Our Basic Example Model](#our-basic-example-model)
|
38
|
+
- [Making the Model Syncable](#making-the-model-syncable)
|
39
|
+
- [Stopping the Model from Syncing with a Flag](#stopping-the-model-from-syncing-with-a-flag)
|
40
|
+
- [Stopping the Model from Syncing with a Method](#stopping-the-model-from-syncing-with-a-method)
|
41
|
+
- [Stopping a Record from Syncing](#stopping-a-record-from-syncing)
|
42
|
+
- [Specify Async Attributes](#specify-async-attributes)
|
43
|
+
- [Specify Default Attributes when an Object is Created](#specify-default-attributes-when-an-object-is-created)
|
44
|
+
- [Relationships](#relationships)
|
45
|
+
- [Defining a Custom Salesforce Object](#defining-a-custom-salesforce-object)
|
46
|
+
- [Deletes](#deletes)
|
47
|
+
- [Inbound Deletes](#inbound-deletes)
|
48
|
+
- [Outbound Deletes](#outbound-deletes)
|
49
|
+
- [Soft Deletes](#soft-deletes)
|
50
|
+
- [Errors](#errors)
|
51
|
+
- [Outbound Message Errors](#outbound-message-errors)
|
52
|
+
- [Finding your 18 Character Organization ID](#finding-your-18-character-organization-id)
|
53
|
+
- [Testing](#testing)
|
54
|
+
- [Contributing](#contributing)
|
5
55
|
|
6
56
|
## Installation
|
7
57
|
|
@@ -10,8 +60,7 @@ Outbound Messaging, SOAP and databasedotcom.
|
|
10
60
|
* Rails ~> 4.0
|
11
61
|
* Salesforce.com instance
|
12
62
|
* [Have your 18 character organization id ready](#finding-your-18-character-organization-id)
|
13
|
-
*
|
14
|
-
* delayed_job gem >= 3.0 installed and configured
|
63
|
+
* restforce gem >= 5.0.4 installed and configured [see below](#restforce)
|
15
64
|
|
16
65
|
### Salesforce Setup
|
17
66
|
|
@@ -38,14 +87,15 @@ Select the fields you wish to sync (Id and SystemModstamp are required).
|
|
38
87
|
|
39
88
|
*You need to do this for each object/model you want to sync.
|
40
89
|
|
41
|
-
###
|
90
|
+
### restforce
|
42
91
|
|
43
|
-
Before using the salesforce_ar_sync gem you must ensure you have the
|
92
|
+
Before using the salesforce_ar_sync gem you must ensure you have the restforce gem installed and configured
|
44
93
|
properly. Make sure each of the models you wish to sync are materialized.
|
45
94
|
|
46
95
|
````ruby
|
47
|
-
|
48
|
-
$sf_client
|
96
|
+
# Note: You can name the config to anything you want
|
97
|
+
$sf_client = Restforce::Client.new("config/salesforce.yml")
|
98
|
+
$sf_client.authenticate username: <username>, password: <password>
|
49
99
|
|
50
100
|
module SalesforceArSync::SalesforceSync
|
51
101
|
SF_CLIENT = $sf_client
|
@@ -94,7 +144,7 @@ Next you will need to tell the gem which models are syncable by adding _salesfor
|
|
94
144
|
and specifying which attributes you would like to sync.
|
95
145
|
|
96
146
|
````ruby
|
97
|
-
salesforce_syncable :sync_attributes => {:
|
147
|
+
salesforce_syncable :sync_attributes => {FirstName: :first_name, LastName: :last_name}
|
98
148
|
````
|
99
149
|
|
100
150
|
The first parameter in the _:sync_attributes_ hash is the Salesforce field name and the second is the model attribute
|
@@ -112,6 +162,7 @@ The options available to configure are
|
|
112
162
|
* __sync_enabled__: a global sync enabled flag which is a boolean true/false
|
113
163
|
* __namespace_prefix__: Namespace prefix of your Salesforce app in case you specified one
|
114
164
|
* __deletion_map__: Salesforce object names mapped to internal app name.
|
165
|
+
* __job_queue__: A symbol naming the ActiveJob queue you wish sync and delete jobs to use. (default is :default)
|
115
166
|
|
116
167
|
To generate a YAML file
|
117
168
|
|
@@ -129,7 +180,7 @@ which will create a template salesforce_ar_sync.yml in /config that looks like t
|
|
129
180
|
deletion_map:
|
130
181
|
|
131
182
|
|
132
|
-
To use the ENV variable you must pass
|
183
|
+
To use the ENV variable you must pass environment variables to rails via the _export_ command in bash or before the
|
133
184
|
initializer loads the ENV settings.
|
134
185
|
|
135
186
|
$ export SALESFORCE_AR_SYNC_ORGANIZATION_ID=123456789123456789
|
@@ -143,37 +194,12 @@ An example of adding an aliased object to the deletion map should look like the
|
|
143
194
|
Account: 'YourModelName'
|
144
195
|
|
145
196
|
### Model Options
|
146
|
-
The model can have several options set:
|
147
|
-
|
148
|
-
[__salesforce_sync_enabled__](#salesforce_sync_enabled)
|
149
|
-
|
150
|
-
[__sync_attributes__](#sync_attributes)
|
151
|
-
|
152
|
-
[__async_attributes__](#async_attributes)
|
153
|
-
|
154
|
-
[__default_attributes_for_create__](#default_attributes_for_create)
|
155
|
-
|
156
|
-
[__salesforce_id_attribute_name__](#salesforce_id_attribute_name)
|
157
|
-
|
158
|
-
[__web_id_attribute_name__](#web_id_attribute_name)
|
159
|
-
|
160
|
-
[__activerecord_web_id_attribute_name__](#activerecord_web_id_attribute_name)
|
161
|
-
|
162
|
-
[__salesforce_sync_web_id__](#salesforce_sync_web_id)
|
163
|
-
|
164
|
-
[__web_class_name__](#web_class_name)
|
165
|
-
|
166
|
-
[__salesforce_object_name__](#salesforce_object_name)
|
167
|
-
|
168
|
-
[__except__](#except)
|
169
|
-
|
170
|
-
[__unscoped_updates__](#unscoped_updates)
|
171
197
|
|
172
198
|
#### <a id="salesforce_sync_enabled"></a>salesforce_sync_enabled
|
173
199
|
Model level option to enable disable the sync, defaults to true.
|
174
200
|
|
175
201
|
````ruby
|
176
|
-
:
|
202
|
+
salesforce_sync_enabled: false
|
177
203
|
````
|
178
204
|
|
179
205
|
#### sync_attributes
|
@@ -183,7 +209,7 @@ value, you should also implement a corresponding my_method_changed? to return if
|
|
183
209
|
it will always be synced.
|
184
210
|
|
185
211
|
````ruby
|
186
|
-
:
|
212
|
+
sync_attributes: { Email: :login, FirstName: :first_name, LastName: :last_name }
|
187
213
|
````
|
188
214
|
|
189
215
|
#### async_attributes
|
@@ -191,7 +217,7 @@ An array of Salesforce attributes which should be synced asynchronously, default
|
|
191
217
|
Use this carefully, nothing is done to ensure data integrity, if multiple jobs are queued for a single object there is no way to guarentee that they are processed in order, or that the save to Salesforce will succeed.
|
192
218
|
|
193
219
|
````ruby
|
194
|
-
:
|
220
|
+
async_attributes: ['Last_Login__c', 'Login_Count__c']
|
195
221
|
````
|
196
222
|
|
197
223
|
Note: The model will fall back to synchronous sync if non-synchronous attributes are changed along with async
|
@@ -201,51 +227,59 @@ attributes
|
|
201
227
|
A hash of default attributes that should be used when we are creating a new record, defaults to empty hash.
|
202
228
|
|
203
229
|
````ruby
|
204
|
-
:
|
230
|
+
default_attributes_for_create: { password_change_required: true }
|
205
231
|
````
|
206
232
|
|
207
233
|
#### salesforce_id_attribute_name
|
208
234
|
The "Id" attribute of the corresponding Salesforce object, defaults to _Id_.
|
209
235
|
|
210
236
|
````ruby
|
211
|
-
:
|
237
|
+
salesforce_id_attribute_name: :Id
|
212
238
|
````
|
213
239
|
|
214
240
|
#### web_id_attribute_name
|
215
241
|
The field name of the web id attribute in the Salesforce Object, defaults to _WebId__c_
|
216
242
|
|
217
243
|
````ruby
|
218
|
-
:
|
244
|
+
web_id_attribute_name: :WebId__c
|
219
245
|
````
|
220
246
|
|
221
247
|
#### activerecord_web_id_attribute_name
|
222
248
|
The field name of the web id attribute in the Active Record Object, defaults to id
|
223
249
|
|
224
250
|
````ruby
|
225
|
-
:
|
251
|
+
activerecord_web_id_attribute_name: :id
|
226
252
|
````
|
227
253
|
|
228
254
|
#### salesforce_sync_web_id
|
229
255
|
Enable or disable sync of the web id, defaults to false. Use this if you have a need for the id field of the ActiveRecord model to by synced to Salesforce.
|
230
256
|
|
231
257
|
````ruby
|
232
|
-
:
|
258
|
+
salesforce_sync_web_id: false
|
259
|
+
````
|
260
|
+
|
261
|
+
#### additional_lookup_fields
|
262
|
+
Optionally can specify what fields can be used for finding the object that should be updated if object was not found by salesforce id or web id
|
263
|
+
|
264
|
+
````ruby
|
265
|
+
additional_lookup_fields: { login: :User_ID_Email__c }
|
233
266
|
````
|
234
267
|
|
268
|
+
|
235
269
|
#### web_class_name
|
236
270
|
The name of the Web Objects class. A custom value can be provided if you wish to sync to a SF object and back to a
|
237
271
|
different web object. Defaults to the model name. This would generally be used if you wanted to flatten a web object
|
238
272
|
into a larger SF object like Contact.
|
239
273
|
|
240
274
|
````ruby
|
241
|
-
:
|
275
|
+
web_class_name: 'Contact',
|
242
276
|
````
|
243
277
|
|
244
278
|
#### salesforce_object_name
|
245
279
|
Optionally holds the name of a method which will return the name of the Salesforce object to sync to, defaults to nil.
|
246
280
|
|
247
281
|
````ruby
|
248
|
-
:
|
282
|
+
salesforce_object_name: :salesforce_object_name_method_name
|
249
283
|
````
|
250
284
|
|
251
285
|
#### except
|
@@ -253,7 +287,14 @@ Optionally holds the name of a method which can contain logic to determine if a
|
|
253
287
|
method is given then only the salesforce_skip_sync attribute is used. Defaults to nil.
|
254
288
|
|
255
289
|
````ruby
|
256
|
-
:
|
290
|
+
except: :except_method_name
|
291
|
+
````
|
292
|
+
|
293
|
+
#### save_method
|
294
|
+
Optionally holds the name of a method which contains custom logic for saving on sync. Defaults to `ActiveRecord::Base#save!`
|
295
|
+
|
296
|
+
````ruby
|
297
|
+
save_method: :save_method_name
|
257
298
|
````
|
258
299
|
|
259
300
|
#### unscoped_updates
|
@@ -261,7 +302,16 @@ Enable bypassing the default scope when searching for records to update. This i
|
|
261
302
|
soft deletion strategy that can respect SF undeletion.
|
262
303
|
|
263
304
|
````ruby
|
264
|
-
:
|
305
|
+
unscoped_updates: true
|
306
|
+
````
|
307
|
+
|
308
|
+
#### readonly_fields
|
309
|
+
Optionally set fields on the salesforce object that have been defined as Read Only in Salesforce.
|
310
|
+
This helps to ensure that those fields are not synced from the model to salesforce (but still syncable the other way).
|
311
|
+
Accepts the salesforce field, not the model's fields.
|
312
|
+
|
313
|
+
````ruby
|
314
|
+
readonly_fields: %i[NumberOfPosts__c ActiveSeats__c]
|
265
315
|
````
|
266
316
|
|
267
317
|
### Stopping the Sync
|
@@ -273,6 +323,16 @@ configuration variable _SALESFORCE_AR_SYNC_CONFIG["SYNC_ENABLED"]_
|
|
273
323
|
* The model level by setting the _:salesforce_sync_enabled => false_ or _:except => :method_name_
|
274
324
|
* The instance level by setting _:salesforce_skip_sync => true_ in the instance
|
275
325
|
|
326
|
+
### Manual Sync
|
327
|
+
|
328
|
+
You can trigger a manual sync of any configured attributes. All checks to skip syncing or to sync asynchronously will still be executed.
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
my_user_record.salesforce_sync(:email)
|
332
|
+
|
333
|
+
my_user_record.salesforce_sync(:email, :phone)
|
334
|
+
```
|
335
|
+
|
276
336
|
## Examples
|
277
337
|
|
278
338
|
### Our Basic Example Model
|
@@ -291,7 +351,7 @@ class Contact < ActiveRecord::Base
|
|
291
351
|
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
|
292
352
|
attr_accessor :first_name, :last_name, :phone, :email
|
293
353
|
|
294
|
-
salesforce_syncable :
|
354
|
+
salesforce_syncable sync_attributes: { FirstName: :first_name, LastName: :last_name, Phone: :phone, Email: :email }
|
295
355
|
end
|
296
356
|
```
|
297
357
|
|
@@ -302,8 +362,8 @@ class Contact < ActiveRecord::Base
|
|
302
362
|
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
|
303
363
|
attr_accessor :first_name, :last_name, :phone, :email
|
304
364
|
|
305
|
-
salesforce_syncable :
|
306
|
-
:
|
365
|
+
salesforce_syncable sync_attributes: {FirstName: :first_name, LastName: :last_name, Phone: :phone, Email: :email},
|
366
|
+
salesforce_sync_enabled: false
|
307
367
|
end
|
308
368
|
```
|
309
369
|
|
@@ -314,8 +374,8 @@ class Contact < ActiveRecord::Base
|
|
314
374
|
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
|
315
375
|
attr_accessor :first_name, :last_name, :phone, :email
|
316
376
|
|
317
|
-
salesforce_syncable :
|
318
|
-
:
|
377
|
+
salesforce_syncable sync_attributes: {FirstName: :first_name, LastName: :last_name, Phone: :phone, Email: :email},
|
378
|
+
except: :skip_sync?
|
319
379
|
|
320
380
|
def skip_sync?
|
321
381
|
if first_name.blank?
|
@@ -339,8 +399,8 @@ class Contact < ActiveRecord::Base
|
|
339
399
|
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
|
340
400
|
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
|
341
401
|
|
342
|
-
salesforce_syncable :
|
343
|
-
:
|
402
|
+
salesforce_syncable sync_attributes: { FirstName: :first_name, LastName: :last_name, Phone: :phone, Email: :email, Last_Login_Time__c: :last_login_time },
|
403
|
+
async_attributes: ['Last_Login_Time__c']
|
344
404
|
end
|
345
405
|
```
|
346
406
|
|
@@ -351,8 +411,8 @@ class Contact < ActiveRecord::Base
|
|
351
411
|
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
|
352
412
|
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
|
353
413
|
|
354
|
-
salesforce_syncable :
|
355
|
-
:
|
414
|
+
salesforce_syncable sync_attributes: { FirstName: :first_name, LastName: :last_name, Phone: :phone, Email: :email },
|
415
|
+
default_attributes_for_create: { password_change_required: true }
|
356
416
|
end
|
357
417
|
```
|
358
418
|
|
@@ -370,9 +430,9 @@ using the 18 digit Salesforce id, but maintain our ActiveRecord relationships.
|
|
370
430
|
attributes :first_name, :last_name, :account_id
|
371
431
|
attr_accessor :first_name, :last_name, :account_id
|
372
432
|
|
373
|
-
salesforce_syncable :
|
374
|
-
:
|
375
|
-
:
|
433
|
+
salesforce_syncable sync_attributes: { FirstName: :first_name,
|
434
|
+
LastName: :last_name,
|
435
|
+
AccountId: :salesforce_account_id }
|
376
436
|
|
377
437
|
def salesforce_account_id_changed?
|
378
438
|
account_id_changed?
|
@@ -398,8 +458,8 @@ class Contact < ActiveRecord::Base
|
|
398
458
|
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
|
399
459
|
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
|
400
460
|
|
401
|
-
salesforce_syncable :
|
402
|
-
:
|
461
|
+
salesforce_syncable sync_attributes: { FirstName: :first_name, LastName: :last_name, Phone: :phone, Email: :email },
|
462
|
+
salesforce_object_name: :custom_salesforce_object_name
|
403
463
|
|
404
464
|
def custom_salesforce_object_name
|
405
465
|
"CustomContact__c"
|
@@ -428,8 +488,8 @@ Syncing inbound deletes is enabled by default, but can be configured in the Rail
|
|
428
488
|
This is done using the :sync_inbound_delete option, which can take either a boolean value, or the name of a method that returns a boolean value.
|
429
489
|
|
430
490
|
```ruby
|
431
|
-
salesforce_syncable :
|
432
|
-
|
491
|
+
salesforce_syncable sync_inbound_delete: :inbound_delete
|
492
|
+
#sync_inbound_delete: true
|
433
493
|
def inbound_delete
|
434
494
|
return self.comments.count == 0
|
435
495
|
end
|
@@ -440,8 +500,8 @@ Syncing outbound deletes to Salesforce is disabled by default, but can be config
|
|
440
500
|
This is done using the :sync_outbound_delete option, which can take either a boolean value, or the name of a method that returns a boolean value.
|
441
501
|
|
442
502
|
```ruby
|
443
|
-
salesforce_syncable :
|
444
|
-
|
503
|
+
salesforce_syncable sync_outbound_delete: :outbound_delete
|
504
|
+
#sync_outbound_delete: false
|
445
505
|
|
446
506
|
def outbound_delete
|
447
507
|
return self.is_trial_user?
|
@@ -471,6 +531,11 @@ If the SOAP handler encounters an error it will be recorded in the log of the ou
|
|
471
531
|
it to an 18 character id by running it through the tool located here:
|
472
532
|
http://cloudjedi.wordpress.com/no-fuss-salesforce-id-converter/ or by installing the Force.com Utility Belt for Chrome.
|
473
533
|
|
534
|
+
## Testing
|
535
|
+
This gem uses [Appraisal](https://github.com/thoughtbot/appraisal) to test against many versions of Rails.
|
536
|
+
|
537
|
+
Run the test script to automatically set up and run the test suite: `./test`
|
538
|
+
|
474
539
|
## Contributing
|
475
540
|
|
476
541
|
1. Fork it
|
@@ -1,8 +1,7 @@
|
|
1
1
|
module SalesforceArSync
|
2
2
|
class SoapMessageController < ::ApplicationController
|
3
|
-
|
4
3
|
protect_from_forgery with: :null_session
|
5
|
-
|
4
|
+
before_action :validate_ip_ranges
|
6
5
|
|
7
6
|
def sync_object
|
8
7
|
delayed_soap_handler SalesforceArSync::SoapHandler::Base
|
@@ -14,20 +13,18 @@ module SalesforceArSync
|
|
14
13
|
|
15
14
|
private
|
16
15
|
|
17
|
-
def delayed_soap_handler
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
render :xml => soap_handler.generate_response(ex), :status => :created
|
24
|
-
end
|
16
|
+
def delayed_soap_handler(klass, priority = 90)
|
17
|
+
soap_handler = klass.new(SalesforceArSync.config['ORGANIZATION_ID'], params)
|
18
|
+
soap_handler.process_notifications(priority) if soap_handler.sobjects
|
19
|
+
render xml: soap_handler.generate_response, status: :created
|
20
|
+
rescue Exception => ex
|
21
|
+
render xml: soap_handler.generate_response(ex), status: :created
|
25
22
|
end
|
26
23
|
|
27
|
-
# to be used in a
|
24
|
+
# to be used in a before_action, checks ip ranges specified in configuration
|
28
25
|
# and renders a 404 unless the request matches
|
29
26
|
def validate_ip_ranges
|
30
|
-
raise ActionController::RoutingError
|
27
|
+
raise ActionController::RoutingError, 'Not Found' unless SalesforceArSync::IPConstraint.new.matches?(request)
|
31
28
|
end
|
32
29
|
end
|
33
|
-
end
|
30
|
+
end
|
@@ -1,31 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SalesforceArSync
|
2
4
|
class Engine < ::Rails::Engine
|
3
|
-
initializer
|
5
|
+
initializer 'salesforce_ar_sync.load_app_instance_data' do |app|
|
4
6
|
SalesforceArSync.setup do |config|
|
5
7
|
config.app_root = app.root
|
6
8
|
|
7
|
-
#Load the configuration from the environment or a yaml file or disable it if no config present
|
8
|
-
SalesforceArSync.config =
|
9
|
-
#load the config file if we have it
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
# Load the configuration from the environment or a yaml file or disable it if no config present
|
10
|
+
SalesforceArSync.config = {}
|
11
|
+
# load the config file if we have it
|
12
|
+
config_file = "#{Rails.root}/config/salesforce_ar_sync.yml"
|
13
|
+
if FileTest.exist?(config_file)
|
14
|
+
begin
|
15
|
+
config = YAML.load_file(config_file, aliases: true)[Rails.env]
|
16
|
+
rescue ArgumentError
|
17
|
+
config = YAML.load_file(config_file)[Rails.env]
|
18
|
+
end
|
19
|
+
SalesforceArSync.config['ORGANIZATION_ID'] = config['organization_id']
|
20
|
+
SalesforceArSync.config['SYNC_ENABLED'] = config['sync_enabled']
|
21
|
+
SalesforceArSync.config['IP_RANGES'] = config['ip_ranges'].split(',').map{ |ip| ip.strip }
|
22
|
+
SalesforceArSync.config['NAMESPACE_PREFIX'] = config['namespace_prefix']
|
23
|
+
SalesforceArSync.config['DELETION_MAP'] = config['deletion_map'].stringify_keys if config['deletion_map']
|
24
|
+
SalesforceArSync.config['JOB_QUEUE'] = config['job_queue']&.to_sym || :default
|
17
25
|
end
|
18
26
|
|
19
|
-
#if we have ENV flags prefer them
|
20
|
-
SalesforceArSync.config[
|
21
|
-
SalesforceArSync.config[
|
22
|
-
SalesforceArSync.config[
|
23
|
-
SalesforceArSync.config[
|
27
|
+
# if we have ENV flags prefer them
|
28
|
+
SalesforceArSync.config['ORGANIZATION_ID'] = ENV['SALESFORCE_AR_SYNC_ORGANIZATION_ID'] if ENV['SALESFORCE_AR_SYNC_ORGANIZATION_ID']
|
29
|
+
SalesforceArSync.config['SYNC_ENABLED'] = ENV['SALESFORCE_AR_SYNC_SYNC_ENABLED'] if ENV.include? 'SALESFORCE_AR_SYNC_SYNC_ENABLED'
|
30
|
+
SalesforceArSync.config['IP_RANGES'] = ENV['SALESFORCE_AR_SYNC_IP_RANGES'].split(',').map{ |ip| ip.strip } if ENV['SALESFORCE_AR_SYNC_IP_RANGES']
|
31
|
+
SalesforceArSync.config['NAMESPACE_PREFIX'] = ENV['SALESFORCE_AR_NAMESPACE_PREFIX'] if ENV['SALESFORCE_AR_NAMESPACE_PREFIX']
|
24
32
|
SalesforceArSync.config['DELETION_MAP'] = ENV['DELETION_MAP'] if ENV['DELETION_MAP']
|
33
|
+
SalesforceArSync.config['JOB_QUEUE'] = ENV['SALESFORCE_AR_SYNC_JOB_QUEUE'] if ENV['SALESFORCE_AR_SYNC_JOB_QUEUE']
|
25
34
|
|
26
|
-
#do we have valid config options now?
|
27
|
-
if !SalesforceArSync.config[
|
28
|
-
SalesforceArSync.config[
|
35
|
+
# do we have valid config options now?
|
36
|
+
if !SalesforceArSync.config['ORGANIZATION_ID'].present? || SalesforceArSync.config['ORGANIZATION_ID'].length != 18
|
37
|
+
SalesforceArSync.config['SYNC_ENABLED'] = false
|
29
38
|
end
|
30
39
|
end
|
31
40
|
end
|
@@ -21,6 +21,10 @@ module SalesforceArSync
|
|
21
21
|
|
22
22
|
self.salesforce_object_name_method = options.has_key?(:salesforce_object_name) ? options[:salesforce_object_name] : nil
|
23
23
|
self.salesforce_skip_sync_method = options.has_key?(:except) ? options[:except] : nil
|
24
|
+
self.salesforce_save_method = options.has_key?(:save_method) ? options[:save_method] : :save!
|
25
|
+
self.additional_lookup_fields = options.has_key?(:additional_lookup_fields) ? options[:additional_lookup_fields] : nil
|
26
|
+
|
27
|
+
self.readonly_fields = options.fetch(:readonly_fields, [])
|
24
28
|
|
25
29
|
instance_eval do
|
26
30
|
before_save :salesforce_sync
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SalesforceArSync
|
4
|
+
class BaseJob < (defined?(::ApplicationJob) ? ::ApplicationJob : ActiveJob::Base)
|
5
|
+
queue_as do
|
6
|
+
SalesforceArSync.config['JOB_QUEUE']
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def prepare_class(klass)
|
12
|
+
klass.is_a?(String) ? klass.camelize.constantize : klass
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SalesforceArSync
|
4
|
+
# simple object to be serialized when asynchronously sending data to Salesforce
|
5
|
+
class SalesforceObjectSyncJob < BaseJob
|
6
|
+
def perform(web_object_name, salesforce_id, attributes)
|
7
|
+
web_object = web_object_name.to_s.constantize.find_by_salesforce_id salesforce_id
|
8
|
+
# object exists in salesforce if we call its system_mod_stamp
|
9
|
+
if web_object&.system_mod_stamp&.present?
|
10
|
+
web_object.salesforce_update_object(JSON.parse(attributes))
|
11
|
+
web_object.update_attribute(:salesforce_updated_at, web_object.system_mod_stamp)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -46,6 +46,9 @@ module SalesforceArSync
|
|
46
46
|
attr_accessor :salesforce_sync_web_id
|
47
47
|
attr_accessor :activerecord_web_id_attribute_name
|
48
48
|
|
49
|
+
# Optionally can specify what fields can be used for finding the object that should be updated
|
50
|
+
attr_accessor :additional_lookup_fields
|
51
|
+
|
49
52
|
# Optionally holds the name of a method which will return the name of the Salesforce object to sync to
|
50
53
|
attr_accessor :salesforce_object_name_method
|
51
54
|
|
@@ -53,19 +56,34 @@ module SalesforceArSync
|
|
53
56
|
# If no method is given then only the salesforce_skip_sync attribute is used.
|
54
57
|
attr_accessor :salesforce_skip_sync_method
|
55
58
|
|
59
|
+
# Optionally holds the name of a method which can provide custom logic for saving a record
|
60
|
+
# If no method is given then `ActiveRecord::Base#save!` is used
|
61
|
+
attr_accessor :salesforce_save_method
|
62
|
+
|
63
|
+
# Optionally holds a list of fields on the model that should not be synced to salesforce
|
64
|
+
# because they are classified as readonly and are calculated on the salesforce side
|
65
|
+
attr_accessor :readonly_fields
|
66
|
+
|
56
67
|
# Accepts values from an outbound message hash and will either update an existing record OR create a new record
|
57
68
|
# Firstly attempts to find an object by the salesforce_id attribute
|
58
69
|
# Secondly attempts to look an object up by it's ID (WebId__c in outbound message)
|
59
70
|
# Lastly it will create a new record setting it's salesforce_id
|
60
|
-
def salesforce_update(attributes={})
|
71
|
+
def salesforce_update(attributes = {})
|
61
72
|
raise ArgumentError, "#{salesforce_id_attribute_name} parameter required" if attributes[salesforce_id_attribute_name].blank?
|
73
|
+
|
62
74
|
data_source = unscoped_updates ? unscoped : self
|
63
75
|
object = data_source.find_by(salesforce_id: attributes[salesforce_id_attribute_name])
|
64
76
|
object ||= data_source.find_by(activerecord_web_id_attribute_name => attributes[salesforce_web_id_attribute_name]) if salesforce_sync_web_id? && attributes[salesforce_web_id_attribute_name]
|
65
77
|
|
78
|
+
if !object && additional_lookup_fields
|
79
|
+
additional_lookup_fields.each do |attribute_name, salesforce_attribute_name|
|
80
|
+
object = data_source.find_by(attribute_name => attributes[salesforce_attribute_name]) if attributes[salesforce_attribute_name]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
66
84
|
if object.nil?
|
67
85
|
object = new
|
68
|
-
salesforce_default_attributes_for_create.merge(:
|
86
|
+
salesforce_default_attributes_for_create.merge(salesforce_id: attributes[salesforce_id_attribute_name]).each_pair do |k, v|
|
69
87
|
object.send("#{k}=", v)
|
70
88
|
end
|
71
89
|
end
|
@@ -82,7 +100,7 @@ module SalesforceArSync
|
|
82
100
|
# We initialize all declared attributes as nil before mapping the values from the message
|
83
101
|
def salesforce_empty_attributes
|
84
102
|
{}.tap do |hash|
|
85
|
-
self.class.salesforce_sync_attribute_mapping.each do |key,
|
103
|
+
self.class.salesforce_sync_attribute_mapping.each do |key, _value|
|
86
104
|
hash[key] = nil
|
87
105
|
end
|
88
106
|
end
|
@@ -95,7 +113,7 @@ module SalesforceArSync
|
|
95
113
|
# create a reversed hash of value's and key's to pass to update_attributes
|
96
114
|
attributes.each do |key, value|
|
97
115
|
# make sure our sync_mapping contains the salesforce attribute AND that our object has a setter for it
|
98
|
-
hash[self.class.salesforce_sync_attribute_mapping[key.to_s].to_sym] = value if self.class.salesforce_sync_attribute_mapping.include?(key.to_s) &&
|
116
|
+
hash[self.class.salesforce_sync_attribute_mapping[key.to_s].to_sym] = value if self.class.salesforce_sync_attribute_mapping.include?(key.to_s) && respond_to?("#{self.class.salesforce_sync_attribute_mapping[key.to_s]}=")
|
99
117
|
end
|
100
118
|
|
101
119
|
# remove the web_id from hash if it exists, as we don't want to modify a web_id
|
@@ -111,29 +129,22 @@ module SalesforceArSync
|
|
111
129
|
|
112
130
|
# Gets passed the Salesforce outbound message hash of changed values and updates the corresponding model
|
113
131
|
def salesforce_process_update(attributes = {})
|
114
|
-
attributes_to_update = salesforce_attributes_to_set(
|
132
|
+
attributes_to_update = salesforce_attributes_to_set(new_record? ? attributes : salesforce_empty_attributes.merge(attributes)) # only merge empty attributes for updates, so we don't overwrite the default create attributes
|
115
133
|
attributes_to_update.each_pair do |k, v|
|
116
|
-
|
134
|
+
send("#{k}=", v)
|
117
135
|
end
|
118
136
|
|
119
137
|
# we don't want to keep going in a endless loop. SF has just updated these values.
|
120
138
|
self.salesforce_skip_sync = true
|
121
|
-
self.
|
139
|
+
self.send(self.class.salesforce_save_method)
|
122
140
|
end
|
123
141
|
|
124
|
-
# def salesforce_object_exists?
|
125
|
-
# return salesforce_object_exists_method if respond_to? salesforce_exists_method
|
126
|
-
# return salesforce_object_exists_default
|
127
|
-
# end
|
128
|
-
|
129
|
-
|
130
142
|
# Finds a salesforce record by its Id and returns nil or its SystemModstamp
|
131
143
|
def system_mod_stamp
|
132
|
-
|
133
|
-
|
144
|
+
sobject = SF_CLIENT.find(salesforce_object_name, salesforce_id)
|
145
|
+
sobject.SystemModstamp
|
134
146
|
end
|
135
147
|
|
136
|
-
|
137
148
|
def salesforce_object_exists?
|
138
149
|
return @exists_in_salesforce if @exists_in_salesforce
|
139
150
|
@exists_in_salesforce = !system_mod_stamp.nil?
|
@@ -141,90 +152,95 @@ module SalesforceArSync
|
|
141
152
|
|
142
153
|
# Checks if the passed in attribute should be updated in Salesforce.com
|
143
154
|
def salesforce_should_update_attribute?(attribute)
|
144
|
-
!
|
155
|
+
!respond_to?("#{attribute}_changed?") || (respond_to?("#{attribute}_changed?") && send("#{attribute}_changed?"))
|
145
156
|
end
|
146
157
|
|
147
158
|
# create a hash of updates to send to salesforce
|
148
|
-
def salesforce_attributes_to_update(include_all = false)
|
159
|
+
def salesforce_attributes_to_update(include_all = false, attrs = [])
|
149
160
|
{}.tap do |hash|
|
150
161
|
self.class.salesforce_sync_attribute_mapping.each do |key, value|
|
151
|
-
if
|
162
|
+
next if (attrs.any? && attrs.exclude?(value.to_sym)) || !respond_to?(value)
|
152
163
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
attribute_value = false
|
157
|
-
end
|
164
|
+
# Checkboxes in SFDC Cannot be nil. Here we check for boolean field type and set nil values to be false
|
165
|
+
attribute_value = send(value)
|
166
|
+
attribute_value = false if is_boolean?(value) && attribute_value.nil?
|
158
167
|
|
159
|
-
|
160
|
-
end
|
168
|
+
hash[key] = attribute_value if !self.class.readonly_fields&.include?(key.to_sym) && (include_all || salesforce_should_update_attribute?(value))
|
161
169
|
end
|
162
170
|
end
|
163
171
|
end
|
164
172
|
|
165
173
|
def is_boolean?(attribute)
|
166
|
-
|
174
|
+
column_for_attribute(attribute) && column_for_attribute(attribute).type == :boolean
|
167
175
|
end
|
168
176
|
|
169
177
|
def salesforce_create_object(attributes)
|
170
|
-
attributes
|
171
|
-
|
172
|
-
self.salesforce_id =
|
178
|
+
attributes[self.class.salesforce_web_id_attribute_name.to_s] = id if self.class.salesforce_sync_web_id? && !new_record?
|
179
|
+
salesforce_id = SF_CLIENT.create(salesforce_object_name, format_attributes(attributes))
|
180
|
+
self.salesforce_id = salesforce_id
|
173
181
|
@exists_in_salesforce = true
|
174
182
|
end
|
175
183
|
|
176
184
|
def salesforce_update_object(attributes)
|
177
|
-
attributes
|
178
|
-
|
185
|
+
attributes[self.class.salesforce_web_id_attribute_name.to_s] = id if self.class.salesforce_sync_web_id? && !new_record?
|
186
|
+
|
187
|
+
SF_CLIENT.update(salesforce_object_name, format_attributes(attributes).merge(Id: salesforce_id))
|
179
188
|
end
|
180
189
|
|
181
190
|
def salesforce_delete_object
|
182
|
-
if
|
183
|
-
SF_CLIENT.
|
191
|
+
if ar_sync_outbound_delete?
|
192
|
+
SF_CLIENT.destroy(salesforce_object_name, salesforce_id)
|
184
193
|
end
|
185
194
|
end
|
186
195
|
|
187
196
|
# Check to see if the user passed in a true/false, if so return that, if not then they passed int a symbol to a method
|
188
197
|
# We then call the method and use its value instead
|
189
198
|
def ar_sync_inbound_delete?
|
190
|
-
[true,false].include?(self.class.sync_inbound_delete) ? self.class.sync_inbound_delete : send(self.class.sync_inbound_delete)
|
199
|
+
[true, false].include?(self.class.sync_inbound_delete) ? self.class.sync_inbound_delete : send(self.class.sync_inbound_delete)
|
191
200
|
end
|
192
201
|
|
193
202
|
def ar_sync_outbound_delete?
|
194
|
-
[true,false].include?(self.class.sync_outbound_delete) ? self.class.sync_outbound_delete : send(self.class.sync_outbound_delete)
|
203
|
+
[true, false].include?(self.class.sync_outbound_delete) ? self.class.sync_outbound_delete : send(self.class.sync_outbound_delete)
|
195
204
|
end
|
196
205
|
|
197
206
|
# if attributes specified in the async_attributes array are the only attributes being modified, then sync the data
|
198
207
|
# via delayed_job
|
199
|
-
def salesforce_perform_async_call?
|
200
|
-
return false if
|
201
|
-
|
208
|
+
def salesforce_perform_async_call?(attributes_to_update)
|
209
|
+
return false if attributes_to_update.empty? || self.class.salesforce_async_attributes.empty?
|
210
|
+
attributes_to_update.keys.all? { |key| self.class.salesforce_async_attributes.include?(key) } && salesforce_id.present?
|
202
211
|
end
|
203
212
|
|
204
213
|
# sync model data to Salesforce, adding any Salesforce validation errors to the models errors
|
205
|
-
def salesforce_sync
|
206
|
-
return if
|
207
|
-
|
208
|
-
|
214
|
+
def salesforce_sync(*attrs)
|
215
|
+
return if salesforce_skip_sync?
|
216
|
+
|
217
|
+
attributes_to_update = salesforce_attributes_to_update(attrs.any?, attrs)
|
218
|
+
|
219
|
+
if salesforce_perform_async_call?(attributes_to_update)
|
220
|
+
SalesforceArSync::SalesforceObjectSyncJob.set(priority: 50).perform_later(
|
221
|
+
self.class.salesforce_web_class_name, salesforce_id,
|
222
|
+
attributes_to_update.to_json
|
223
|
+
)
|
209
224
|
else
|
210
225
|
if salesforce_object_exists?
|
211
|
-
salesforce_update_object(
|
226
|
+
salesforce_update_object(attributes_to_update) if attributes_to_update.present?
|
212
227
|
else
|
213
|
-
salesforce_create_object(
|
228
|
+
salesforce_create_object(attributes_to_update(!new_record?)) if salesforce_id.nil?
|
214
229
|
end
|
215
230
|
end
|
216
231
|
rescue Exception => ex
|
217
|
-
|
218
|
-
|
232
|
+
errors.add(:base, ex.message)
|
233
|
+
false
|
219
234
|
end
|
220
235
|
|
221
236
|
def sync_web_id
|
222
|
-
return false if !self.class.salesforce_sync_web_id? ||
|
223
|
-
|
237
|
+
return false if !self.class.salesforce_sync_web_id? || SalesforceArSync.config['SYNC_ENABLED'] == false
|
238
|
+
|
239
|
+
SF_CLIENT.update(salesforce_object_name, Id: salesforce_id, self.class.salesforce_web_id_attribute_name.to_s => get_activerecord_web_id) if salesforce_id
|
224
240
|
end
|
225
241
|
|
226
242
|
def get_activerecord_web_id
|
227
|
-
|
243
|
+
send(self.class.activerecord_web_id_attribute_name)
|
228
244
|
end
|
229
245
|
|
230
246
|
private
|
@@ -233,10 +249,14 @@ module SalesforceArSync
|
|
233
249
|
# This method converts all attribute that are arrays into these values
|
234
250
|
# Eg. ["A","B","C"] => "A;B;C"
|
235
251
|
def format_attributes(attributes)
|
236
|
-
attributes.each do |k,v|
|
237
|
-
attributes[k] = v.join(
|
252
|
+
attributes.each do |k, v|
|
253
|
+
attributes[k] = v.join(';') if v.is_a?(Array)
|
254
|
+
|
255
|
+
# Databasedotcom apparently did some other stuff to make sure this happened in the background
|
256
|
+
# restforce does not so need to make sure this is called on anything that can call it
|
257
|
+
attributes[k] = v.iso8601 if defined?(v.iso8601)
|
238
258
|
end
|
239
|
-
|
259
|
+
attributes
|
240
260
|
end
|
241
261
|
end
|
242
262
|
end
|
@@ -3,47 +3,50 @@ module SalesforceArSync
|
|
3
3
|
class Base
|
4
4
|
attr_reader :xml_hashed, :options, :sobjects
|
5
5
|
|
6
|
-
def initialize
|
7
|
-
@options = options
|
8
|
-
@xml_hashed = options
|
9
|
-
@organization = SalesforceArSync.config[
|
6
|
+
def initialize(_organization, options = {})
|
7
|
+
@options = options.to_unsafe_h
|
8
|
+
@xml_hashed = options.to_unsafe_h
|
9
|
+
@organization = SalesforceArSync.config['ORGANIZATION_ID']
|
10
10
|
@sobjects = collect_sobjects if valid?
|
11
11
|
end
|
12
12
|
|
13
13
|
# queues each individual record from the message for update
|
14
14
|
def process_notifications(priority = 90)
|
15
15
|
batch_process do |sobject|
|
16
|
-
|
16
|
+
SalesforceArSync::SyncObjectJob.set(
|
17
|
+
priority: priority,
|
18
|
+
wait_until: 5.seconds.from_now
|
19
|
+
).perform_later(options[:klass], sobject)
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
20
23
|
# ensures that the received message is properly formed, and that it comes from the expected Salesforce Org
|
21
24
|
def valid?
|
22
|
-
notifications = @xml_hashed.try(:[],
|
25
|
+
notifications = @xml_hashed.try(:[], 'Envelope').try(:[], 'Body').try(:[], 'notifications')
|
23
26
|
|
24
|
-
organization_id = notifications.try(:[],
|
25
|
-
|
27
|
+
organization_id = notifications.try(:[], 'OrganizationId')
|
28
|
+
!notifications.try(:[], 'Notification').nil? && organization_id == @organization # we sent this to ourselves
|
26
29
|
end
|
27
30
|
|
28
|
-
def batch_process
|
31
|
+
def batch_process
|
29
32
|
return if sobjects.nil? || !block_given?
|
30
|
-
sobjects.each do |
|
33
|
+
sobjects.each do |sobject|
|
31
34
|
yield sobject
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
35
|
-
#xml for SFDC response
|
36
|
-
#called from soap_message_controller
|
38
|
+
# xml for SFDC response
|
39
|
+
# called from soap_message_controller
|
37
40
|
def generate_response(error = nil)
|
38
41
|
response = "<Ack>#{sobjects.nil? ? false : true}</Ack>" unless error
|
39
42
|
if error
|
40
43
|
response = "<soapenv:Fault><faultcode>soap:Receiver</faultcode><faultstring>#{error.message}</faultstring></soapenv:Fault>"
|
41
44
|
end
|
42
|
-
|
45
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soapenv:Body><notificationsResponse>#{response}</notificationsResponse></soapenv:Body></soapenv:Envelope>"
|
43
46
|
end
|
44
47
|
|
45
48
|
def self.namespaced(field)
|
46
|
-
|
49
|
+
SalesforceArSync.config['NAMESPACE_PREFIX'].present? ? :"#{SalesforceArSync.config['NAMESPACE_PREFIX']}__#{field}" : :"#{field}"
|
47
50
|
end
|
48
51
|
|
49
52
|
# Get configuration for the in app deletions.
|
@@ -55,11 +58,11 @@ module SalesforceArSync
|
|
55
58
|
private
|
56
59
|
|
57
60
|
def collect_sobjects
|
58
|
-
notification = @xml_hashed[
|
61
|
+
notification = @xml_hashed['Envelope']['Body']['notifications']['Notification']
|
59
62
|
if notification.is_a? Array
|
60
|
-
return notification.collect{ |h| h[
|
63
|
+
return notification.collect { |h| h['sObject'].symbolize_keys }
|
61
64
|
else
|
62
|
-
return [notification[
|
65
|
+
return [notification['sObject'].try(:symbolize_keys)]
|
63
66
|
end
|
64
67
|
end
|
65
68
|
end
|
@@ -3,7 +3,10 @@ module SalesforceArSync
|
|
3
3
|
class Delete < SalesforceArSync::SoapHandler::Base
|
4
4
|
def process_notifications(priority = 90)
|
5
5
|
batch_process do |sobject|
|
6
|
-
SalesforceArSync::
|
6
|
+
SalesforceArSync::DeleteObjectJob.set(
|
7
|
+
priority: priority,
|
8
|
+
wait_until: 5.seconds.from_now
|
9
|
+
).perform_later(SalesforceArSync::SoapHandler::Delete, sobject)
|
7
10
|
end
|
8
11
|
end
|
9
12
|
|
data/lib/salesforce_ar_sync.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'salesforce_ar_sync/engine'
|
2
2
|
require 'salesforce_ar_sync/version'
|
3
3
|
require 'salesforce_ar_sync/extenders/salesforce_syncable'
|
4
|
-
require 'salesforce_ar_sync/salesforce_object_sync'
|
5
4
|
require 'salesforce_ar_sync/soap_handler/base'
|
6
5
|
require 'salesforce_ar_sync/soap_handler/delete'
|
7
6
|
require 'salesforce_ar_sync/ip_constraint'
|
8
|
-
require 'salesforce_ar_sync/
|
7
|
+
require 'salesforce_ar_sync/jobs/base_job'
|
8
|
+
require 'salesforce_ar_sync/jobs/salesforce_object_sync_job'
|
9
|
+
require 'salesforce_ar_sync/jobs/delete_object_job'
|
10
|
+
require 'salesforce_ar_sync/jobs/sync_object_job'
|
9
11
|
|
10
12
|
module SalesforceArSync
|
11
13
|
mattr_accessor :app_root
|
@@ -18,4 +20,4 @@ end
|
|
18
20
|
|
19
21
|
if defined?(ActiveRecord::Base)
|
20
22
|
ActiveRecord::Base.extend SalesforceArSync::Extenders::SalesforceSyncable
|
21
|
-
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: salesforce_ar_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Halliday
|
@@ -9,55 +9,69 @@ authors:
|
|
9
9
|
- Andrew Coates
|
10
10
|
- Devon Noonan
|
11
11
|
- Liam Nediger
|
12
|
-
autorequire:
|
12
|
+
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2025-07-15 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
|
-
name:
|
18
|
+
name: actionpack-xml_parser
|
19
19
|
requirement: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- - "
|
21
|
+
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: '
|
23
|
+
version: '0'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
27
27
|
requirements:
|
28
|
-
- - "
|
28
|
+
- - ">="
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: '
|
30
|
+
version: '0'
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
|
-
name:
|
32
|
+
name: rails
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
34
34
|
requirements:
|
35
35
|
- - ">="
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: '
|
37
|
+
version: '5'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
requirements:
|
42
42
|
- - ">="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: '
|
44
|
+
version: '5'
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
|
-
name:
|
46
|
+
name: rexml
|
47
47
|
requirement: !ruby/object:Gem::Requirement
|
48
48
|
requirements:
|
49
|
-
- - "
|
49
|
+
- - "~>"
|
50
50
|
- !ruby/object:Gem::Version
|
51
|
-
version: '
|
51
|
+
version: '3.2'
|
52
|
+
type: :runtime
|
53
|
+
prerelease: false
|
54
|
+
version_requirements: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - "~>"
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '3.2'
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: ammeter
|
61
|
+
requirement: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - "~>"
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 1.1.2
|
52
66
|
type: :development
|
53
67
|
prerelease: false
|
54
68
|
version_requirements: !ruby/object:Gem::Requirement
|
55
69
|
requirements:
|
56
|
-
- - "
|
70
|
+
- - "~>"
|
57
71
|
- !ruby/object:Gem::Version
|
58
|
-
version:
|
72
|
+
version: 1.1.2
|
59
73
|
- !ruby/object:Gem::Dependency
|
60
|
-
name:
|
74
|
+
name: rake
|
61
75
|
requirement: !ruby/object:Gem::Requirement
|
62
76
|
requirements:
|
63
77
|
- - ">="
|
@@ -71,7 +85,7 @@ dependencies:
|
|
71
85
|
- !ruby/object:Gem::Version
|
72
86
|
version: '0'
|
73
87
|
- !ruby/object:Gem::Dependency
|
74
|
-
name:
|
88
|
+
name: rspec
|
75
89
|
requirement: !ruby/object:Gem::Requirement
|
76
90
|
requirements:
|
77
91
|
- - ">="
|
@@ -85,7 +99,7 @@ dependencies:
|
|
85
99
|
- !ruby/object:Gem::Version
|
86
100
|
version: '0'
|
87
101
|
- !ruby/object:Gem::Dependency
|
88
|
-
name:
|
102
|
+
name: sqlite3
|
89
103
|
requirement: !ruby/object:Gem::Requirement
|
90
104
|
requirements:
|
91
105
|
- - ">="
|
@@ -99,21 +113,21 @@ dependencies:
|
|
99
113
|
- !ruby/object:Gem::Version
|
100
114
|
version: '0'
|
101
115
|
- !ruby/object:Gem::Dependency
|
102
|
-
name:
|
116
|
+
name: vcr
|
103
117
|
requirement: !ruby/object:Gem::Requirement
|
104
118
|
requirements:
|
105
|
-
- - "
|
119
|
+
- - ">="
|
106
120
|
- !ruby/object:Gem::Version
|
107
|
-
version:
|
121
|
+
version: '0'
|
108
122
|
type: :development
|
109
123
|
prerelease: false
|
110
124
|
version_requirements: !ruby/object:Gem::Requirement
|
111
125
|
requirements:
|
112
|
-
- - "
|
126
|
+
- - ">="
|
113
127
|
- !ruby/object:Gem::Version
|
114
|
-
version:
|
128
|
+
version: '0'
|
115
129
|
- !ruby/object:Gem::Dependency
|
116
|
-
name:
|
130
|
+
name: webmock
|
117
131
|
requirement: !ruby/object:Gem::Requirement
|
118
132
|
requirements:
|
119
133
|
- - ">="
|
@@ -127,19 +141,19 @@ dependencies:
|
|
127
141
|
- !ruby/object:Gem::Version
|
128
142
|
version: '0'
|
129
143
|
- !ruby/object:Gem::Dependency
|
130
|
-
name:
|
144
|
+
name: restforce
|
131
145
|
requirement: !ruby/object:Gem::Requirement
|
132
146
|
requirements:
|
133
|
-
- - "
|
147
|
+
- - "~>"
|
134
148
|
- !ruby/object:Gem::Version
|
135
|
-
version:
|
149
|
+
version: 5.0.5
|
136
150
|
type: :runtime
|
137
151
|
prerelease: false
|
138
152
|
version_requirements: !ruby/object:Gem::Requirement
|
139
153
|
requirements:
|
140
|
-
- - "
|
154
|
+
- - "~>"
|
141
155
|
- !ruby/object:Gem::Version
|
142
|
-
version:
|
156
|
+
version: 5.0.5
|
143
157
|
description: ActiveRecord extension & rails engine for syncing data with Salesforce.com
|
144
158
|
email:
|
145
159
|
- mhalliday@infotech.com
|
@@ -163,8 +177,10 @@ files:
|
|
163
177
|
- lib/salesforce_ar_sync/engine.rb
|
164
178
|
- lib/salesforce_ar_sync/extenders/salesforce_syncable.rb
|
165
179
|
- lib/salesforce_ar_sync/ip_constraint.rb
|
166
|
-
- lib/salesforce_ar_sync/
|
167
|
-
- lib/salesforce_ar_sync/
|
180
|
+
- lib/salesforce_ar_sync/jobs/base_job.rb
|
181
|
+
- lib/salesforce_ar_sync/jobs/delete_object_job.rb
|
182
|
+
- lib/salesforce_ar_sync/jobs/salesforce_object_sync_job.rb
|
183
|
+
- lib/salesforce_ar_sync/jobs/sync_object_job.rb
|
168
184
|
- lib/salesforce_ar_sync/salesforce_sync.rb
|
169
185
|
- lib/salesforce_ar_sync/soap_handler/base.rb
|
170
186
|
- lib/salesforce_ar_sync/soap_handler/delete.rb
|
@@ -172,7 +188,7 @@ files:
|
|
172
188
|
homepage: http://github.com/InfoTech/
|
173
189
|
licenses: []
|
174
190
|
metadata: {}
|
175
|
-
post_install_message:
|
191
|
+
post_install_message:
|
176
192
|
rdoc_options: []
|
177
193
|
require_paths:
|
178
194
|
- lib
|
@@ -187,9 +203,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
203
|
- !ruby/object:Gem::Version
|
188
204
|
version: '0'
|
189
205
|
requirements: []
|
190
|
-
|
191
|
-
|
192
|
-
signing_key:
|
206
|
+
rubygems_version: 3.5.22
|
207
|
+
signing_key:
|
193
208
|
specification_version: 4
|
194
209
|
summary: ActiveRecord extension & rails engine for syncing data with Salesforce.com
|
195
210
|
test_files: []
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module SalesforceArSync
|
2
|
-
# simple object to be serialized when asynchronously sending data to Salesforce
|
3
|
-
class SalesforceObjectSync < Struct.new(:web_object_name, :salesforce_id, :attributes)
|
4
|
-
def perform
|
5
|
-
web_object = "#{web_object_name}".constantize.find_by_salesforce_id salesforce_id
|
6
|
-
#object exists in salesforce if we call its system_mod_stamp
|
7
|
-
if (system_mod_stamp = web_object.system_mod_stamp)
|
8
|
-
web_object.salesforce_update_object(web_object.salesforce_attributes_to_update(true))
|
9
|
-
web_object.update_attribute(:salesforce_updated_at, system_mod_stamp)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|