Last Updated: February 25, 2016
·
318
· volontarian

DRY Conventional API Provider Host Setting by Environment Fallback and Alias Mapping

For my content management system gem http://Home-Page.Software I wanted to support different APIs and use the right host for the current environment by a fallback chain.
Therefor I need to handle aliases of user environment names and environments not supported by an API provider.

Given these settings from a Rails initializer powered by rails-settings-cached (you have to use development, test, staging or production as key):

Setting.defaults[
  'apis.providers.volontariat.hosts.development'
] = 'http://localhost:3001'
Setting.defaults[
  'apis.providers.volontariat.hosts.production'
] = 'http://Volontari.at' # currently down :-(

I wanted something like this:

host = ApiProviderHost.new('volontariat', Rails.env).to_s

I expect it to behave like this:

describe ApiProviderHost do
  describe '#to_s' do
    it 'behaves like this' do
      provider = 'name_of_host'
      Setting.defaults[
        'apis.providers.name_of_host.hosts.development'
      ] = 'http://localhost:3001'

      # no fallback or alias mapping needed
      expect(
        described_class.new(provider, 'development').to_s
      ).to be == 'http://localhost:3001'

      # fallback to development
      expect(
        described_class.new(provider, 'test').to_s
      ).to be == 'http://localhost:3001'

      # alias mapping needed
      expect(
        described_class.new(provider, 'dev').to_s
      ).to be == 'http://localhost:3001'

      # alias not found
      expect{
        described_class.new(provider, 'unknown_environment').to_s
      }.to raise_error(
        NotImplementedError, 
        'Your environment is unknown. Please update alias mapping!'
      )

      # environment not supported by provider
      expect{
        described_class.new(provider, 'staging').to_s
      }.to raise_error(
        NotImplementedError, 
        'The API provider does not support your environment!'
      )
    end
  end
end

And I implemented this class:

class ApiProviderHost
  ENVIRONMENTS = [:development, :test, :staging, :production]
  ALIASES = { 
    dev: :development, testing: :test, stage: :staging, show: :staging, 
    live: :production, prod: :production 
  }
  FALLBACKS = { 
    development: [:development, :test, :staging, :production], 
    test: [:test, :development, :staging, :production],
    staging: [:staging, :production],
    production: [:production]
  }

  def initialize(provider, working_environment)
    @provider = provider
    @environment = working_environment.to_s.to_sym
  end

  def setting_namespace
    "apis.providers.#{@provider}.hosts"
  end

  def environment
    if ENVIRONMENTS.include?(@environment)
      @environment
    else
      ALIASES[@environment] || raise(
        NotImplementedError, 
        'Your environment is unknown. Please update alias mapping!'
      )
    end
  end

  def to_s
    host = nil

    FALLBACKS[environment].each do |provider_environment|
      host = Setting["#{setting_namespace}.#{provider_environment}"]

      break if host
    end

    unless host
      raise(
        NotImplementedError, 
        'The API provider does not support your environment!'
      )
    end

    host
  end
end

P.S.: In the past I lived with always setting a host for each environment and without environment alias mapping but this was not DRY:

Setting.defaults[
  'apis.providers.volontariat.hosts.development'
] = 'http://localhost:3001'
Setting.defaults[
  'apis.providers.volontariat.hosts.test'
] = 'http://localhost:3001'
Setting.defaults[
  'apis.providers.volontariat.hosts.staging'
] = 'http://Volontari.at'
Setting.defaults[
  'apis.providers.volontariat.hosts.production'
] = 'http://Volontari.at'

# optional: I also put the little envionment alias mapping code here if you can't live without it
environment = { 
    dev: :development, testing: :test, stage: :staging, show: :staging, 
    live: :production, prod: :production 
  }[Rails.env.to_s.to_sym] || Rails.env
host = Setting[
  "apis.providers.volontariat.hosts.#{environment}"
]