module ExternalServices::ActiveRecord::HasExternalService::ClassMethods

Public Instance Methods

external_services_disabled() click to toggle source
# File lib/external_services/active_record.rb, line 40
def external_services_disabled
  Thread.current[:external_services_disabled]
end
external_services_disabled=(val) click to toggle source
# File lib/external_services/active_record.rb, line 44
def external_services_disabled=(val)
  Thread.current[:external_services_disabled] = val
end
has_external_service(name, options = {}) click to toggle source
# File lib/external_services/active_record.rb, line 7
def has_external_service(name, options = {})
  class_attribute :external_services unless respond_to?(:external_services)
  self.external_services ||= {}
  self.external_services[name.to_sym] = options

  unless options[:only_api_actions] == true
    service_class = get_service_class(name, options)
    service_assoc = :"#{name}_service"
    has_one service_assoc, class_name: service_class.to_s, as: :subject, dependent: :destroy, autosave: true

    define_external_service_getset         name, options
    define_external_service_sync_methods   name, options
  end

  define_external_service_callbacks      name, options
  define_external_service_helper_methods name, options

  extend  service_class::SubjectClassMethods if defined? service_class::SubjectClassMethods
  include service_class::SubjectMethods if defined? service_class::SubjectMethods

  # rubocop:disable Lint/HandleExceptions
  begin
    service_module = const_get(name.to_s.camelize, false)
    include service_module
  rescue NameError
  end
  # rubocop:enable Lint/HandleExceptions
end
includes_external_services() click to toggle source
# File lib/external_services/active_record.rb, line 36
def includes_external_services
  includes(self.external_services.keys.map { |name| :"#{name}_service" })
end
without_external_services() { || ... } click to toggle source
# File lib/external_services/active_record.rb, line 48
def without_external_services
  old = external_services_disabled
  self.external_services_disabled = true

  yield
ensure
  self.external_services_disabled = old
end

Private Instance Methods

define_external_service_callbacks(name, options = {}) click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity

# File lib/external_services/active_record.rb, line 92
def define_external_service_callbacks(name, options = {})
  service_assoc = :"#{name}_service"
  only_api_actions = (options[:only_api_actions] == true)

  callbacks_module = Module.new do
    extend ActiveSupport::Concern

    included do
      unless only_api_actions
        before_save do
          public_send(:"build_#{service_assoc}") if public_send(service_assoc).blank?
        end

        before_update  :halt_on_external_services_syncing
        before_destroy :halt_on_external_services_syncing
      end

      after_save    :"#{name}_on_create", if: proc { # Rails 5.1+ support
        respond_to?(:saved_change_to_id?) ? saved_change_to_id? : id_changed?
      }

      after_save    :"#{name}_on_update", if: proc {# Rails 5.1+ support
        !(respond_to?(:saved_change_to_id?) ? saved_change_to_id? : id_changed?)
      }

      after_destroy :"#{name}_on_destroy"
    end

    define_method :"#{name}_on_create" do
      public_send(service_assoc).on_subject_create(self) unless only_api_actions
    end
    protected :"#{name}_on_create"

    define_method :"#{name}_on_update" do
      public_send(service_assoc).on_subject_update(self) unless only_api_actions
    end

    define_method :"#{name}_on_destroy" do
      public_send(service_assoc).on_subject_destroy(self) unless only_api_actions
    end
    protected :"#{name}_on_destroy"

    protected def halt_on_external_services_syncing
      if external_services_syncing?
        errors.add :base, :external_services_sync_in_process
        if ::ActiveRecord::VERSION::MAJOR < 5
          return false
        else
          throw :abort
        end
      end
    end
  end
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

  include callbacks_module
end
define_external_service_getset(name, _options = {}) click to toggle source
Calls superclass method
# File lib/external_services/active_record.rb, line 63
def define_external_service_getset(name, _options = {})
  service_assoc = :"#{name}_service"

  define_method :"#{name}_id" do
    public_send(service_assoc).try(:public_send, :external_id)
  end

  define_method :"#{name}_id=" do |val|
    public_send(service_assoc).try(:public_send, :external_id=, val)
  end

  define_method :"#{name}_extra_data" do
    public_send(service_assoc).try(:public_send, :extra_data)
  end

  define_method :"#{name}_extra_data=" do |val|
    public_send(service_assoc).try(:public_send, :extra_data=, val)
  end

  define_singleton_method :"find_by_#{name}_id" do |id|
    all.joins(service_assoc).find_by(external_services: { external_id: id })
  end

  define_method service_assoc do
    super() || public_send(:"build_#{service_assoc}")
  end
end
define_external_service_helper_methods(name, _options = {}) click to toggle source

rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize

