Implicit Contexts in Ruby

Pre-requisites

In order to properly understand this article you will need to understand:

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.

References

Yugui tutorial post

Daniel Azuma - instance_eval vs class_eval

Written on November 26, 2023