SalesforceARSync allows you to sync models and fields with Salesforce through a combination of Outbound Messaging, SOAP and databasedotcom.
- Rails >= 3.1
- Salesforce.com instance
- Have your 18 character organization id ready
- databasedotcom gem >= 1.3 installed and configured see below
- delayed_job gem >= 3.0 installed and configured
Before you can start syncing your data several things must be completed in Salesforce.
Create a new Remote Access Application entry by going to
Setup -> Develop -> Remote Access
You can use http://localhost/nothing for the Callback URL
Each model you wish to sync requires a workflow to trigger outbound messaging. You can set the worflow to trigger on the specific fields you wish to update.
Setup -> Create -> Workflow & Approvals -> Worflow Rules
Click New Rule, select the object (model) you wish to sync and click Next, give the rule a name, select Every time a record is created or edited and set a rule on the field(s) you want to sync ( a formula checking if the fields have changed is recommended). Click Save & Next, in the Add Worflow Action dropdown select New Outbound Message. Enter a name and set the Endpoint URL to be http://yoursite.com/integration/sf_soap/model_name. Select the fields you wish to sync (Id and SystemModstamp are required).
*You need to do this for each object/model you want to sync.
Before using the salesforce_ar_sync gem you must ensure you have the databasedotcom gem installed and configured properly. Make sure each of the models you wish to sync are materialized.
$sf_client = Databasedotcom::Client.new("config/databasedotcom.yml")
$sf_client.authenticate :username => <username>, :password => <password>
module SalesforceArSync::SalesforceSync
SF_CLIENT = $sf_client
end
Add this line to your application's Gemfile:
gem 'salesforce_ar_sync'
And then execute:
$ bundle
Or install it yourself as:
$ gem install salesforce_ar_sync
Before using the gem you must complete the setup of your rails app.
The gem needs to know your 18 character organization id, it can be stored in a YAML file or in the ENV class.
To create the yaml file run
$ rails generate salesforce_ar_sync:configuration <organization id>
Next you will need to decide which models you want to sync. For each model you must create a migration and run them
$ rails generate salesforce_ar_sync:migrations <models> --migrate
To mount the engine add the following line to your routes.rb file
mount SalesforceArSync::Engine => '/integration'
You can change '/integration' to whatever you want, all of the engine routes will be based off of this path. Running
$ rake routes | grep salesforce_ar_sync
will show you all of the gems routes, make sure you point your outbound messages at these urls.
Next you will need to tell the gem which models are syncable by adding salesforce_syncable to your model class and specifying which attributes you would like to sync.
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name}
The first parameter in the :sync_attributes hash is the Salesforce field name and the second is the model attribute name.
The gem can be configured using a YAML file or with the ENV variable.
The options available to configure are
- organization_id: the 18 character organization id of your Salesforce instance
- sync_enabled: a global sync enabled flag which is a boolean true/false
To generate a YAML file
$ rails generate salesforce_ar_sync:configuration
Or with an organization id
$ rails generate salesforce_ar_sync:configuration 123456789123456789
which will create a template salesforce_ar_sync.yml in /config that looks like the following
organization_id: <organization id> #18 character organization_id
sync_enabled: true
To use the ENV variable you must pass environemnt variables to rails via the export command in bash or before the initializer loads the ENV settings.
$ export SALESFORCE_AR_SYNC_ORGANIZATION_ID=123456789123456789
$ export SALESFORCE_AR_SYNC_SYNC_ENABLED=true
The model can have several options set:
salesforce_sync_enabled
sync_attributes
async_attributes
default_attributes_for_create
salesforce_id_attribute_name
web_id_attribute_name
salesforce_sync_web_id
web_class_name
salesforce_object_name
except
Model level option to enable disable the sync, defaults to true.
:salesforce_sync_enabled => false
Hash mapping of Salesforce attributes to web attributes, defaults to empty hash.
"Web" attributes can be actual method names to return a custom value.If you are providing a method name to return a
value, you should also implement a corresponding my_method_changed? to return if the value has changed. Otherwise
it will always be synced.
:sync_attributes => { :Email => :login, :FirstName => :first_name, :LastName => :last_name }
An array of Salesforce attributes which should be synced asynchronously, defaults to an empty array. When an object is saved and only attributes contained in this array, the save to Salesforce will be queued and processed asyncronously. 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.
:async_attributes => ["Last_Login__c", "Login_Count__c"]
Note: The model will fall back to synchronous sync if non-synchronous attributes are changed along with async attributes
A hash of default attributes that should be used when we are creating a new record, defaults to empty hash.
:default_attributes_for_create => {:password_change_required => true}
The "Id" attribute of the corresponding Salesforce object, defaults to Id.
:salesforce_id_attribute_name => :Id
The field name of the web id attribute in the Salesforce Object, defaults to WebId__c
:web_id_attribute_name => :WebId__c
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.
:salesforce_sync_web_id => false
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 different web object. Defaults to the model name. This would generally be used if you wanted to flatten a web object into a larger SF object like Contact.
:web_class_name => 'Contact',
Optionally holds the name of a method which will return the name of the Salesforce object to sync to, defaults to nil.
:salesforce_object_name => :salesforce_object_name_method_name
Optionally holds the name of a method which can contain logic to determine if a record should be synced on save. If no method is given then only the salesforce_skip_sync attribute is used. Defaults to nil.
:except => :except_method_name
Stopping the gem from syncing can be done on three levels.
- The global level before the app starts via the .yml file, ENV variables or after the app starts with the gem's configuration variable SALESFORCE_AR_SYNC_CONFIG["SYNC_ENABLED"]
- The model level by setting the :salesforce_sync_enabled => false or :except => :method_name
- The instance level by setting :salesforce_skip_sync => true in the instance
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
end
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email}
end
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
:salesforce_sync_enabled => false
end
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
:except => :skip_sync?
def skip_sync?
if first_name.blank?
return true
end
end
end
customer = Contact.find_by_email('[email protected]')
customer.salesforce_skip_sync = true
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email, :Last_Login_Time__c => :last_login_time},
:async_attributes => ["Last_Login_Time__c"]
end
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
:default_attributes_for_create => {:password_change_required => true}
end
If you want to keep the standard ActiveRecord associations in place, but need to populate these relationships from Salesforce records, you can define methods in your models to add to the attribute mapping.
The following example shows a Contact model, which is related to an Account model through account_id, we implement a getter, setter and _changed? method to do our lookups and map these methods in our sync_attributes mapping instead of the standard attributes. This allows us to send/receive messages from Salesforce using the 18 digit Salesforce id, but maintain our ActiveRecord relationships.
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :account_id
attr_accessor :first_name, :last_name, :account_id
salesforce_syncable :sync_attributes => { :FirstName => :first_name,
:LastName => :last_name,
:AccountId => :salesforce_account_id }
def salesforce_account_id_changed?
account_id_changed?
end
def salesforce_account_id
return nil if account_id.nil?
account.salesforce_id
end
def salesforce_account_id=(account_id)
self.account = nil and return if account_id.nil?
self.account = Account.find_or_create_by_salesforce_id(account_id)
end
end
class Contact < ActiveRecord::Base
attributes :first_name, :last_name, :phone, :email, :last_login_time, :salesforce_id, :salesforce_updated_at
attr_accessor :first_name, :last_name, :phone, :email, :last_login_time
salesforce_syncable :sync_attributes => {:FirstName => :first_name, :LastName => :last_name, :Phone => :phone, :Email => :email},
:salesforce_object_name => :custom_salesforce_object_name
def custom_salesforce_object_name
"CustomContact__c"
end
end
In order to handle the delete of objects coming from Salesforce, a bit of code is necessary because an Outbound Message cannot be triggered when an object is deleted. To work around this you will need to create a new Custom Object in your Salesforce environment:
Deleted_Object__C
Object_Id__c_ => Text(18)
Object_Type__c_ => Text(255)
Object_Id__c will hold the 18 digit Id of the record being deleted. Object_Type__c will hold the name of the Rails Model that the Salesforce object is synced with.
If you trigger a record to be written to this object whenever another object is deleted, and configure an Outbound Message to send to the /sf_soap/delete action whenever a Deleted_Object__c record is created, the corresponding record will be removed from your Rails app.
If the SOAP handler encounters an error it will be recorded in the log of the outbound message in Salesforce. To view the message go to
Setup -> Monitoring -> Outbound Messages
Your 15 character organization id can be found in Setup -> Company Profile -> Company Information. You must convert it to an 18 character id by running it through the tool located here: http://cloudjedi.wordpress.com/no-fuss-salesforce-id-converter/ or by installing the Force.com Utility Belt for Chrome.
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request