Home > Mobile >  Can I create a closure in Ruby that adds functionality to a method and maintains its parameter defin
Can I create a closure in Ruby that adds functionality to a method and maintains its parameter defin

Time:07-05

I would like to make a method that can take in a method name or other callable block and return a proc/lambda in order to add additional functionality, such as a call counter.

Here I have an example of a closure that defines a lambda and maintains a counter. This is working as expected.

def get_countable_greeter
  method_use_count = 0
  lambda do |greeting: 'Hello', name: 'you'|
    puts "#{greeting}, #{name}!"
    method_use_count  = 1
    puts "call count: #{method_use_count}"
  end
end

greeter = get_countable_greeter
greeter.call
greeter.call(greeting:'Hi', name: 'Mom')

# OUTPUT:
# Hello, you!
# call count: 1
# Hi, Mom!
# call count: 2

What I really want is a more generalized solution. I'd like to be able to do something like the following, that would let me basically add on the concept of a counter to an already defined method:

def greet(greeting: 'Hello', name: 'you')
  puts "#{greeting}, #{name}!"
end

def get_countable_method(method_name)
  method_use_count = 0
  Proc.new do
    # What goes here?
    method_use_count  = 1
    puts "call count: #{method_use_count}"
  end
end

greeter = get_countable_method(:greet)
greeter.call
greeter.call(greeting:'Hi', name: 'Mom')

I can't figure out any way to pass along the arguments properly. Is this even possible?

One thing I considered was somehow using the Method#parameters information for the passed in method name, but that doesn't maintain the default values of the original method. Any assistance or alternative approaches would be very appreciated.

CodePudding user response:

You can use send / public_send to dynamically invoke a method with a given name, e.g.:

def greet(greeting: 'Hello', name: 'you')
  puts "#{greeting}, #{name}!"
end

def get_countable_method(method_name)
  method_use_count = 0
  Proc.new do |**kwargs|
    send(method_name, **kwargs)
    method_use_count  = 1
    puts "call count: #{method_use_count}"
  end
end

greeter = get_countable_method(:greet)
greeter.call
greeter.call(greeting:'Hi', name: 'Mom')

Output:

Hello, you!
call count: 1
Hi, Mom!
call count: 2

** is used to accept arbitrary keyword arguments (and also to pass them along)

  • Related