Implicit Contexts in Ruby
Pre-requisites
In order to properly understand this article you will need to understand:
- ruby’s self
The Difference between instance_eval
and class_eval
class Person
# where is name defined?
def name
puts "Ben"
end
end
- On Person?
- On an instance?
- On a singleton class?
Person.name # undefined method `name' for Person:Class
ben = Person.new
ben.name # => "Ben" # 'name' is on the person class? Absolutely!
puts Person.instance_method(:name)
# => #<UnboundMethod: Person#name() instance_eval_post.rb:7>
Methods in ruby are defined in classes - not on objects per se - but on classes. It could be a singleton class, or a “normal” class, but they must always be in a class.
Keep this in mind as we work through the following exercises:
Person.instance_eval do
# what is self?
puts "self in instance eval is: #{self}"
# => self in instance eval is: Person
end
Person.instance_eval do
@name = "Ben" # Where is `@name` defined?
end
This time, @name
is defined on the Person class.
puts Person.instance_variable_get(:@name) # On the Person class
Key take-out: instance_eval
set’s self
to the receiver.
class Person
def name
puts "Ben Koshy"
end
end
ben = Person.new
Person.instance_eval do
# where would hobby be defined?
def hobby
"golf"
end
end
Where would the hobby
method be defined? If you said hobby
is on the Person class, you’d be wrong:
ben.hobby # no method error
# => instance_eval_post.rb:29:in `<main>': undefined method `hobby' for #<Class:Person> (NoMethodError)
puts Person.instance_method(:hobby)
# => instance_eval_post.rb:28:in `instance_method': undefined method `hobby' for class `Person' (NameError) from instance_eval_post.rb:28:in `<main>'
puts Person.hobby # hobby is defined on the Person singleton class
# => golf
hobby
is defined on Person’s singleton class. Consider this:
class Person
def name
end
end
ben = Person.new
ben.instance_eval do
# where is hobby defined?
def hobby
"golf is life"
end
end
tiger = Person.new
tiger.hobby # will this work?
Person.hobby? # Will this work?
Where is hobby
defined now?
- on
ben
? - on the
Person
class? - on the
Person
singleton class?
If you said tiger plays golf: you’d be wrong:
# this will work
ben.hobby # => golf is life
# but this will fail:
tiger = Person.new
tiger.hobby # tiger does not know about hobby - because it is defined on ben's singleton class
Person.hobby? # this will fail too.
puts ben.singleton_class.instance_method(:hobby)
The difference is subtle.
Let’s dig a little deeper and go back to the original example:
class Person
def name
end
end
What’s going on with instance_eval
?
It changes multiple contexts. It changes self
- but it does more than this.
Why is it that name
is defined so that it is an instance method on the Person class?
The answer is: ruby keeps track of a “current_class”. If you define a method - that will be defined in the context of the “current_class”. When you define a method in a Person
class - the “current_class” is set to “Person” and all methods will be defined on this “current class” till this implicit context is changed.
But when you are defining methods using instance_eval
, then the class where those methods are designed - will be the singleton class of the receiver - in this case, it will be ben’s singleton class.
class Person
def name
"Ben"
# where will last name be defined?
def last_name
"Koshy"
end
end
end
# Hint: think about what the "current_class" is.
Answer:
class Person
def name
"Ben"
def last_name
"Koshy"
end
end
end
# Does not work
Person.instance_method(:last_name) # => instance_eval_post.rb:11:in `instance_method': undefined method `last_name' for class `Person' (NameError)
# Does not work
Person.singleton_method(:last_name) # => undefined singleton method `last_name' for `Person' (NameError)
What? Nothing works? Surely one or the other must work - last_name is defined on something?
The answer is obvious but subtle: last_name
is only defined when name
is run. I admit it’s a trick question, but it’s a subtle issue that can trip you up if unaware.
class Person
def name
"Ben"
def last_name # where is it defined?
"Koshy"
end
end
end
Person.instance_method(:last_name) # => instance_eval_post.rb:11:in `instance_method': undefined method `last_name' for class `Person' (NameError)
Person.singleton_method(:last_name) # => undefined singleton method `last_name' for `Person' (NameError)
ben = Person.new
ben.name # Let's try again:
puts Person.instance_method(:last_name) # this works now!
If you said last_name
is defined on the Person
you’d be right. But why? At the point last_name
is defined: the “current class” is still Person
. Methods must be defined on classes. In this case - the class is Person.
Ruby can be tricky. You not only have to understand what self
is - but you have to know where methods will be defined. There are two implicit contexts.
Tell me where middle_name
will be defined?
class Person
def name
"Ben"
end
end
Person.instance_eval do
def last_name
def middle_name
"Chenathara"
end
end
end
Consider the answer below:
Person.last_name
puts Person.middle_name # => Chenathara
All you have to remember is that instance_eval
changes the “current class” as well as the implicit receiver “self”.
class_eval
class_eval is different to instance_eval, because it set’s the receiver’s current_class to be the receiver, in addition to also setting self
to be the receiver.