For exmaple, the method bar() is located at last of method foo(), so bar() is "tail call".

def foo()
...
bar()
end

In this case, after invocation of method bar(), foo()'s method frame information (which contains local variables, program counter, stack pointer and so on) is no longer needed because method foo() doesn't work after that (correctly, method foo() only does "return").

Next example, a simple recursion code by foo(). Of course, foo() is "tail call".

*1: Generally, tail-recursion optimization includes another optimization technique - "call" to "jump" translation. In my opinion, it is difficult to apply this optimization because recognizing "recursion" is difficult in Ruby's world.

Next example. fact() method invocation in "else" clause is *not* a "tail call".

def fact(n)
if n < 2
1
else
n * fact(n-1)
end
end

If you want to use tail-call optimization on fact() method, you need to change fact() method as follows (continuation passing style).

def fact(n, r)
if n < 2
r
else
fact(n-1, n*r)
end
end

In this case, fact() is tail-call (and a bit difficult to read/write).

Of course, the following code is easy to understand and short.

(1..n).inject(:*)

Last examples. Recognizing tail-call is a bit difficult.

def foo
begin
bar2() # not a tail-call
rescue
bar3() # not a tail-call
rescue
bar4() # not a tail-call
ensure
bar5() # tail-call!
end
end