Reputation: 2648
In my current project, I am having the following repetitive pattern in some classes:
class MyClass
def method1(pars1, ...)
preamble
# implementation method1
rescue StandardError => e
recovery
end
def method2(pars2, ...)
preamble
# implementation method2
rescue StandardError => e
recovery
end
# more methods with the same pattern
end
So, I have been thinking about how to dry that repetive pattern. My goal is to have something like this:
class MyClass
define_operation :method1, pars1, ... do
# implementation method1
end
define_operation :method2, pars2, ... do
# implementation method2
end
# more methods with the same pattern but generated with define_wrapper_method
end
I have tried to implement a kind of metagenerator but I have had problems for forwarding the block that would receive the generator. This is more or less what I have tried:
def define_operation(op_name, *pars, &block)
define_method(op_name.to_s) do |*pars|
preamble
yield # how can I do here for getting the block? <-----
rescue StandardError => e
recovery
end
end
Unfortunately, I cannot find a way for forwarding block
to the define_method
method. Also, it is very possible that the parameters, whose number is variable, are being passed to define_method
in a wrong way.
I would appreciate any clue, help, suggestion.
Upvotes: 2
Views: 296
Reputation: 29318
If I understand correctly you are looking for something like:
class Operation
def self.op(name,&block)
define_method(name) do |*args|
op_wrap(block.arity,*args,&block)
end
end
def op_wrap(arity=0,*args)
if arity == args.size || (arrity < 0 && args.size >= arity.abs - 1)
begin
preamble
yield *args
rescue StandardError => e
recovery
end
else
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{arity < 0 ? (arity.abs - 1).to_s.+('+') : arity })"
end
end
def preamble
puts __method__
end
def recovery
puts __method__
end
end
So your usage would be
class MyClass < Operation
op :thing1 do |a,b,c|
puts "I got #{a},#{b},#{c}"
end
op :thing2 do |a,b|
raise StandardError
end
def thing3
thing1(1,2,3)
end
end
Additionally this offers you both options presented as you could still do
def thing4(m1,m2,m3)
@m1 = m1
op_wrap(1,'inside_wrapper') do |str|
# no need to yield because the m1,m2,m3 are in scope
# but you could yield other arguments
puts "#{str} in #{__method__}"
end
end
Allowing you to pre-process arguments and decide what to yield to the block
Examples
m = MyClass.new
m.thing1(4,5,6)
# preamble
# I got 4,5,6
#=> nil
m.thing2('A','B')
# preamble
# recovery
#=> nil
m.thing3
# preamble
# I got 1,2,3
#=> nil
m.thing1(12)
#=> #<ArgumentError: wrong number of arguments (given 1, expected 3)>
Upvotes: 2
Reputation: 22926
You do not need metaprogramming to achieve this. Just add a new method that wraps the common logic like below:
class MyClass
def method1(param1)
run_with_recovery(param1) do |param1|
# implementation method1
end
end
def method2(param1, param2)
run_with_recovery(param1, param2) do |param1, param2|
# implementation method2
end
end
private
def run_with_recovery(*params)
preamble
yield(*params)
rescue StandardError => e
recovery
end
end
Test it here: http://rubyfiddle.com/riddles/4b6e2
If you really wanted to do metaprogramming, this will work:
class MyClass
def self.define_operation(op_name)
define_method(op_name.to_s) do |*args|
begin
puts "preamble"
yield(args)
rescue StandardError => e
puts "recovery"
end
end
end
define_operation :method1 do |param1|
puts param1
end
define_operation :method2 do |param1, param2|
puts param1
puts param2
end
end
MyClass.new.method1("hi")
MyClass.new.method2("hi", "there")
Test this here: http://rubyfiddle.com/riddles/81b9d/2
Upvotes: 7