Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
like image 377
Faizaan Khan Avatar asked Apr 24 '20 12:04

Faizaan Khan


1 Answers

This one is very sneaky. There are some non-ASCII space characters inside your code which the ruby interpreter is recognizing as the name of a method that's being called.

I threw your code inside my terminal, getting precisely the same error as you, but after writing it by hand and executing method by method, did not get it.

Found a conversor online and after copy/pasting your code (here's the link to the one I used), the code ran without that error.

So formatting the file properly should do the trick with that particular error you're experiencing.

like image 118
cesartalves Avatar answered Oct 17 '22 09:10

cesartalves