Here's what I'm doing:
$ cat 1.rb
#!/usr/bin/env ruby
class A
#a = 1
##b = 2
def self.m
p #a
p ##b
end
end
class B < A
end
class C < A
##b = 3
end
B.m
$ ./1.rb
nil
3
I expected to see 1 and 2. I don't really understand why and what can I do?
These other posts should help you with your question:
Difference between class variables and class instance variables?
Ruby class instance variable vs. class variable
Class variables are accessible by subclasses, but instance variables bound to a class object are not.
class A
#a = 1 # instance variable bound to A
##b = 2 # class variable bound to A
end
class B < A; end # B is a subclass of A
# B has access to the class variables defined in A
B.class_variable_get(:##b) # => 2
# B does not have access to the instance variables bound to the A object
B.instance_variable_get(:#a) # => nil
Instance variables bound to a class object are often referred to as 'class instance variables'.
Related
Why can't I access an instance variable directly in ruby without using an accessor method or instance_variable_get?
class Foo
#my_var
end
Why shouldn't we be able to use Foo.#my_var in this example?
The example provided by the OP is a class instance variable. These can only be accessed by class methods.
"regular" attribute accessors won't allow access from outside the class. Here are a couple of ways to create accessors that work:
class A
#class_instance_var = "foo"
class << self
attr_accessor :class_instance_var
end
end
puts A::class_instance_var # Output: foo
OR
class A
#class_instance_var = "foo"
def self.class_instance_var
#class_instance_var
end
end
puts A::class_instance_var # Output: foo
Class instance variables
Class instance variable names also begin with #. However, they are defined at class level, outside any methods. Class instance variables can only be accessed by class methods. They are shared amongst all instances of a class but not its subclasses. In other words, they are not inheritable. If the value of a class instance variable is changed in one instance of the class, all other instances are affected. Earlier we saw how all classes are instances of a built-in class called Class. That is what makes class instance variables possible.
class Vehicle
#count = 0 # This is a class instance variable
def initialize
self.class.increment_count
self.class.show_count
end
def self.increment_count # This is a class method
#count += 1
end
def self.show_count # This is a class method
puts #count
end
end
class Car < Vehicle
#count = 0
end
v1 = Vehicle.new # Output: 1
v2 = Vehicle.new # Output: 2
v3 = Vehicle.new # Output: 3
car1 = Car.new # Output: 1
car2 = Car.new # Output: 2
v3 = Vehicle.new # Output: 4
Let's review the example above. A class instance variable called #count is set in the Vehicle class, with an initial value of 0. Every time the Vehicle class is instantiated, the initialize method calls self.increment_count to increment the value of #count and self.show_count to return the new value. Then, we have the Car class, which is a subclass of Vehicle and inherits all of its methods. However, it does not inherit the #count class instance variable, as this type of variable is not inheritable. That's why the counter works within the Car class, but it has its own count.
Methods prefixed with self., such as self.increment_count and self.show_count, are class methods. That is the only kind of method capable of accessing class instance variables.
That's just not how the language is built. Maybe look at openstruct
require 'ostruct'
obj = OpenStruct.new(my_var: 1)
obj.my_var
# => 1
By the way, you're method of setting up an instance variable is not correct. You should only be setting instance variables inside instance methods or initialize, otherwise use class variables or constants.
An example with constants:
class Foo
MyVar = 1
end
Foo::MyVar
# => 1
You could also make Foo.new.#my_var work with method_missing:
class Foo
def method_missing(m, *args, &block)
self.instance_variable_get(m)
end
def initialize
#my_var = 1
end
end
Foo.new.#my_var
# => 1
In Java, I can declare a public member of a class, but it seems I cannot do this in Ruby.
class Person
#a = 1
def hello()
puts(#a)
end
end
p = Person.new
p.hello()
#nil
Why is the output nil rather than 1?
Because instance variable #a is not initialized for the instance pr.
class Person
#a = 1
def hello()
puts(#a)
end
end
pr = Person.new
pr.instance_variable_get(:#a) # => nil
Now see below:-
class Person
def initialize(a)
#a=a
end
def hello()
puts(#a)
end
end
pr = Person.new(1)
pr.instance_variables # => [:#a]
Person.instance_variables # => []
pr.instance_variable_get(:#a) # => 1
pr.hello # => 1
Instance variables
An instance variable has a name beginning with #, and its scope is confined to whatever object self refers to. Two different objects, even if they belong to the same class, are allowed to have different values for their instance variables. From outside the object, instance variables cannot be altered or even observed (i.e., ruby's instance variables are never public) except by whatever methods are explicitly provided by the programmer. As with globals, instance variables have the nil value until they are initialized.
Now look here:-
class Person
#a = 1
def self.hello()
puts(#a)
end
end
Person.hello # => 1
Person.instance_variables # => [:#a]
Person.new.instance_variables # => []
So in this example #a is the instance variable of the object Person,not the instances of Person.Very good tips is here - Class Level Instance Variables.
class A
def set(v)
##v = v
end
def put
puts ##v
end
end
class B < A
end
class C < A
end
B.new.set 'b'
B.new.put # => b
C.new.set 'c'
C.new.put # => c
B.new.put # => c
Why? And how should I write this to have 'b' in last B.new.put?
Here is a nice article on the subject - Class and Instance Variables In Ruby.
Basically, what you can do is:
class A
class << self
attr_accessor :class_var
end
def set_class_var(value)
self.class.class_var = value
end
def get_class_var
self.class.class_var
end
end
class B < A; end
A.class_var = 'a'
B.class_var = 'b'
puts A.class_var # => a
puts B.class_var # => b
A.new.set_class_var 'aa'
B.new.set_class_var 'bb'
puts A.new.get_class_var # => aa
puts B.new.get_class_var # => bb
To understand it you should think about A as an instance of Class class (and that's how it is in Ruby). But every object in Ruby has its own singleton class that stores object-specific stuff like methods defined on object itself:
a = A.new
def a.foo
puts 'foo'
end
In that case foo is method defined only for a object and not for every instance of A class. And another way to define method in object's singleton class is like that:
class << a # open a's singleton class
def bar # define method that will be available only on 'a' object
puts 'bar'
end
end
In the first code snippet we use that approach to define class_var attribute accessor in the context of singleton class of our A class (it's a bit tricky, so you need to think about it). As the result class itself has class_var variable as well as its descendant class B. The difference is that every one of them has its own class_var variable that do not interfere.
Another option is to pull out class_inheritable_accessor code from Rails and include its behavior in your classes. See here for a good discussion and the guts of the code.
Perhaps you don't really want a class variable, though.
Assigning a value to a class variable (an ## variable) sets it for EVERY instance of the class. It even "sets" it for instances that "aren't created yet." So, consider this...
B.new.set 'b' # OK, that set ##v for that particular instance of B
B.new.put # Hey, you just created *another* new instance of B!
How can ##v have a value in that one? The second object's value of ##v would be unset, except for the fact that ##v is a class variable, so it has the same value for every instance of the class.
I have something like the following
class A
def initialize
#var = 0
end
def dosomething
#var+=1
end
end
class B < A
def initialize
super
end
def func
puts #var
end
end
The problem is when I call
a = A.new
a.dosomething
b = B.new
the value which #var returns is 0 how would I change my code so it would return the "new" value of var (1)?
Quick answer, for if you actually understand Classes, Inheritance and Objects : replace #var (an instance variable, and therefore different in a and b) with ##var (a class variable, and therefore the same in all instances of class A).
Otherwise, your question indicates you have a fundamental misunderstanding of what's going on with classes, objects and inheritance.
Your code does the following:
Defines a class, called A. This is essentially a blueprint from which you can create objects.
Declares that when an object of type A is created, that object should be given it's own private copy of an attribute, called var, which is set to 0.
Declares that objects of type A can be asked to dosomething, which increases the value of that object's var by 1.
Defines a class called B, which is a special case of an A
Therefore, in your second snippet, you create an object a, which is an A. It has its own attribute called var, which is set to 0 and then incremented. You then create b, which is a B (and is therefore also an A). b has its own attribute called var, separate from a's var, which is set to 0.
Variables like #var are called instance variables because they are unique to every instance of a class. You've created two separate instances, a and b, and they have their own instance variables.
You can use class variables if you want to see them in your subclass:
class A
##var = 0
def dosomething
##var += 1
end
end
class B < A
def func
puts ##var
end
end
You can also use class-level accessor methods:
class A
class << self
attr_accessor :var
def dosomething(n)
self.var = n
end
end
end
class B < A
def func
puts A.var
end
end
irb(main):031:0> A.dosomething(5)
=> 5
irb(main):032:0> b = B.new
=> #<B:0x2b349d>
irb(main):033:0> b.func
5
Note that inheritance is not needed for this to work.
You could use class variables:
class A
##var = 0
def dosomething
##var += 1
end
end
class B < A
def func
puts ##var
end
end
a = A.new
a.dosomething
a.dosomething
b = B.new
b.func # => 2
#var is an instance variable, each instance of this class has its own value of this variable. So it is correct that b does return 0, since it is a different instance compared to a.
To update the value in b use:
b = B.new
b.dosomething
Here is more information on instance variables.
Or if you want a class variable (ie a variable which is the same in all instances of that class), use ##var. Then your given example works.
Here is some more information on class variables.
Which solution you need, depends on your application.
You need to use class variables, not instance variables. Try something like this:
class A
##var = 0
def dosomething
##var+=1
end
end
class B < A
def func
puts ##var
end
end
how does this work?
in irb:
>> class A
>> b = [1, 2,3]
>> end
=> [1, 2, 3]
Is b an instance variable? class variable? how would I access b from
outside the class? Is it used for meta-programming?
Is b an instance variable? class variable?
No, it's a local variable inside the class ... end scope.
how would I access b from outside the class?
You wouldn't. It goes out of scope (and is thus inaccessible) once it reaches the end.
Is it used for meta-programming?
It can be. Example:
class A
b = [1,2,3]
b.each do |i|
define_method("foo#{i}") do end
end
end
I've now defined the methods foo1, foo2 and foo3.
Of course this wouldn't behave any differently if I didn't create the variable b and just did [1,2,3].each directly. So creating a local variable by itself does nothing, it allows you to write cleaner code (the same as using local variables in a method).
b is a simple block variable, you cannot access it from outside of the block.
You can use class like this :
class Building
##count=0 #This is a class variable
MIN_HEIGHT=50 #This is a constant
attr_accessor :color, :size #grant access to instance variables
def initialize options
#color=options[:color] ##color is an instance variable
#size=options[:size] ##size too
##count=##count+1
end
def self.build options #This is a class method
# Adding a new building
building=Building.new options
end
end
#[...]
Building.build({:color=>'red', :size=>135})
blue_building=Building.new({:color=>'blue', :size=>55})
puts blue_building.color # How to use an instance variable
# => 'blue'
puts "You own #{Building.count.to_s} buildings !" # How to use a class variable
# => 'You own 2 buildings !'
puts Building::MIN_HEIGHT # How to use a constant
# => 50