visit
Warning the code below is not meant for use in production (especially the last 3 examples). It is just my exploration of the language capabilities. Some of the techniques shown are useful in a bunch of cases, but make sure to proceed with caution. Simplicity, security and readability are more important than fancy stuff.
class User
def initialize(name)
@name = name
end
def hello
puts "Hello, #{@name}!"
end
def method_missing(_)
hello
end
end
user = User.new('Gregory')
user.hello()
Not much to see here - this is how you call methods in plenty of languages. Interesting fact: you can put spaces around the dot:
user . hello()
will work as well.user.hello
user.send(:hello)
user.public_send(:hello)
In this case we pass the name of the function to be called as an argument to either send or
public_send
methods that are defined in every class. The difference between send and public_send
is that the latter respects the privacy of methods - if you try to call private method, it will raise an error, while send will still call it.I'm passing the name of the function using symbol type (
:hello
), but you can use string as well ("hello"
).user.method(:hello).call
user.method(:hello).()
user.method(:hello)[]
3 examples, with the 2nd and 3rd being just a syntax sugar, so I put them together. This one is quite interesting. Calling
user.method(:hello)
returns an instance of Method class. This object can be passed around as any value and can be called any time - it also stores the reference to the object to which it belongs, so if I change the user's name, the new one will be used:method = user.method(:hello)
user.set_instance_variable(:@name, "Not Only Code")
method.call() # prints "Hello, Not Only Code!"
The
.()
and []
are equivalent of .call()
and can also take arguments - proc.call(1,2,3)
, proc.(1,2,3)
and proc[1,2,3]
will all work the same way (the last one won't support named arguments though).user.tap(&:hello)
tap
is a funny little method that takes a block, passes itself as an argument there and executes the block, and then finally returns itself. I rarely use it, but there are cases where it might be helpful (like side effects when chaining methods).The
&:hello
syntax turns the :hello
symbol into a Proc instance. You can read more about this syntax on - it's really well explained there. Proc
is a callable object, just like Method
from previous examples.:hello.to_proc.call(user)
I like this one because it reverses the order - user becomes the argument of the function. What happens under the hood here is very similar to the previous one - the
call
function of Proc
passes the initial symbol to the argument received. Something similar to this:class Proc
def call(obj)
obj.send(@symbol_used_to_create_proc)
end
end
class User
def method_missing(_)
hello
end
end
user.i_am_a_lizard_king # prints "Hello, Gregory!"
user.i_can_do_everything # prints "Hello, Gregory!"
method_missing
is a method that will be executed when object receives a call to a method that is not defined. It's a powerful function that is one of fundaments of Ruby's flexibility, however it might lead to bugs that take ages to find (and to some perfomance issues), so use it with caution.eval("user.hello")
Again, a bit of cheating, since I still use the standard method call syntax, but how it works under the hood is obviously very different.
eval
passes the string to Ruby parser and interpreter just as if it was a part of my code, and then executes the code. You should definitely, absolutely avoid using it in your code, especially if you allow users to pass some values to your application.require 'method_source' # external gem
method_source = user.method(:hello).source
method_body = method_source.split("\n")[1...-1].join(";")
user.instance_eval(method_source)
user.method(:hello).source
returns the source of the method as a string. The output of this is the whole body (including the spaces): def hello
puts "Hello, #{@name}!"
end
How does
method_source
gem do it? The Method
class in Ruby has a source_location
function that returns the location of the method's source code - file and line number where the method starts. Then method_source
essentially opens that file, finds the respective line, looks for end that will end the method and returns the code in between.Now that I have the full code of the method, I want to remove the method definition and the end. In my case I just remove the first and last line, but if the method was a one-liner, it would require some changes. The output of the 2nd line is a string with value:
puts "Hello, #{@name}!"
.Finally, I take this string and I pass it to
instance_eval
of my user object. instance_eval
works kind of like eval except that it executes code in different scope. If I call eval it would execute code in scope of my whole file, where the @name
variable is not defined. By passing it to instance_eval
I ensure it uses the correct values.Also published at: //www.notonlycode.org/12-ways-to-call-a-method-in-ruby/