vendredi 24 avril 2020

DRY singleton classes in rails service

I am working with Elasticsearch persistence model, and have some common methods for each index.

Given an Events Index, I have a service class where some methods are defined, the same goes for other n indexes built of their models.

class EventSearchService
  class << self

    def with_index(index_name)
      old_repository = repository
      @repository = EventSearchService::ElasticsearchEventRepository.new(index_name: index_name)

      yield

    ensure
      @repository = old_repository
    end

    def index_name
      repository.index_name
    end

   def index_all(event_documents)
      return unless event_documents.present?

      actions = event_documents.map do |e|
        { index: { _index: index_name, _id: e.id, _type: "_doc", data: e.to_hash }}
      end

      repository.client.bulk(body: actions)
    end


    protected

    def repository
      @repository ||= EventSearchService::ElasticsearchEventRepository.new
    end
  end
end

My problem is that I have ended up with n files with the same class methods. When I try to extract it out to an abstract class directly, I get an error whose investigation reaches me to a point that singleton classes can't be inherited.

After searching for some answers, I followed this thread and I tried to DRY it up

require 'forwardable'
require 'singleton'

class ElasticsearchService
  include Singleton

  class << self
    extend Forwardable

    def_delegators(
      :with_index,
      :index_name,
      :index_all,
      :repository
    )
  end

  def with_index(index_name)
    old_repository = repository
    @repository = search_repository.new(index_name: index_name)
​
    yield
​
  ensure
    @repository = old_repository
  end
​
  def index_name
    repository.index_name
  end
​
  def index_all(documents)
    return unless documents.present?
​
    actions = documents.map do |d|
      { index: { _index: index_name, _id: d.id, _type: "_doc", data: e.to_hash }}
    end
​
    repository.client.bulk(body: actions)
  end
​
  def search_repository
    fail "Needs to be overriden"
  end
​
  protected
​
  def repository
    @repository ||= search_repository.new
  end
end

And I include it as

class EventSearchService < ElasticsearchService
  def search_repository
    EventSearchService::ElasticsearchEventRepository
  end
end

I have redacted the code to keep it small, simple, and related to the cause, but wanted to show different aspects of it. Sorry if it's too long a read.

The error I get is:

`<class:ElasticsearchService>': undefined local variable or method `​' for ElasticsearchService:Class (NameError)

Aucun commentaire:

Enregistrer un commentaire