I have a private method on accounts_controller.rb that authenticates 3rd party service (Salesforce) allowing me to pull accounts record from 3rd party (Salesforce):
Now, I created another controller called cases_controller.rb in which I want to do CRUD operation with different object/table. Do I just copy and paste this authenticating method to every different controllers that needs authentication with 3rd party, or is there a better way not to repeat this code? How can I use same private method to different classes?
Throwing this method into the ApplicationController is a good start. This tends to lead to a bloated ApplicationController over time, so it is something to be aware of.
With the code that you posted, there is no advantage of performing a before filter. You are returning an instance of a Restforce class, âclientâ, and in a before filter you would no longer have a handle on the instance of the Restforce class âclientâ. Something like this is a short implementation.
class ApplicationController
private
def restforce_client
client = Restforce.new :username => ENV['SALESFORCE_USERNAME'],
:password => ENV['SALESFORCE_PASSWORD'],
:client_id => ENV['SALESFORCE_PASSWORD'],
:client_secret => ENV['SALESFORCE_PASSWORD'],
:password => ENV['SALESFORCE_PASSWORD']
client.authenticate!
client
end
end
class AccountsController < ApplicationController
def index
accounts = restforce_client.query("accounts query code")
end
end
class CasesController < ApplicationController
def index
cases = restforce_client.query("cases query code")
end
end
However the details of Restforce is leaking into your controllers using it this way and is probably not a final solution either. One solution could be to wrap the Restforce client in a wrapper class with something along the lines of this pseudocode (no error handling and such):
class CrmClient
def initialize
@client = Restforce.new :username => ENV['SALESFORCE_USERNAME'],
:password => ENV['SALESFORCE_PASSWORD'],
:client_id => ENV['SALESFORCE_PASSWORD'],
:client_secret => ENV['SALESFORCE_PASSWORD'],
:password => ENV['SALESFORCE_PASSWORD']
@client.authenticate!
end
def accounts
@client.query("accounts query code")
end
def cases
@client.query("cases query code")
end
end
class ApplicationController
private
def crm_client
CrmClient.new
end
end
class AccountsController < ApplicationController
def index
@accounts = crm_client.accounts
end
end
class CasesController < ApplicationController
def index
@cases = crm_client.cases
end
end
This just extracts the details out of your controllers and the implementation could be changed in one place or swapped out completely.
As a small addition to @frank_west_iiiâs great post, you may want to memoize #crm_client in ApplicationController so that you can reuse the same CrmClient object each time. This helps with performance, since you donât get slowed down by an extra authentication request to an external service each time you access #crm_client.
class ApplicationController
private
def crm_client
@crm_client ||= CrmClient.new
end
end
On the other hand this may have drawbacks depending on your specific use case, so your mileage may vary - take this with a grain of salt.
Hey @frank_west_iii, are you suggesting to create a different class called âCrmClientâ in the app/controller directory? I am still learning what needs to be implemented as service objects, helpers, concerns, etc as mentioned in following links:
My opinion is that this is just a plain old ruby object that sits inside your app/model directory named crm_client.rb (class CrmClient). It could go wherever you want it to go. If you have not listened to the latest Bike Shed episode, they have a good discussion on this. The Bike Shed: 5: Rails is Not Your Architecture
It might be considered a service object in some circles, not sure, but I wouldnât bother putting a label on it myself.