Search code examples
rubyclassmoduleinitializationactivesupport

How to call any instance method in ruby object without instantiating it?


I am creating a helper module to initialize the object before calling its methods

module Initialized
  extend ActiveSupport::Concern

  class_methods do
    def run(*args)
      new(*args).run
    end

    def call(*args)
      new(*args).call
    end

    def execute(*args)
      new(*args).create
    end
  end
end

So instead of defining run, call, and execute in my helper module I need to receive any method name and check if it exists on the main class after initializing it, then call the requested instance method if exists in the main class or raise an error if not

I would say my targeted code would be something like this

module Initialized
  extend ActiveSupport::Concern
  class_methods do
    def _(*args, methodname)
      new(*args).try(:send, "#{methodname}") || raise 'Method not exist'
    end
  end
end

Sample usage would be

class MyClass
  include Initialized

  def initialize(param1)
    @param1 = param1
  end

  def call
    puts "call '#{@param1}'"
  end
end

then calling

MyClass.call('method param')

I found these links but couldn't find my answer yet:

meta-dynamic-generic-programming-in-ruby

ruby-module-that-delegates-methods-to-an-object

template-methods-in-ruby


Solution

  • Despite the fact method_missing would do the job, I'd probably avoid it in this case in favor of a more explicit delegation. Simple and dirty example:

    module InstanceDelegator
      def delegate_to_instance(*methods)
        methods.each do |method_name|
          define_singleton_method method_name do |*args|
            new(*args).public_send(method_name)
          end
        end
      end
    end
    
    class Foo
      extend InstanceDelegator
    
      delegate_to_instance :bar # <- define explicitly which instance methods
                                # should be mirrored by the class ones
    
      def bar
        puts "bar is called"
      end
    
      def baz
        puts "baz is called"
      end
    end
    
    # and then
    
    Foo.bar #  => bar is called
    Foo.baz # NoMethodError ...
    
    # reopening works too
    class Foo
      delegate_to_instance :baz
    end
    
    Foo.baz # baz is called
    

    Pros:

    1. you don't need to redefine method_missing (less magic -> less pain when you debug the code)
    2. you control precisely which instance methods to be wrapped with the class level "shorthand" (fewer chances to delegate something you don't want to - more robust code)
    3. (minor) no need to raise NoMethodError explicitly - you can fully rely on the core dispatching as it is...