Instance_eval: why a subclass is a superclass
def singleton_class
class << self
self
end
end
class Human
proc = lambda { puts 'proc says my class is ' + self.name.to_s }
singleton_class.instance_eval do
define_method(:lab) do
proc.call
end
end
end
class Developer < Human
end
Human.lab # class is Human
Developer.lab # class is Human ; oops
The following solution works.
def singleton_class
class << self
self
end
end
class Human
proc = lambda { puts 'proc says my class is ' + self.name.to_s }
singleton_class.instance_eval do
define_method(:lab) do
self.instance_eval &proc
end
end
end
class Developer < Human
end
Human.lab # class is Human
Developer.lab # class is Human ; oops
Why does Developer.lab say it's Human? And what can be done to process proc Developer reports when Developer.lab is called.
a source to share
It's subtle, but it boils down to just calling the block (in this case, it acts like a normal closure, but self
matches where it was defined, i.e. in Human
), or uses it (directly) as a method definition block, or instance_eval
:
def singleton_class
class << self
self
end
end
class Human
PROC = proc { puts 'proc says my class is ' + self.name.to_s }
singleton_class.instance_eval do
define_method(:lab) do
PROC.call
end
define_method(:lab2, &PROC.method(:call))
define_method(:lab3) do
instance_eval(&PROC)
end
define_method(:lab4, &PROC)
end
end
class Developer < Human
end
Human::PROC.call # => "class is Human" (original closure)
Developer.lab # Same as previous line, so "class is Human" (original closure)
Developer.lab2 # ditto
Developer.instance_eval(&Human::PROC) # => "class is Developer" (instance_eval changes sets a different scope)
Developer.lab3 # Same as previous line, so "class is Developer"
Developer.lab4 # ditto
a source to share
A closure captures self in the context where it is defined, just like closures are supposed to do. So when it is called, it will use references to the context it has captured. Closure is not an ideal tool for defining intended functionality. Instead of proc.call, the content of the "define_method" call should be "puts" proc says my class is "+ name.to_s"
a source to share
I need to think a little about why this works, but for now this works:
class Human
proc = -> { name }
define_singleton_method(:lab, &proc)
end
class Developer < Human; end
require 'test/unit'
class TestClosures < Test::Unit::TestCase
def test_that_the_human_class_is_named_human
assert_equal 'Human', Human.lab
end
def test_that_the_developer_class_is_named_developer
assert_equal 'Developer', Developer.lab
end
end
a source to share