My Rails apps need to connect to several external services to perform some functions. Now, I realize that some of the services I need might also be reused outside Rails (like in Capistrano deployment), so I want to find the best compromise I can so I can reuse those services across several Ruby apps.Simple example : during the deployment of my application with Capistrano, I want to send a notification to a slack channel when it's done. Similarly inside my Rails app, I might want to send some slack notification at some pointAnd because I like bullet pointsDecoupled from Rails - can be used eg. during a Capistrano deploymentService is made of a client that usually requires to be initialized with some app/secret keysWe're talking single Threads, so it does (or not ?) make sense to have a singleton/static service that we do not need to instanciate everytimeThe service must survive/restart automatically when there's a crash or a console/web reload!The service should be able to report its status at application start/anytimeHere's my attempt. With the below code I'm having problems after a code reload from the console or from the development rails console# Define common structure for third party API connectors# based on Singleton#module pattern https://github.com/nslocum/design-patterns-in-ruby/tree/master/singleton/module_as_singleton# Include this module and implement the following class methods# - #credentials_present?# - #should_be_activated?# - #service_clientmodule ServiceConnector  extend ActiveSupport::Concern  included do    @activated = false    @activation_attempt = false    @client = nil    attr_reader :client, :activated    def self.report_activation_successful      puts SUCCESS - Service #{service_name} is activated    end    def self.report_missing_credentials      puts ERROR - Service #{service_name} should be activated  \            'but credentials are not available'    end    def self.report_not_activated      puts INFO - Service #{service_name} is deactivated     end    def self.client      @client ||= service_client    end    def self.service_name      name.gsub('Connector', '')    end    def self.activate      @activation_attempt = true      if credentials_present?        @client = service_client        @activated = true      end    end    def self.activated?      @activated    end    def self.status_report      if @activated        report_activation_successful      elsif @activation_attempt        report_missing_credentials      else        report_not_activated      end    end    def self.nothing      puts WARNING - Service #{service_name} deactivated  \          'no communication with remote API'      nil    end    def self.secretize(value)      half = (value/2).floor      value[0..half].to_s + '*'*value.siz    end  endendAnd here's how I implement a service (eg Slack)module SlackConnector  include ServiceConnector  @webhook = nil  def self.webhook=(webhook)    @webhook = webhook  end  def self.credentials_present?    @webhook != nil  end  def self.service_client    ::Slack::Notifier.new(      @webhook,      username: MyBot-#{ENV['RAILS_ENV']},    )  end  def self.ping(message, opts = {})    return nothing unless activated?    @client.ping(message, {      icon_url: 'https://example.com/image.png,    }.merge(opts))  endOn the Rails side, I'm initializing in an initializer# initializers/slack.rbslack = ::SlackConnectordef slack_should_be_activated?  Rails.env.production? or  Rails.env.staging? or  (Rails.env.development? and ENV['ENABLE_SLACK_DEV'] == 'true')endif slack_should_be_activated?  slack.webhook = Rails.application.secrets.slack.dig('webhooks', 'some_webhook')  slack.activate  slack.status_reportend
Service Connector abstraction for Ruby
ruby;ruby on rails;web services