salesforce_ar_sync 1.0.1 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|