Instance methods
TweetWhen you define a method on a class:
class Dog
def bark
'woof'
end
end
You can use the method like this:
dog = Dog.new
dog.bark
=> "woof"
But not like this:
Dog.bark
# NoMethodError: undefined method `bark` for Dog:Class
How come we can only define methods on the class but only use them in the instance and not the class?
Last week, I wrote about Singleton Classes, and I showed how class methods and instance methods are two sides of the same coin, and how you can make Dog.bark
work.
But what ARE methods, and why are they defined on Classes, and not on other kinds of Objects?
First, lets look at some methods Ruby provides to find out about methods on a class:
Dog.methods
=> [:new, :allocate, :superclass, :meow, :<=>, :module_exec, :<=, :>=, :==, :===, :include?, :name, :ancestors, :included_modules, :instance_methods, :public_instance_methods, :protected_instance_methods, :constants, :const_get, :private_instance_methods, :const_set, :class_variables, :const_defined?, :remove_class_variable, :class_variable_set, :class_variable_defined?, :class_variable_get, :public_constant, :private_constant, :deprecate_constant, :singleton_class?, :class_eval, :freeze, :inspect, :const_missing, :public_method_defined?, :class_exec, :prepend, :method_defined?, :<, :protected_method_defined?, :>, :private_method_defined?, :public_class_method, :private_class_method, :to_s, :autoload, :autoload?, :module_eval, :instance_method, :public_instance_method, :include, :tap, :public_send, :instance_variables, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :private_methods, :kind_of?, :is_a?, :instance_variable_get, :instance_of?, :define_singleton_method, :method, :extend, :public_method, :singleton_method, :to_enum, :enum_for, :=~, :!~, :eql?, :respond_to?, :display, :object_id, :send, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :trust, :untrusted?, :methods, :protected_methods, :frozen?, :public_methods, :singleton_methods, :!, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]
Oops that was a lot of output. It turns out #methods
accepts a parameter that when set to false
, only returns the methods on the object.
Dog.methods(false)
=> []
Uh, okay :bark
is not there. Well that makes sense since Dog
is just an object that is an instance of Class
. If we call #methods
on an instance of it:
Dog.new.methods.select{|x| x == :bark}
=> [:bark]
Excellent. So it is one of the Dog’s methods.
If you are wondering why I did not use
methods(false)
, scroll down to the You were probably asking… section
I can extract this method by doing:
Dog.new.method(:bark)
=> #<Method: Dog#bark>
And I can even call it!
m = Dog.new.method(:bark)
m.call
=> "woof"
Excellent. There is another method called #instance_methods
that will give us the instance methods of a class:
Dog.instance_methods(false)
=> [:bark]
Similarly, I can extract the method this way:
Dog.instance_method(:bark)
=> #<UnboundMethod: Dog#bark>
m = Dog.instance_method(:bark)
m.call
# NoMethodError: undefined method `call` for #<UnboundMethod: Dog#bark>
Wait, I can’t call an UnboundMethod
?
Unbound methods
What is an UnboundMethod
anyway?
Well it turns out that all methods in Ruby need to be ‘bound’ to an object. In other words, every method in Ruby actually has a hidden self
parameter:
# Like this, but not really.
def look_at(self, thing_to_look_at)
end
# You can't do the above either, since self is a keyword, you can't use it as a parameter name
Without this parameter, methods cannot be called. This is why (as shown above), an UnboundMethod
has no #call
method.
And the way you pass an argument to this self
parameter is by using the #bind
method on an UnboundMethod
:
um = Dog.instance_method(:bark)
um.bind(Dog.new)
=> #<Method: Dog#bark>
Now we can call it again!
um = Dog.instance_method(:bark)
m = um.bind(Dog.new)
m.call
=> "woof"
Similarly you can call #unbind
to get the UnboundMethod
of any method:
m = Dog.new.method(:bark)
um = m.unbind
=> #<UnboundMethod: Dog#bark>
Does this mean I can transfer methods to other objects?
Well, no not really. Say you had something like this:
class NamedDog < Dog
def initialize(name)
@name = name
end
def bark
"#{@name}: woof"
end
end
# Lets say there were two dogs:
lady = NamedDog.new('lady')
tramp = NamedDog.new('tramp')
You can then grab the unbound method #bark
and bind it to any other NamedDog
.
NamedDog.instance_method(:bark).bind(lady).call
=> "lady: woof"
But you can’t bind it to anything else:
NamedDog.instance_method(:bark).bind(Object.new)
# TypeError: bind argument must be an instance of NamedDog
You can however, bind it to any object that inherits from NamedDog
:
class Dalmatian < NamedDog
end
pongo = Dalmatian.new('pongo')
NamedDog.instance_method(:bark).bind(pongo).call
=> "pongo: woof"
You can also bind the old #bark
method from Dog
:
Dog.instance_method(:bark).bind(tramp).call
=> "woof"
You were probably asking…
Why did I do that big #select
on methods, why can’t I just go Dog.new.methods(false)
?
Dog.new.methods(false)
=> []
What, why? Why don’t we see :bark
which we defined on Dog
? Why did it appear in the call to #methods
but not here?
Well, if you have read my post about assigning methods to objects, you would be aware that you can do this:
paco = Dog.new
def paco.complain
'definitely not stoked'
end
paco.methods(false)
=> [:complain]
#methods
only returns methods available to an object that are defined on their Singleton Class.
If you check the singleton class for instance methods, you will find :complain
there:
paco.singleton_class.instance_methods(false)
=> [:complain]
Methods defined on Singleton Classes can only be bound to their instance (which makes sense since Singleton Classes are unique to a particular instance). So this does not work:
paco.singleton_class.instance_method(:complain).bind(lady)
# TypeError: singleton method called for a different object
Even though lady
is a Dog, it is not paco
, so a method defined on paco
’s singleton class is not going to bind to lady
.
Class Methods
This same mechanism is how Class Methods can be defined on one class and not another. (again, see my post about assigning methods to objects for more detail).
def Dog.kingdom
'animal'
end
Dog.methods(false)
=> [:kingdom]
Dog.singleton_class.instance_methods(false)
=> [:kingdom]
k = Dog.singleton_class.instance_method(:kingdom)
=> #<UnboundMethod: #<Class:Dog>#kingdom>
k.bind(Object)
# TypeError: singleton method called for a different object
BUT, You can bind a Dog
’s instance method to NamedDog
, since it is a child class of Dog
.
k = Dog.singleton_class.instance_method(:kingdom)
=> #<UnboundMethod: #<Class:Dog>#kingdom>
m = k.bind(NamedDog)
=> #<Method: NamedDog(Dog).kingdom>
m.call
=> "animal"
This is possible because the singleton class of NamedDog
is the child of the singleton class of Dog
. So the same rules that apply to classes apply here.
NamedDog.singleton_class.ancestors
=> [
#<Class:NamedDog>,
#<Class:Dog>,
#<Class:Object>,
#<Class:BasicObject>,
Class,
Module,
Object,
Kernel,
BasicObject]
So to answer the question
The reason methods are defined on classes in Ruby is because of how methods are generally expected to be used in Ruby. You would declare a class that has a bunch of methods, and instantiate a bunch of objects that use those methods.
class SomeClass
def some_method()
self.class.name
end
end
The alternative to this would be to copy the routine to each instance when a class is instatiated.
Extravagance aside, this would remove the need for singleton classes, but it would be impossible to modify the behavior of all instances of a class at once by modifying the class, which is another great feature of Ruby.
# You can redefine methods right after you have defined them
# existing instances will change behaviour too
class Dog
def bark
"wewf"
end
end
shiitake = Dog.new
shiitake.bark
=> "wewf"
class Dog
def bark
"wan"
end
end
shiitake.bark
=> "wan"
Hopefully this post has gone some way to explain the nature of methods and explains Ruby singleton classes and classes even more clearly, and would help you understand better how metaprogramming in Ruby is done.
Its difficult to remember so many code snippets
So we compiled a small booklet to hand out at RubyKaigi 2019, that contains lots of small examples of using Ruby and its less obvious features. We’re giving them out for free too because we are nice like that and hopefully everyone will find it useful!
We will be at the fifth floor of the conference so please come over and have a chat with us!