In Ruby, when defining the contents of a class with class_exec, I am getting unexpected results. When I define a class variable in the block sent to class_exec, the class variable is being defined on Object instead of the class on which class_exec is being called:
class X; end
X.class_exec do
##inner_value = "123"
def inner_value
##inner_value
end
def inner_value=(arg)
##inner_value = arg
end
end
obj1 = X.new
puts obj1.inner_value
puts ##inner_value
puts Object.class_variables
Produces:
123
123
##inner_value
This does not happen when using class_eval:
X.class_eval(<<-RUBY)
##inner_value = "123"
def inner_value
##inner_value
end
def inner_value=(arg)
##inner_value = arg
end
RUBY
obj1 = X.new
puts obj1.inner_value
puts ##inner_value
puts Object.class_variables
Produces:
123
and an error:
uninitialized class variable ##inner_value in Object (NameError)
The results with class_eval are what I would expect to happen in both cases. I have tried this with both MRI 1.8.7 and MRI 1.9.3 and got the same results running on Windows XP.
Is this expected behavior? If so, why? If not, bug?
class variables are bound to the class in which they are declared at compile time. The block passed to class_exec is compiled before it is passed to class_exec, so the class variables are bound to Object.
I guess your class_exec is at the top level, which is in Object, so that's where they go. To demonstrate:
public
class Object
##x = "ribbit"
end
def foo
puts "test: #{##x}"
end
x = Object.new
x.foo
This is why when you use class vars in a module, all classes that include that module (through the included methods) will see the same class variables. The class variables are bound to the module. If you run this:
class WithClassVars
def self.classvars
#classvars ||= {}
end
def classvars
self.class.classvars
end
end
class A < WithClassVars;end
class B < WithClassVars;end
a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2
puts a.classvars
puts b.classvars
a and b will end up with the same data.
If you pass your code as a string to class_eval, the string is compiled in class_eval, so you can make sure they are in the right class then.
So if you want to store per-class data, you have to either go with class_eval, or use some mechanism to use a class's instance variables. Say:
class WithClassVars
def self.classvars
#classvars ||= {}
end
def classvars
self.class.classvars
end
end
class A < WithClassVars;end
class B < WithClassVars;end
a = A.new
b = B.new
a.classvars[:a] = 1
b.classvars[:a] = 2
puts a.classvars
puts b.classvars
Related
I am trying to access a class variable from a subclass. I understand that class variables are not inherited which answers the question of why the code does not work, however I don't fully understand how I can work around it.
This is my code:
Class A
...
class << self
def format(a, b)
#format = a
end
def item(a, b)
#item[a] = b
end
end
end
Class B < A
format 4, 7
item 7, 12
...
end
Class C < B
item 7, 18
end
Running the following in a irb session
B.format => 4
C.format => nil
So understanding that class variables aren't inherited, is it possible to make C.format => 4 or will I need to refactor as such:
Class B < A
format 4, 7
item 7, 12
end
Class C < A
format 4, 7
item 7, 18
end
My reason for wanting to avoid the latter is that I have a lot of variables defined in that same way (calling a function to set a class variable) and I don't want to have to duplicate all the code across class B and C because one instance variable differs.
I understand that class variables are not inherited
This is completely false and you have not actually understood what class variables are.
A class variable in Ruby is declared with the ## sigil. And they are definitely inherited:
class A
##x= "Hello World"
end
class B < A
puts ##x # Outputs "Hello World"
end
Class variables are actually shared between a class and its subclasses:
class Animal
##greating = "Hello"
def greet
"#{##greating} I'm a #{self.class.name.downcase}"
end
end
class Cat < Animal
##greating = "Meow"
end
class Dog < Animal
##greating = "Woof"
end
puts Dog.new.greet # Woof I'm a dog
puts Cat.new.greet # Woof I'm a cat
puts Animal.new.greet # Woof I'm a animal
As you can see from the example this can often lead to unexpected and undesireable effects. And class variables are not considered thread safe.
What you are actually setting is referred to as a class instance variable - this is just a instance variable except that its scope is not a instance of A. Instead its scope is the singleton class A which is an instance of the Class class.
Unlike true class variables which have their own sigil class instance variables are not shared between a class and its subclasses as thier scope is the singleton class. Every subclass its own singleton class.
class Animal
#greating = "Hello"
def self.greeting
#greating
end
def greet
"#{self.class.greeting} I'm a #{self.class.name.downcase}"
end
end
class Cat < Animal
#greating = "Meow"
end
class Dog < Animal
#greating = "Woof"
end
puts Dog.new.greet # Woof I'm a dog
puts Cat.new.greet # Meow I'm a cat
puts Animal.new.greet # Hello I'm a animal
Class instance variables are actually far more useful then true class variables. If you want to simulate the inheritance of class variables with class instance variables you can do it with the Class#inherited callback provided by Ruby:
class A
#x = [self.name]
class << self
attr_accessor :x
def inherited(subclass)
puts "#{subclass.name} is inheriting from #{self.name}"
subclass.x = x
subclass.x.push(subclass.name)
end
end
end
class B < A; end # outputs "B is inheriting from A"
class C < B; end # outputs "C is inheriting from B"
puts B.x.inspect # => ['A', 'B']
puts C.x.inspect # => ['A', 'B', 'C']
Firstly, to define a class you need to use the keyword class not Class.
Secondly, there is no reason to pass the method A::format an argument it does not use. I therefore changed it to have just one argument.
Thirdly, after defining A, when A.item is first executed an exception will be raised because #item has not been defined. Specifically, #item (like any other undefined instance variable) will return nil when called and nil has no no method []. I therefore changed the method item to initialize the class instance variable (not class variable) #item to an empty array.
class A
class << self
def format(a)
#format = a
end
def item(a, b)
#item = []
#item[a] = b
end
end
end
class B < A
format 4
item 7, 12
end
class C < B
item 7, 18
def self.format
superclass.instance_variable_get(:#format)
end
end
It is my understanding that you want a method C::format to return the value of B's instance variable #format.
C.format
#=> 4
If one attempts to execute B.format an exception will be raised because B::format requires two arguments. If B.format is meant to return the value of B's class instance variable #format you need to write:
B.instance_variable_get(:#format)
#=> 4
You might add read and write accessors for the the instance variable #format, in which case your code would be simplified somewhat:
class A
class << self
attr_accessor :format
def item(a, b)
#item = []
#item[a] = b
end
end
end
class B < A
#format = 4
item 7, 12
end
class C < B
item 7, 18
def self.format
superclass.format
end
end
C.format
#=> 4
I understand class variables should start with ##. Now I have this class:
class C
#v = "my variable"
def self.get_v
#v
end
end
puts C.get_v #returns "my variable"
Is #v still a class variable? But its not having ##?
No, it is a class instance variable, i.e. an instance variable of the class object C.
The difference between the two is that the former is visible inside all the classes of a hierarchy, the latter is visible only inside the class that defines it. A simple example to (I hope) explain:
class A
#a = 0 # class instance variable
##b = 1 # class variable
end
class B < A
#a = 1
##b = 2
end
puts A.a # => 0
# the definition of ##b inside B have altered the value
# inside A. It's not the case for #a.
puts A.b # => 2
No, it's not a class variable. It's a regular instance variable. But it belongs to an instance of Class object. It's important not to confuse the two.
Do the introspection as below, you would get the answer:
class C
#v = "my variable"
def self.get_v
defined? #v
end
end
puts C.get_v #=> instance-variable
class C
#v = "my variable"
def self.get_v
self.instance_variables
end
end
puts C.get_v #=> #v
It means #v is class instance variable.
class C
#v = "my variable"
def self.get_v
self.new.instance_variables
end
end
p C.get_v #=> []
This is because #v is not instance variable of the instances of class C.
Same introspection you can do for the class variables also:
class C
##v = "my variable"
def self.get_v
defined? ##v
end
end
p C.get_v #=> "class variable
In summary I can say ask the question to your object as above, it has the answers for you.
I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end
If creating a class variable is often dangerous and unpredictable why do we need them?
If solution is just to use class instance variable with the class level accessors:
class Foo
#variable = :something
def self.getvariable
#variable
end
def self.setvariable(value)
#variable = value
end
end
Then why do we need class variables???
Class variables have their use on occasion, but I agree that using the eigenclass is frequently more useful:
class Foo
#bar = 'bar'
class << self
attr_accessor :bar
end
end
puts Foo.bar # bar
puts Foo.bar = 'baz' # baz
The above is safe with inheritance, because it sets a variable in the Foo constant, rather than a class variable.
Foo.new.instance_eval { puts ##bar } # error
This has several causes:
It's syntactic sugar. You can always get a class variable (whether you are in class or instance scope) using ##var. This won't work for instance variables of the class.
Class variables persist for the singleton classes of the instances of this class. Example:
class Test
#instance_var = 0
##class_var = 0
def self.instance_var
#instance_var
end
def self.class_var
##class_var
end
end
Test.instance_var #=> 0
Test.class_var #=> 0
Test.new.singleton_class.instance_var #=> nil
Test.new.singleton_class.class_var #=> 0
Here is an example (think ActiveRecord):
class Base
def connect(connection)
##connection = connection
end
def connection
##connection
end
end
class User < Base
end
class SuperUser < User
end
Base.new.connect("A connection")
puts User.new.connection #=> A connection
puts SuperUser.new.connection #=> A connection
The trick here is that class variable is accessible from an instance method and is inherited. Try this:
class Base
def self.connect(connection)
#connection = connection
end
def self.connection
#connection
end
def connection
self.class.connection
end
end
class User < Base
end
Base.connect("A connection")
puts User.new.connection #=> nil
You will get nil as self.connection tries to access it's own class instance variable (from User class), and it is not inherited.
Added: And yes, it can be dangerous if you misuse it:
##a = "A"
class A
def self.a
##a
end
def a
##a
end
end
puts A.a #=> A
puts A.new.a #=> A
class X
end
class A
def get_it
puts #the_variable
end
end
class B
def init_it
#the_variable = X.new
a = A.new
end
end
In the above code I want methods of Class A to access the instance of X instantiated in B
Try using Object#instance_variable_set:
class B
def init_it
#the_variable = X.new
a = A.new
a.instance_variable_set(:#the_variable, #the_variable)
end
end