You could utilize caller_locations
:
def mymethod(x)
first, *stack = caller_locations(0)
recursion = stack.any? do |cl|
cl.absolute_path == first.absolute_path && cl.label == first.label
end
p x: x, recursion: recursion
mymethod(x + 1) if x < 3
end
mymethod(1)
# {:x=>1, :recursion=>false}
# {:x=>2, :recursion=>true}
# {:x=>3, :recursion=>true}
By passing a start value of 0
, caller_locations
will return its own invocation which you can compare to the remaining caller locations to detect recursion, i.e. even across multiple method calls. You can limit the number of entries by passing a second limit parameter, e.g. if you only need to detect direct invocations.
Note that the above doesn't work when enabling tail call optimization because it effectively re-uses the last stack frame which makes every recursive call look like the initial one.
A more simplistic approach is to just pass an optional argument when calling the method from itself to explicitly indicate a recursive call, e.g.:
def mymethod(x, recursion: false)
p x: x, recursion: recursion
mymethod(x + 1, recursion: true) if x < 3
end
mymethod(1)
# {:x=>1, :recursion=>false}
# {:x=>2, :recursion=>true}
# {:x=>3, :recursion=>true}
Aside from debugging or for detecting (unintentional) infinite loops, I would avoid having a method behave differently based on its calling context.