Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing class instance variables in Ruby

I am working on a small rails app and have a problem with ruby's OOP model. I have the following simplified class structure.

class Foo
  protected
    @bar = []
    def self.add_bar(val)
      @bar += val
    end
    def self.get_bar
      @bar
    end
end

class Baz < Foo
  add_bar ["a", "b", "c"]
end

My problem is now, that when I call add_bar in the class definition of Baz, @bar is apparently not initialized and I get an error that the + Operator is not available for nil. Calling add_bar on Foo directly does not yield this problem. Why is that and how can I initialize @bar correctly?

To make clear what I want, I will point out the behavior I would expect from these classes.

Foo.add_bar ["a", "b"]
Baz.add_bar ["1", "2"]
Foo.get_bar # => ["a", "b"]
Baz.get_bar # => ["a", "b", "1", "2"]

How could I achieve this?

like image 788
HalloDu Avatar asked Feb 04 '11 17:02

HalloDu


People also ask

How do you initialize an instance variable in Ruby?

You can use the method Object#instance_variables to list all instance variables of an object. You normally “declare” and initialize all the instance variables in the initialize method.

Can instance variables be initialized?

Instance variables can be initialized in constructors, where error handling or other logic can be used. To provide the same capability for class variables, the Java programming language includes static initialization blocks.


3 Answers

Short answer: instance variables don't get inherited by subclasses

Longer answer: the problem is that you wrote @bar = [] in the body of the class (outside any method). When you set an instance variable, it is stored on whatever is currently self. When you're in a class body, self is the class object Foo. So, in your example, @foo gets defined on the class object Foo.

Later, when you try to look up an instance variable, Ruby looks in whatever is currently self. When you call add_bar from Baz, self is Baz. Also self is STILL Baz in the body of add_bar (even though that method is in Foo). So, Ruby looks for @bar in Baz and can't find it (because you defined it in Foo).

Here's an example that might make this clearer

class Foo
  @bar = "I'm defined on the class object Foo. self is #{self}"

 def self.get_bar
    puts "In the class method. self is #{self}"    
    @bar
  end

  def get_bar
    puts "In the instance method. self is #{self} (can't see @bar!)"
    @bar
  end
end

>> Foo.get_bar
In the class method. self is Foo
=> "I'm defined on the class object Foo. self is Foo"

>> Foo.new.get_bar
In the instance method. self is #<Foo:0x1056eaea0> (can't see @bar!)
=> nil

This is admittedly a bit confusing, and a common stumbling point for people new to Ruby, so don't feel bad. This concept finally clicked for me when I read the 'Metaprogramming' chapter in Programming Ruby (aka "The Pickaxe").

How I'd solve your problem: Look at Rails' class_attribute method. It allows for the sort of thing you're trying to do (defining an attribute on a parent class that can get inherited (and overidden) in its subclasses).

like image 54
r00k Avatar answered Sep 28 '22 09:09

r00k


Well, since @bar is defined as class instance variable then it's limited to the class Foo. Check this:

class Foo
    @bar = []
end

class Baz < Foo
end

Foo.instance_variables #=> [:@bar]
Baz.instance_variables #=> []

Anyway, for this simple example you can do this:

class Foo
  protected
    def self.add_bar(val)
      @bar ||=[]
      @bar += val
    end
    def self.get_bar
      @bar
    end
end

class Baz < Foo
  add_bar ["a", "b", "c"]
end

Read more about this here.

like image 41
khelll Avatar answered Sep 28 '22 10:09

khelll


I do it like so:

class Base

    class << self

        attr_accessor :some_var


        def set_some_var(value)
            self.some_var = value
        end

    end

end


class SubClass1 < Base
  set_some_var :foo
end

class SubClass2 < Base
  set_some_var :bar
end

Then it should do what you want.

[8] pry(main)> puts SubClass1.some_var
foo
[9] pry(main)> puts SubClass2.some_var
bar

Note that the set_some_var method is optional, you can do SubClass1.some_var = ... if you prefer.

If you want some default value, add something like that under class << self

def some_var
    @some_var || 'default value'
end
like image 39
Nicolas Goy Avatar answered Sep 28 '22 09:09

Nicolas Goy