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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 49700928b08b5846bf589c0f17a6b3e901cd95a2
4
- data.tar.gz: 383abff58a8729112727c7b5f8ae91c912215002
2
+ SHA256:
3
+ metadata.gz: 5a366e664f19f09c3ad3f31088f75d90e4d11ba5b2708d59a5a8e470ebc0276c
4
+ data.tar.gz: 2f37638a57e5769698051c8cbbdcaa49dce50de4eae90d12da6a59dabc5b0765
5
5
  SHA512:
6
- metadata.gz: e0138c4ea8225d242f7263fda308178a103a0797fb8c0ac430feda439a189942190e5ec3a230d32f883bf0faea6f24c7937527d796bea801920f1c4e9aa1492b
7
- data.tar.gz: be0fd33f8551419b04be48a7424afeb62ce43d6f43bf083e10a25487bc3ce52eb7581caa839bbf05902c68b9b9f3667406f484667633b29d394ab2d1e3d93e14
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 databasedotcom.
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
- * databasedotcom gem >= 1.3 installed and configured [see below](#databasedotcom)
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
- ### databasedotcom
90
+ ### restforce
42
91
 
43
- Before using the salesforce_ar_sync gem you must ensure you have the databasedotcom gem installed and configured
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
- $sf_client = Databasedotcom::Client.new("config/databasedotcom.yml")
48
- $sf_client.authenticate :username => <username>, :password => <password>
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 => {:FirstName => :first_name, :LastName => :last_name}
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 environemnt variables to rails via the _export_ command in bash or before the
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
- :salesforce_sync_enabled => false
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
- :sync_attributes => { :Email => :login, :FirstName => :first_name, :LastName => :last_name }
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
- :async_attributes => ["Last_Login__c", "Login_Count__c"]
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
- :default_attributes_for_create => {:password_change_required => true}
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
- :salesforce_id_attribute_name => :Id
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
- :web_id_attribute_name => :WebId__c
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
- :activerecord_web_id_attribute_name => :id
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
- :salesforce_sync_web_id => false
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
- :web_class_name => 'Contact',
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
- :salesforce_object_name => :salesforce_object_name_method_name
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
- :except => :except_method_name
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
- :unscoped_updates => true
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 :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email}
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 :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
306
- :salesforce_sync_enabled => false
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 :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
318
- :except => :skip_sync?
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 :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email, :Last_Login_Time__c => :last_login_time},
343
- :async_attributes => ["Last_Login_Time__c"]
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 :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
355
- :default_attributes_for_create => {:password_change_required => true}
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 :sync_attributes => { :FirstName => :first_name,
374
- :LastName => :last_name,
375
- :AccountId => :salesforce_account_id }
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 :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
402
- :salesforce_object_name => :custom_salesforce_object_name
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 :sync_inbound_delete => :inbound_delete
432
- #:sync_inbound_delete => true
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 :sync_outbound_delete => :outbound_delete
444
- #:sync_outbound_delete => false
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
- before_filter :validate_ip_ranges
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 (klass, priority = 90)
18
- begin
19
- soap_handler = klass.new(SalesforceArSync.config["ORGANIZATION_ID"], params)
20
- soap_handler.process_notifications(priority) if soap_handler.sobjects
21
- render :xml => soap_handler.generate_response, :status => :created
22
- rescue Exception => ex
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 before_filter, checks ip ranges specified in configuration
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.new('Not Found') unless SalesforceArSync::IPConstraint.new.matches?(request)
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 "salesforce_ar_sync.load_app_instance_data" do |app|
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 = Hash.new
9
- #load the config file if we have it
10
- if FileTest.exist?("#{Rails.root}/config/salesforce_ar_sync.yml")
11
- config = YAML.load_file("#{Rails.root}/config/salesforce_ar_sync.yml")[Rails.env]
12
- SalesforceArSync.config["ORGANIZATION_ID"] = config['organization_id']
13
- SalesforceArSync.config["SYNC_ENABLED"] = config['sync_enabled']
14
- SalesforceArSync.config["IP_RANGES"] = config['ip_ranges'].split(',').map{ |ip| ip.strip }
15
- SalesforceArSync.config["NAMESPACE_PREFIX"] = config['namespace_prefix']
16
- SalesforceArSync.config['DELETION_MAP'] = config['deletion_map'].stringify_keys
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["ORGANIZATION_ID"] = ENV["SALESFORCE_AR_SYNC_ORGANIZATION_ID"] if ENV["SALESFORCE_AR_SYNC_ORGANIZATION_ID"]
21
- SalesforceArSync.config["SYNC_ENABLED"] = ENV["SALESFORCE_AR_SYNC_SYNC_ENABLED"] if ENV.include? "SALESFORCE_AR_SYNC_SYNC_ENABLED"
22
- SalesforceArSync.config["IP_RANGES"] = ENV["SALESFORCE_AR_SYNC_IP_RANGES"].split(',').map{ |ip| ip.strip } if ENV["SALESFORCE_AR_SYNC_IP_RANGES"]
23
- SalesforceArSync.config["NAMESPACE_PREFIX"] = ENV["SALESFORCE_AR_NAMESPACE_PREFIX"] if ENV["SALESFORCE_AR_NAMESPACE_PREFIX"]
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["ORGANIZATION_ID"].present? || SalesforceArSync.config["ORGANIZATION_ID"].length != 18
28
- SalesforceArSync.config["SYNC_ENABLED"] = false
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SalesforceArSync
4
+ class DeleteObjectJob < BaseJob
5
+ def perform(klass, sobject)
6
+ prepare_class(klass).delete_object(sobject)
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SalesforceArSync
4
+ class SyncObjectJob < BaseJob
5
+ def perform(klass, sobject)
6
+ prepare_class(klass).salesforce_update(sobject)
7
+ end
8
+ end
9
+ 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(:salesforce_id => attributes[salesforce_id_attribute_name]).each_pair do |k, v|
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, value|
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) && self.respond_to?("#{self.class.salesforce_sync_attribute_mapping[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(self.new_record? ? attributes : salesforce_empty_attributes.merge(attributes)) # only merge empty attributes for updates, so we don't overwrite the default create attributes
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
- self.send("#{k}=", v)
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.save!
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
- hash = JSON.parse(SF_CLIENT.http_get("/services/data/v#{SF_CLIENT.version}/query", :q => "SELECT SystemModstamp FROM #{salesforce_object_name} WHERE Id = '#{salesforce_id}'").body)
133
- hash["records"].first.try(:[], "SystemModstamp")
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
- !self.respond_to?("#{attribute}_changed?") || (self.respond_to?("#{attribute}_changed?") && self.send("#{attribute}_changed?"))
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 self.respond_to?(value)
162
+ next if (attrs.any? && attrs.exclude?(value.to_sym)) || !respond_to?(value)
152
163
 
153
- #Checkboxes in SFDC Cannot be nil. Here we check for boolean field type and set nil values to be false
154
- attribute_value = self.send(value)
155
- if is_boolean?(value) && attribute_value.nil?
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
- hash[key] = attribute_value if include_all || salesforce_should_update_attribute?(value)
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
- self.column_for_attribute(attribute) && self.column_for_attribute(attribute).type == :boolean
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.merge!(self.class.salesforce_web_id_attribute_name.to_s => id) if self.class.salesforce_sync_web_id? && !new_record?
171
- result = SF_CLIENT.http_post("/services/data/v#{SF_CLIENT.version}/sobjects/#{salesforce_object_name}", format_attributes(attributes).to_json)
172
- self.salesforce_id = JSON.parse(result.body)["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.merge!(self.class.salesforce_web_id_attribute_name.to_s => id) if self.class.salesforce_sync_web_id? && !new_record?
178
- SF_CLIENT.http_patch("/services/data/v#{SF_CLIENT.version}/sobjects/#{salesforce_object_name}/#{salesforce_id}", format_attributes(attributes).to_json)
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 self.ar_sync_outbound_delete?
183
- SF_CLIENT.http_delete("/services/data/v#{SF_CLIENT.version}/sobjects/#{salesforce_object_name}/#{salesforce_id}")
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 salesforce_attributes_to_update.empty? || self.class.salesforce_async_attributes.empty?
201
- salesforce_attributes_to_update.keys.all? {|key| self.class.salesforce_async_attributes.include?(key) } && salesforce_id.present?
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 self.salesforce_skip_sync?
207
- if salesforce_perform_async_call?
208
- Delayed::Job.enqueue(SalesforceArSync::SalesforceObjectSync.new(self.class.salesforce_web_class_name, salesforce_id, salesforce_attributes_to_update), :priority => 50)
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(salesforce_attributes_to_update) if salesforce_attributes_to_update.present?
226
+ salesforce_update_object(attributes_to_update) if attributes_to_update.present?
212
227
  else
213
- salesforce_create_object(salesforce_attributes_to_update(!new_record?)) if salesforce_id.nil?
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
- self.errors[:base] << ex.message
218
- return false
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? || self.salesforce_skip_sync?
223
- SF_CLIENT.http_patch("/services/data/v#{SF_CLIENT.version}/sobjects/#{salesforce_object_name}/#{salesforce_id}", { self.class.salesforce_web_id_attribute_name.to_s => get_activerecord_web_id }.to_json) if salesforce_id
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
- self.send(self.class.activerecord_web_id_attribute_name)
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(";") if v.is_a?(Array)
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
- return attributes
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 organization, options = {}
7
- @options = options
8
- @xml_hashed = options
9
- @organization = SalesforceArSync.config["ORGANIZATION_ID"]
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
- options[:klass].camelize.constantize.delay(:priority => priority, :run_at => 5.seconds.from_now).salesforce_update(sobject)
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(:[], "Envelope").try(:[], "Body").try(:[], "notifications")
25
+ notifications = @xml_hashed.try(:[], 'Envelope').try(:[], 'Body').try(:[], 'notifications')
23
26
 
24
- organization_id = notifications.try(:[], "OrganizationId")
25
- return !notifications.try(:[], "Notification").nil? && organization_id == @organization # we sent this to ourselves
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(&block)
31
+ def batch_process
29
32
  return if sobjects.nil? || !block_given?
30
- sobjects.each do | sobject |
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
- return "<?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>"
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
- SalesforceArSync.config["NAMESPACE_PREFIX"].present? ? :"#{SalesforceArSync.config["NAMESPACE_PREFIX"]}__#{field}" : :"#{field}"
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["Envelope"]["Body"]["notifications"]["Notification"]
61
+ notification = @xml_hashed['Envelope']['Body']['notifications']['Notification']
59
62
  if notification.is_a? Array
60
- return notification.collect{ |h| h["sObject"].symbolize_keys}
63
+ return notification.collect { |h| h['sObject'].symbolize_keys }
61
64
  else
62
- return [notification["sObject"].try(:symbolize_keys)]
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::SoapHandler::Delete.delay(priority: priority, run_at: 5.seconds.from_now).delete_object(sobject)
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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SalesforceArSync
2
- VERSION = "2.0.2"
4
+ VERSION = '5.2.0'
3
5
  end
@@ -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/railtie'
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.2
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: 2016-08-18 00:00:00.000000000 Z
15
+ date: 2025-07-15 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
- name: rails
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: '4.0'
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: '4.0'
30
+ version: '0'
31
31
  - !ruby/object:Gem::Dependency
32
- name: actionpack-xml_parser
32
+ name: rails
33
33
  requirement: !ruby/object:Gem::Requirement
34
34
  requirements:
35
35
  - - ">="
36
36
  - !ruby/object:Gem::Version
37
- version: '0'
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: '0'
44
+ version: '5'
45
45
  - !ruby/object:Gem::Dependency
46
- name: rake
46
+ name: rexml
47
47
  requirement: !ruby/object:Gem::Requirement
48
48
  requirements:
49
- - - ">="
49
+ - - "~>"
50
50
  - !ruby/object:Gem::Version
51
- version: '0'
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: '0'
72
+ version: 1.1.2
59
73
  - !ruby/object:Gem::Dependency
60
- name: rspec
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: webmock
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: vcr
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: ammeter
116
+ name: vcr
103
117
  requirement: !ruby/object:Gem::Requirement
104
118
  requirements:
105
- - - "~>"
119
+ - - ">="
106
120
  - !ruby/object:Gem::Version
107
- version: 1.1.2
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: 1.1.2
128
+ version: '0'
115
129
  - !ruby/object:Gem::Dependency
116
- name: sqlite3
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: databasedotcom
144
+ name: restforce
131
145
  requirement: !ruby/object:Gem::Requirement
132
146
  requirements:
133
- - - ">="
147
+ - - "~>"
134
148
  - !ruby/object:Gem::Version
135
- version: '0'
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: '0'
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/railtie.rb
167
- - lib/salesforce_ar_sync/salesforce_object_sync.rb
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
- rubyforge_project:
191
- rubygems_version: 2.6.6
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,7 +0,0 @@
1
- module SalesforceArSync
2
- class Railtie < Rails::Railtie
3
- initializer 'salesforce_ar_sync.insert_middleware' do |app|
4
- app.config.middleware.insert_after ActionDispatch::ParamsParser, ActionDispatch::XmlParamsParser
5
- end
6
- end
7
- end
@@ -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