Reputation: 285
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
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
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
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
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