Configurable refinements in ruby

83 Views Asked by At

I'd like to patch parts of an existing library with a config object:

module Library
  class A
  end

  class B
  end
end

module Config
  def config(&block)
    @config ||= block&.call
  end
end

module Patch
  refine Library::A.singleton_class do
    extend Config
    config { "my for A" }
  end

  refine Library::B.singleton_class do
    extend Config
    config { "my for B" }
  end
end

Library::A should have some predefined config and Library::B should have its own config. Unfortunately, this throws an error:

using Patch
Library::A.config
=> undefined method `config' for Library::A:Class (NoMethodError)

I guess I don't understand how refinements work in this case. But is there a way to achieve something like this?

1

There are 1 best solutions below

0
On BEST ANSWER

here is my solution, basically i include Config to the Patch to be able to define configurations at class level, then include the Patch itself to the refined classes (Library::A and Library::B) to be able to access the defined configurations.

module Config
  def self.included(base)
    class << base
      def refinement(clazz, patch=self, &block)
        $predefined_configs ||= Hash.new
        $predefined_configs[clazz] ||= Hash.new
        block&.call($predefined_configs[clazz])
        refine clazz.singleton_class do
          include patch
        end
      end
    end
  end

  def config
   $predefined_configs[self].tap do |_config|
      # reuse common configs
      _config.merge!({
        lang: "ruby"
      })
   end
  end
end

then

module Library
  class A; end

  class B; end
end

module Patch
  include Config

  refinement(Library::A) do |_config|
    _config[:name] = "my for A"
  end

  refinement(Library::B) do |_config|
    _config[:name] = "my for B"
  end
end

class LoadConfig
  using Patch
  puts Library::A.config # {:name=>"my for A", :lang=>"ruby"}
end

puts Library::A.config # error

using Patch
puts Library::B.config # {:name=>"my for B", :lang=>"ruby"}