Gareth Burrows
Gareth Burrows

Reputation: 1182

Drying up Rails controllers

I have an application in Ruby/Rails where I am going to have to connect to a third-party application (Xero accounting) in order to send/pull data. This happens in several controllers thoughout the application. However, whilst the specifics are different, the actual connection is the same, and looks like this:

require 'xero_gateway'
xero_config = YAML.load_file("#{Rails.root}/config/xero.yml")["testing"]
xero_private_key = "#{ENV['HOME']}/certs/breathehr_xero.key" || ENV["xero_private_key"]
xero_gateway = XeroGateway::PrivateApp.new(xero_config['consumer_key'], xero_config['consumer_secret'], xero_private_key)

The next step of the code might be fetch something, like an invoice, as in:

xero_gateway.get_invoice('INV-001')

Or, in another controller, it might be to create a contact, such as:

xero_gateway.build_contact

Is there somewhere I can put the connection code so that I can end up just calling xero_gateway in each specific controller? It doesn't feel right to be repeating the same code again and again each time I have to authenticate and connect.

Upvotes: 0

Views: 121

Answers (3)

Greg Burghardt
Greg Burghardt

Reputation: 18783

To build on @Dave Newton's answer:

You would create a "provider" class that marries an object factory to some configuration:

File: lib/xero_gateway_provider.rb

require 'xero_gateway'

class XeroGatewayProvider
    cattr_accessor :default_private_key, :default_consumer_key, :default_consumer_secret

    def initialize(overrides = {})
        @private_key = overrides[:private_key] || self.class.default_private_key
        @consumer_key = overrides[:consumer_key] || self.class.default_consumer_key
        @consumer_secret = overrides[:consumer_secret] || self.class.default_consumer_secret
    end

    def create_private_app
        XeroGateway::PrivateApp.new(@consumer_key, @consumer_secret, @private_key)
    end
end

Then you could create a Rails initializer: config/initializers/xero_gateway_provider.rb

require 'xero_gateway_provider'
conf = YAML.load_file("#{Rails.root}/config/xero.yml")[Rails.env]
XeroGatewayProvider.default_private_key = "#{ENV['HOME']}/certs/breathehr_xero.key" || ENV["xero_private_key"]
XeroGatewayProvider.default_consumer_key = conf["consumer_key"]
XeroGatewayProvider.default_consumer_secret = conf["consumer_secret"]

And to use it:

# Using default configs
provider = XeroGatewayProvider.new;
private_app = provider.create_private_app
private_app.get_invoice("...")

# Using overrides
provider = XeroGatewayProvider.new :consumer_key => '...', :consumer_secret => '...';
private_app = provider.create_private_app
private_app.get_invoice("...")

Edit: Just realized there is no point to instantiating XeroGatewayProvider if it uses class level properties, so I made them defaults allowing you to configure each provider individually.

Also @Gareth Burrows comment on where to put and name the class, I think this would fit just fine in the lib/ directory. See the edits to the post for specifics.

Upvotes: 1

Rafal
Rafal

Reputation: 2576

You can create a regular ruby class in the models folder called Xero or something and do this code in the initializer.

require 'xero_gateway'
class Xero
  def initialize
    xero_config = YAML.load_file("#{Rails.root}/config/xero.yml")["testing"]
    xero_private_key = "#{ENV['HOME']}/certs/breathehr_xero.key" || ENV["xero_private_key"]
    xero_gateway = XeroGateway::PrivateApp.new(xero_config['consumer_key'],     xero_config['consumer_secret'], xero_private_key)
  end
end

And then just call:

xero_gateway = Xero.new

Another option is to create an initializer in the initializers/ folder. xero_gateway.rb

And put the initialization code in there. This way it will be parsed only on application startup.

Upvotes: 0

Dave Newton
Dave Newton

Reputation: 160181

You could put it just about anywhere, including:

  1. A utility class used by your actions, or
  2. A base action class (meh), or
  3. A module included in your actions (meh).

I lean towards a utility class, because:

  1. It's easy to instantiate or mock/stub it for testing, and
  2. It doesn't make the surface area of your action class any bigger

Upvotes: 0

Related Questions