Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does instance_eval() define a class method when called on a class?

Foo = Class.new
Foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end
puts Foo.instance_bar       #=> "instance_bar"
puts Foo.new.instance_bar   #=> undefined method ‘instance_bar’

My understanding is that calling instance_eval on an object is supposed to allow you to define an instance variable or method for that object.

But in the example above, when you call it on class Foo to define the instance_bar method, instance_bar becomes a class method that can be invoked with "Foo.instance_bar". It is clear that this code has not created an instance method because Foo.new.instance_bar results in "undefined method ‘instance_bar’".

Why does instance_eval define a class method rather than an instance method in this context?

like image 248
pez_dispenser Avatar asked May 23 '09 02:05

pez_dispenser


2 Answers

x.instance_eval changes your context so self evaluates to x.

This allows you to do many things, including defining instance variables and instance methods but only for x.

 x = Object.new
 y = Object.new

 # define instance variables for x and y
 x.instance_eval { @var = 1 }
 y.instance_eval { @var = 2 }

 # define an instance method for all Objects
 class Object
   def var
     @var
   end
 end

 x.var #=> 1
 y.var #=> 2

Ruby lets you define instance methods for an object in a couple places. Normally, one defines them in a class, and those instance methods are shared among all instances of that class (like def var above).

However, we can also define an instance method for just a single object:

# here's one way to do it
def x.foo
  "foo!"
end
# here's another
x.instance_eval do
  # remember, in here self is x, so bar is attached to x.
  def bar
    "bar!"
  end
end

Even though x and y have the same class, they don't share these methods, since they were only defined for x.

x.foo #=> "foo!"
x.bar #=> "bar!"
y.foo #=> raises NoMethodError
y.bar #=> raises NoMethodError

Now in ruby, everything's an object, even classes. Class methods are just instance methods for that class object.

# we have two ways of creating a class:
class A 
end
# the former is just syntatic sugar for the latter
B = Class.new

# we have do ways of defining class methods:

# the first two are the same as for any other object
def A.baz
  "baz!"
end
A.instance_eval do
   def frog
     "frog!"
   end
end

# the others are in the class context, which is slightly different
class A
  def self.marco
    "polo!"
  end
  # since A == self in here, this is the same as the last one.
  def A.red_light
    "green light!"
  end

  # unlike instance_eval, class context is special in that methods that
  # aren't attached to a specific object are taken as instance methods for instances
  # of the class
  def example
     "I'm an instance of A, not A itself"
  end
end
# class_eval opens up the class context in the same way
A.class_eval do
  def self.telegram
    "not a land shark"
  end
end

Note again, that all these methods are A-specific, B doesn't get access to any of them:

A.baz #=> "baz!"
B.telegram #=> raises NoMethodError

The important thing to take away from here is that class methods are just instance methods of an object of class Class

like image 122
rampion Avatar answered Sep 28 '22 08:09

rampion


The purpose of 'instance_eval' is to extend objects, but the purpose of 'class_eval' is to extend classes. And because classes are also objects, you can apply instance_eval on classes.

I guess that extending of classes is just more understandable in classic OOP. Dynamic languages allow us to easily specify behaviour of particular objects. Fact that each object can have own behaviour adds a lot of flexibility designing an application. Not only data can vary for objects of the same class. Two humans differ not only because they were born in different years, not only because they have different parents but they can think different and thus behave different.

Ability to change the behaviour of each object is fundamental. It exists in many languages.

Thinking about instance_eval think about objects first. Then you'll realize that classes are also objects - objects which additional purpose to create new objects, to hold description of common behaviour (methods). You can not only use definition of a class, but can also assign class to a variable, pass class as an argument, call method on a class, program a class!

I would recommend articles written by Yehuda Katz and Yugui to dive deeper into it:

  • http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/
  • http://yugui.jp/articles/846
like image 43
Grimmo Avatar answered Sep 28 '22 09:09

Grimmo