salesforce_ar_sync 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +7 -14
- data/app/controllers/salesforce_ar_sync/soap_message_controller.rb +31 -0
- data/config/routes.rb +4 -0
- data/lib/salesforce_ar_sync/engine.rb +5 -9
- data/lib/salesforce_ar_sync/extenders/salesforce_syncable.rb +2 -10
- data/lib/salesforce_ar_sync/salesforce_object_sync.rb +7 -10
- data/lib/salesforce_ar_sync/salesforce_sync.rb +31 -26
- data/lib/salesforce_ar_sync/version.rb +1 -1
- metadata +5 -3
data/README.md
CHANGED
@@ -46,8 +46,10 @@ properly. Make sure each of the models you wish to sync are materialized.
|
|
46
46
|
````ruby
|
47
47
|
$sf_client = Databasedotcom::Client.new("config/databasedotcom.yml")
|
48
48
|
$sf_client.authenticate :username => <username>, :password => <password>
|
49
|
-
|
50
|
-
|
49
|
+
|
50
|
+
module SalesforceArSync::SalesforceSync
|
51
|
+
SF_CLIENT = $sf_client
|
52
|
+
end
|
51
53
|
````
|
52
54
|
|
53
55
|
### Gem Installation
|
@@ -140,8 +142,7 @@ The model can have several options set:
|
|
140
142
|
[__web_id_attribute_name__](#web_id_attribute_name)
|
141
143
|
[__salesforce_sync_web_id__](#salesforce_sync_web_id)
|
142
144
|
[__web_class_name__](#web_class_name)
|
143
|
-
[__salesforce_object_name__](#salesforce_object_name)
|
144
|
-
[__salesforce_object__](#salesforce_object)
|
145
|
+
[__salesforce_object_name__](#salesforce_object_name)
|
145
146
|
[__except__](#except)
|
146
147
|
|
147
148
|
#### <a id="salesforce_sync_enabled"></a>salesforce_sync_enabled
|
@@ -216,14 +217,6 @@ Optionally holds the name of a method which will return the name of the Salesfor
|
|
216
217
|
:salesforce_object_name => :salesforce_object_name_method_name
|
217
218
|
````
|
218
219
|
|
219
|
-
#### salesforce_object
|
220
|
-
Optionally holds the name of a method which will retrieve a Salesforce object. Default implementation is called if no
|
221
|
-
method is specified, defaults to nil.
|
222
|
-
|
223
|
-
````ruby
|
224
|
-
:salesforce_object => :salesforce_object_method_name
|
225
|
-
````
|
226
|
-
|
227
220
|
#### except
|
228
221
|
Optionally holds the name of a method which can contain logic to determine if a record should be synced on save. If no
|
229
222
|
method is given then only the salesforce_skip_sync attribute is used. Defaults to nil.
|
@@ -367,9 +360,9 @@ class Contact < ActiveRecord::Base
|
|
367
360
|
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
|
368
361
|
|
369
362
|
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
|
370
|
-
:
|
363
|
+
:salesforce_object_name => :custom_salesforce_object_name
|
371
364
|
|
372
|
-
def
|
365
|
+
def custom_salesforce_object_name
|
373
366
|
"CustomContact__c"
|
374
367
|
end
|
375
368
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SalesforceArSync
|
2
|
+
class SoapMessageController < ::ApplicationController
|
3
|
+
before_filter :validate_ip_ranges
|
4
|
+
|
5
|
+
def sync_object
|
6
|
+
delayed_soap_handler SalesforceArSync::SoapHandler::Base
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete
|
10
|
+
delayed_soap_handler SalesforceArSync::SoapHandler::Delete
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def delayed_soap_handler (klass, priority = 90)
|
16
|
+
begin
|
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
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# to be used in a before_filter, checks ip ranges specified in configuration
|
26
|
+
# and renders a 404 unless the request matches
|
27
|
+
def validate_ip_ranges
|
28
|
+
raise ActionController::RoutingError.new('Not Found') unless SalesforceArSync::IPConstraint.new.matches?(request)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/config/routes.rb
ADDED
@@ -6,16 +6,12 @@ module SalesforceArSync
|
|
6
6
|
|
7
7
|
#Load the configuration from the environment or a yaml file or disable it if no config present
|
8
8
|
SalesforceArSync.config = Hash.new
|
9
|
-
|
10
9
|
#load the config file if we have it
|
11
10
|
if FileTest.exist?("#{Rails.root}/config/salesforce_ar_sync.yml")
|
12
|
-
config = YAML.load_file("#{Rails.root}/config/salesforce_ar_sync.yml")
|
13
|
-
config = config[
|
14
|
-
|
15
|
-
|
16
|
-
SalesforceArSync.config["SYNC_ENABLED"] = config['sync_enabled']
|
17
|
-
SalesforceArSync.config["IP_RANGES"] = config['ip_ranges'].split(',').map{ |ip| ip.strip }
|
18
|
-
end
|
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 }
|
19
15
|
end
|
20
16
|
|
21
17
|
#if we have ENV flags prefer them
|
@@ -30,4 +26,4 @@ module SalesforceArSync
|
|
30
26
|
end
|
31
27
|
end
|
32
28
|
end
|
33
|
-
end
|
29
|
+
end
|
@@ -15,12 +15,11 @@ module SalesforceArSync
|
|
15
15
|
self.salesforce_web_class_name = options.has_key?(:web_class_name) ? options[:web_class_name] : self.name
|
16
16
|
|
17
17
|
self.salesforce_object_name_method = options.has_key?(:salesforce_object_name) ? options[:salesforce_object_name] : nil
|
18
|
-
self.salesforce_object_method = options.has_key?(:salesforce_object) ? options[:salesforce_object] : nil
|
19
18
|
self.salesforce_skip_sync_method = options.has_key?(:except) ? options[:except] : nil
|
20
19
|
|
21
20
|
instance_eval do
|
22
21
|
before_save :salesforce_sync
|
23
|
-
after_create :sync_web_id
|
22
|
+
after_create :sync_web_id
|
24
23
|
|
25
24
|
def salesforce_sync_web_id?
|
26
25
|
self.salesforce_sync_web_id
|
@@ -35,13 +34,6 @@ module SalesforceArSync
|
|
35
34
|
return self.class.name
|
36
35
|
end
|
37
36
|
|
38
|
-
# Calls a method, if provided, to retrieve an object from Salesforce. Calls the default implementation if
|
39
|
-
# no custom method is specified
|
40
|
-
def salesforce_object
|
41
|
-
return send(self.class.salesforce_object_method) if self.class.salesforce_object_method.present?
|
42
|
-
return send(:salesforce_object_default)
|
43
|
-
end
|
44
|
-
|
45
37
|
# Calls a method, if provided, to determine if a record should be synced to Salesforce.
|
46
38
|
# The salesforce_skip_sync instance variable is also used.
|
47
39
|
# The SALESFORCE_AR_SYNC_ENABLED flag overrides all the others if set to false
|
@@ -54,4 +46,4 @@ module SalesforceArSync
|
|
54
46
|
end
|
55
47
|
end
|
56
48
|
end
|
57
|
-
end
|
49
|
+
end
|
@@ -1,16 +1,13 @@
|
|
1
1
|
module SalesforceArSync
|
2
2
|
# simple object to be serialized when asynchronously sending data to Salesforce
|
3
|
-
class SalesforceObjectSync < Struct.new(:web_object_name, :
|
3
|
+
class SalesforceObjectSync < Struct.new(:web_object_name, :salesforce_id, :attributes)
|
4
4
|
def perform
|
5
|
-
|
6
|
-
|
7
|
-
if
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
web_object = "#{web_object_name}".constantize.find_by_salesforce_id salesforce_id
|
12
|
-
web_object.update_attribute(:salesforce_updated_at, sf_object.SystemModstamp) unless web_object.nil?
|
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)
|
13
10
|
end
|
14
11
|
end
|
15
12
|
end
|
16
|
-
end
|
13
|
+
end
|
@@ -39,11 +39,7 @@ module SalesforceArSync
|
|
39
39
|
|
40
40
|
# Optionally holds the name of a method which will return the name of the Salesforce object to sync to
|
41
41
|
attr_accessor :salesforce_object_name_method
|
42
|
-
|
43
|
-
# Optionally holds the name of a method which will retrieve a Salesforce object. Default implementation is called
|
44
|
-
# if no method is specified
|
45
|
-
attr_accessor :salesforce_object_method
|
46
|
-
|
42
|
+
|
47
43
|
# Optionally holds the name of a method which can contain logic to determine if a record should be synced on save.
|
48
44
|
# If no method is given then only the salesforce_skip_sync attribute is used.
|
49
45
|
attr_accessor :salesforce_skip_sync_method
|
@@ -116,16 +112,22 @@ module SalesforceArSync
|
|
116
112
|
self.save!
|
117
113
|
end
|
118
114
|
|
119
|
-
|
120
|
-
|
121
|
-
return
|
122
|
-
|
123
|
-
|
115
|
+
# def salesforce_object_exists?
|
116
|
+
# return salesforce_object_exists_method if respond_to? salesforce_exists_method
|
117
|
+
# return salesforce_object_exists_default
|
118
|
+
# end
|
119
|
+
|
120
|
+
|
121
|
+
# Finds a salesforce record by its Id and returns nil or its SystemModstamp
|
122
|
+
def system_mod_stamp
|
123
|
+
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)
|
124
|
+
hash["records"].first.try(:[], "SystemModstamp")
|
125
|
+
end
|
124
126
|
|
125
|
-
# Look up and link object by web id
|
126
|
-
@sf_object ||= "Databasedotcom::#{self.salesforce_object_name}".constantize.send("find_by_#{self.class.salesforce_web_id_attribute_name.to_s}", id) if self.class.salesforce_sync_web_id? && !new_record?
|
127
127
|
|
128
|
-
|
128
|
+
def salesforce_object_exists?
|
129
|
+
return @exists_in_salesforce if @exists_in_salesforce
|
130
|
+
@exists_in_salesforce = !system_mod_stamp.nil?
|
129
131
|
end
|
130
132
|
|
131
133
|
# Checks if the passed in attribute should be updated in Salesforce.com
|
@@ -157,12 +159,14 @@ module SalesforceArSync
|
|
157
159
|
|
158
160
|
def salesforce_create_object(attributes)
|
159
161
|
attributes.merge!(self.class.salesforce_web_id_attribute_name.to_s => id) if self.class.salesforce_sync_web_id? && !new_record?
|
160
|
-
|
162
|
+
result = SF_CLIENT.http_post("/services/data/v#{SF_CLIENT.version}/sobjects/#{salesforce_object_name}", attributes.to_json)
|
163
|
+
self.salesforce_id = JSON.parse(result.body)["id"]
|
164
|
+
@exists_in_salesforce = true
|
161
165
|
end
|
162
166
|
|
163
167
|
def salesforce_update_object(attributes)
|
164
168
|
attributes.merge!(self.class.salesforce_web_id_attribute_name.to_s => id) if self.class.salesforce_sync_web_id? && !new_record?
|
165
|
-
|
169
|
+
SF_CLIENT.http_patch("/services/data/v#{SF_CLIENT.version}/sobjects/#{salesforce_object_name}/#{salesforce_id}", attributes.to_json)
|
166
170
|
end
|
167
171
|
|
168
172
|
# if attributes specified in the async_attributes array are the only attributes being modified, then sync the data
|
@@ -175,23 +179,24 @@ module SalesforceArSync
|
|
175
179
|
# sync model data to Salesforce, adding any Salesforce validation errors to the models errors
|
176
180
|
def salesforce_sync
|
177
181
|
return if self.salesforce_skip_sync?
|
178
|
-
|
179
182
|
if salesforce_perform_async_call?
|
180
|
-
Delayed::Job.enqueue(SalesforceArSync::SalesforceObjectSync.new(self.class.salesforce_web_class_name,
|
183
|
+
Delayed::Job.enqueue(SalesforceArSync::SalesforceObjectSync.new(self.class.salesforce_web_class_name, salesforce_id, salesforce_attributes_to_update), :priority => 50)
|
181
184
|
else
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
185
|
+
if salesforce_object_exists?
|
186
|
+
salesforce_update_object(salesforce_attributes_to_update) if salesforce_attributes_to_update.present?
|
187
|
+
else
|
188
|
+
salesforce_create_object(salesforce_attributes_to_update(!new_record?)) if salesforce_id.nil?
|
189
|
+
end
|
186
190
|
end
|
187
191
|
rescue Exception => ex
|
188
192
|
self.errors[:base] << ex.message
|
189
193
|
return false
|
190
194
|
end
|
191
|
-
|
192
|
-
def sync_web_id
|
195
|
+
|
196
|
+
def sync_web_id
|
193
197
|
return false if !self.class.salesforce_sync_web_id? || self.salesforce_skip_sync?
|
194
|
-
|
195
|
-
end
|
198
|
+
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 => id }.to_json) if salesforce_id
|
199
|
+
end
|
200
|
+
|
196
201
|
end
|
197
|
-
end
|
202
|
+
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: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date:
|
16
|
+
date: 2013-04-24 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: rails
|
@@ -185,6 +185,8 @@ files:
|
|
185
185
|
- lib/salesforce_ar_sync/soap_handler/delete.rb
|
186
186
|
- lib/salesforce_ar_sync/version.rb
|
187
187
|
- lib/salesforce_ar_sync.rb
|
188
|
+
- app/controllers/salesforce_ar_sync/soap_message_controller.rb
|
189
|
+
- config/routes.rb
|
188
190
|
homepage: http://github.com/InfoTech/
|
189
191
|
licenses: []
|
190
192
|
post_install_message:
|
@@ -205,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
207
|
version: '0'
|
206
208
|
requirements: []
|
207
209
|
rubyforge_project:
|
208
|
-
rubygems_version: 1.8.
|
210
|
+
rubygems_version: 1.8.23
|
209
211
|
signing_key:
|
210
212
|
specification_version: 3
|
211
213
|
summary: ActiveRecord extension & rails engine for syncing data with Salesforce.com
|