Mr. Bless
Mr. Bless

Reputation: 285

Need Help to understand the piece of code

I am looking into someones code and found that he has done class eval with something like this

self.class_eval("@default_robot_engine = RobotEngine.new(some_block)")

and later it is accessed like this

self.class_eval("@default_robot_engine")

I need help to understand this code. Is there any other way to access @default_robot_engine rather than doing class_eval on it?

when I do Class.instance_variable_names I get

["@attribute_methods_mutex", "@generated_attribute_methods", "@generated_feature_methods", "@observer_instances", "@per_page", "@parent_name", "@registered_robot_engines", "@default_robot_engine", "@primary_key", "@quoted_primary_key", "@locking_column", "@attribute_methods_generated", "@table_name", "@quoted_table_name", "@arel_table", "@arel_engine", "@relation", "@columns", "@column_names", "@columns_hash", "@cached_attributes", "@attribute_method_matchers_cache", "@generated_external_attribute_methods"]

and I am able to access all the instance variable like this ClassName.registered_robot_engine except default_robot_engine. why?

Ok I got the answer because this instance variable is a dynamic one and attr_reader is not set on it so I think only way to access it is via class_eval

Upvotes: 1

Views: 135

Answers (4)

7stud
7stud

Reputation: 48599

I am able to access all the instance variable like this ClassName.registered_robot_engine except default_robot_engine. why?

class Dog
  class<< self
    attr_accessor :registered_robot_engine

    def set_stuff
      @registered_robot_engine = 'hello'
      @default_robot_engine = 20
    end
  end
end

Dog.set_stuff
puts Dog.registered_robot_engine
puts Dog.default_robot_engine

--output:--
hello
1.rb:16:in `<main>': undefined method `default_robot_engine' for Dog:Class (NoMethodError)

The basic rule in ruby is that all instance variables are private by default, so unless you provide accessor methods for an instance variable you can't access it. In the example above, there are no accessor methods defined for @default_robot_engine, so it is inaccessible, while the other instance variable does have accessor methods defined for it, so it is accessible.

Both class_eval() and instance_eval() allow you to violate encapsulation and read or write private instance variables:

class Dog
  class <<self
    def set_stuff
      @registered_robot_engine = 'hello'
      @default_robot_engine = 20
    end

    def set_more_stuff
      class_eval do 
        @default_robot_engine = 100 
      end
    end
  end
end

Dog.set_stuff
Dog.set_more_stuff

puts Dog.class_eval{ @default_robot_engine }

--output:--
100

instance_variable_set() and instance_variable_get() allow you to do the same thing:

class Dog
  def initialize
    @name = "Rover"
  end
end

d = Dog.new
d.instance_variable_set(:@name, "John")
puts d.instance_variable_get(:@name)

--output:--
John

Secondly, it is hard to imagine why the programmer didn't use standard getter and setter, as in:

class << self
  attr_accessor :default_robot_engine
end

I would guess that the programmer is using someone else's module, and the programmer has decided to violate encapsulation, which ruby allows you to do. Some languages believe that although encapsulation is good, it shouldn't be strictly enforced. If for some reason a programmer wants to violate encapsulation, they should have the freedom to do so.

Upvotes: 0

Boris Stitnicky
Boris Stitnicky

Reputation: 12578

This is a particularly weird piece of code. Firstly, self.class_eval is not necessary at all. Plain class_eval would do just right. I guess that the programmer was used more to other languages than Ruby. In Ruby, one uses explicit self receiver only in rare cases, such as when invoking methods ending with = sign, or when making sure that the method called is a public method (private methods will fail when called with explicit receiver).

Secondly, it is hard to imagine why the programmer didn't use standard getter and setter, as in:

class << self
  attr_accessor :default_robot_engine
end

# Here is the case when its legal to use explicit self receiver:
self.default_robot_engine = RobotEngine.new( some_block )

and later access it simply by

default_robot_engine

I strongly suspect the original programmer from ignorance of Ruby basics. Even though one sometimes has reasons to tamper instance variables without defining accessors, one does it preferrably not via class_eval, buyt by using #instance_variable_get/set methods:

instance_variable_set :@default_robot_engine, RobotEngine.new( some_block )
instance_variable_get :@default_robot_engine

Class eval seems to me like too big a hammer for this case.

Upvotes: 2

ChrisPhoenix
ChrisPhoenix

Reputation: 1090

Wow, this is a fun one.

1.9.3-p429 :094 > class C; self.class_eval "a=3;@b=4;@@c=5"; end
 => 5 
1.9.3-p429 :095 > C.class_variables
 => [:@@c] 
1.9.3-p429 :096 > class C; puts self.class_eval "a+@b+@@c"; end
NameError: undefined local variable or method `a' for C:Class
from (irb):96:in `class_eval'
from (irb):96:in `class_eval'
from (irb):96:in `<class:C>'
from (irb):96
from /Users/cphoenix/.rvm/rubies/ruby-1.9.3-p429/bin/irb:16:in `<main>'
1.9.3-p429 :097 > class C; puts self.class_eval "@b+@@c"; end
9
 => nil 
1.9.3-p429 :098 > 
1.9.3-p429 :098 > C.object_id
 => 2151815060 
1.9.3-p429 :099 > C.class_eval "puts self.object_id"
2151815060
 => nil 
1.9.3-p429 :100 > 

Here's what seems to be happening. When you do C.class_eval, you are executing the code in the context of the class; self is the class.

When you say C.class_variables, it prints out the things that look like class variables. That's only @@c out of the three variables I defined in line 094.

So I'm guessing that this self.class_eval is a way of defining a class variable with only one @ instead of two.

I don't know why a+@b+@@c fails to find a, but @b+@@c does find both variables. So I guess this is only a partial answer... I don't know for sure whether @b is stored in a different place than @@c, and I have no clue what happens to a.

This may just be Ruby weirdness.

Upvotes: 1

p.matsinopoulos
p.matsinopoulos

Reputation: 7810

Try reading and understanding this one:

http://www.jimmycuadra.com/posts/metaprogramming-ruby-class-eval-and-instance-eval

It is very useful.

Upvotes: 0

Related Questions