0

I have the following class:

class Foo
  include BaseFoo

  attr_reader :with_context

  def initialize(with_context: %i[bar baz])
    @with_context = with_context
  end

  def call
    puts bar # Should return 'lorem ipsum'
    puts baz # Should return 'lorem ipsum'
  end
end

How can I dynamically define methods in module BaseFoo based on the values of with_context?

Unfortunately, this wont workk because the module doesnt know what with_context is. But to give you a general sense of what needs to be achieved. Pseudo code:

module BaseFoo
  with_context.each |context|
    define_method context do
      'lorem ipsum'
    end
  end
end

Basically, BaseFoo should have the following public methods:

def bar
  'lorem ipsum'
end

def baz
  'lorem ipsum'
end

1 Answer 1

1

You can't really do this without actually interacting with the module. When a module is included its included in the class which places the module on the ancestors chain of the class and fires the Module#included callback on the module.

Since this happens on the class level there is no way to access the context of an instance unless you're actually calling an instance method. In which case self is the instance of the class that includes the module:

module BaseFoo
  def extend_base_foo
    with_context.each do |context|
      BaseFoo.module_eval do
        define_method context do
          'lorem ipsum'
        end
      end
    end
  end
end

class Foo
  include BaseFoo

  attr_reader :with_context

  def initialize(with_context: %i[bar baz])
    @with_context = with_context
    extend_base_foo
  end

  def call
    puts bar # Should return 'lorem ipsum'
    puts baz # Should return 'lorem ipsum'
  end
end

But honestly why on earth would you ever want to do this? Creating an instance of Foo will actually define those methods on the module itself which means that they are included in every instance of Foo:

Foo.new(with_context: [:x, :y])
Foo.new.x # loren ipsum - big WTF moment

If you really want to do something like this create an anonymous module and extend the instance with it:

class Foo
  def initialize(with_context: %i[bar baz])
    m = Module.new do
      with_context.each do |context|
        define_method(context) do
          'lorem ipsum'
        end
      end
    end
    self.extend(m)
  end

  def call
    puts bar # Should return 'lorem ipsum'
    puts baz # Should return 'lorem ipsum'
  end
end
Sign up to request clarification or add additional context in comments.

2 Comments

Your first example does not work. Foo.new.call returns block in extend_base_foo': private method define_method' called for BaseFoo:Module (NoMethodError)
I ran it in ruby 2.7.0p0 (2019-12-25 revision 647ee6f091). It's probaly version dependent. Maybe use BaseFoo.module_eval do ... end.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.