# File lib/external_services/active_record.rb, line 185
def define_external_service_helper_methods(name, _options = {})
  ## subject class methods
  helpers_class_module = Module.new do
    define_method :"with_#{name}_api_for" do |synced: [], for_syncing: [], &b|
      (synced + for_syncing).map(&:class).uniq.each do |k|
        return true if k.send("#{name}_api_disabled")
      end

      unsynced = [synced].flatten.select { |o| o.send("#{name}_id").nil? }

      if unsynced.any?
        objects = unsynced.map { |o| "#{o.class.name} (id=#{o.id})" }.join(', ')
        raise "[#{name}] Trying to work with an unsynced objects: #{objects}"
      end

      b.call
    end

    define_method :"#{name}_api_name" do
      to_s.demodulize.underscore
    end

    define_method :"#{name}_api_path" do
      send(:"#{name}_api_name").pluralize
    end

    define_method :"#{name}_api_disabled" do
      ENV["#{name}_api_disabled".upcase] == 'true' || Thread.current[:"#{name}_api_disabled"] || external_services_disabled
    end

    define_method :"#{name}_api_disabled=" do |val|
      Thread.current[:"#{name}_api_disabled"] = val
    end

    define_method :"without_#{name}_api" do |&blk|
      begin
        old = send :"#{name}_api_disabled"
        send :"#{name}_api_disabled=", true

        blk.call
      ensure
        send :"#{name}_api_disabled=", old
      end
    end

    define_method :"find_all_by_#{name}_ids" do |ids|
      conditions = { external_services: { type: get_service_class(name).name, external_id: ids } }
      includes(:"#{name}_service").where(conditions)
    end

    define_method :"#{name}_synced" do
      includes(:"#{name}_service").where.not(external_services: { external_id: [nil, ''] })
    end

    define_method :"not_#{name}_synced" do
      includes(:"#{name}_service").where(external_services: { external_id: [nil, ''] })
    end
  end

  ## subject methods
  helpers_module = Module.new do
    define_method :"#{name}_api_disabled" do
      self.class.send :"#{name}_api_disabled"
    end

    define_method :"#{name}_api_path" do
      if send(:"#{name}_id").present?
        "#{self.class.send(:"#{name}_api_path")}/#{send(:"#{name}_id")}"
      else
        self.class.send(:"#{name}_api_path")
      end
    end

    define_method :"#{name}_api_data" do
      send(:"to_#{name}_api")
    end

    define_method :"#{name}_api_action" do |method, **args|
      return if self.class.send(:"#{name}_api_disabled")
      return if !args[:force] && send(:"#{name}_api_disabled")

      path    = args[:path]    || send(:"#{name}_api_path")
      data    = args[:data]    || send(:"#{name}_api_data")
      options = args[:options] || {}
      async   = args[:async].nil? ? true : args[:async]

      options[:change_external_id] = true if options[:change_external_id].nil?

      action = "ExternalServices::ApiActions::#{name.to_s.camelize}".constantize.new(
        initiator: self,
        name:      args[:name] || self.class.send(:"#{name}_api_name"),
        method:    method,
        path:      path,
        data:      data,
        queue:     args[:queue],
        options:   options,
        async:     async
      )

      if async
        action.save!
      else
        action.execute!
      end
    end
  end

  extend  helpers_class_module
  include helpers_module
end
define_external_service_sync_methods(name, _options = {}) click to toggle source
Calls superclass method
# File lib/external_services/active_record.rb, line 150
def define_external_service_sync_methods(name, _options = {})
  service_assoc = :"#{name}_service"
  synced_method = :"#{name}_service_synced?"
  syncing_method = :"#{name}_service_syncing?"
  disabled_method = :"#{name}_api_disabled"

  syncs_module = Module.new do
    extend ActiveSupport::Concern

    define_method synced_method do
      public_send(service_assoc).external_id?
    end

    define_method syncing_method do
      action_class = "ExternalServices::ApiActions::#{name.to_s.camelize}".constantize
      action_class.to_create(self).unprocessed.exists?
    end

    define_method :external_services_synced? do
      result = (!defined?(super) || super())
      result &&= public_send(synced_method) unless public_send(disabled_method)
      result
    end

    define_method :external_services_syncing? do
      result = (defined?(super) && super())
      result ||= public_send(syncing_method) unless public_send(disabled_method)
      result
    end
  end

  include syncs_module
end
get_service_class(name, options = {}) click to toggle source
# File lib/external_services/active_record.rb, line 59
def get_service_class(name, options = {})
  (options[:class] || "ExternalServices::#{name.to_s.camelize}").constantize
end
halt_on_external_services_syncing() click to toggle source
# File lib/external_services/active_record.rb, line 134
          def halt_on_external_services_syncing
  if external_services_syncing?
    errors.add :base, :external_services_sync_in_process
    if ::ActiveRecord::VERSION::MAJOR < 5
      return false
    else
      throw :abort
    end
  end
end