xx77aBs
xx77aBs

Reputation: 4768

How to initialize ruby class variable in module

Let's say I have code like this:

class TestClass
  module ClassMethods
    attr_accessor :number

    def initialize
      @number = 47
    end
  end

  include ClassMethods
  extend ClassMethods
end

TestClass.new.number returns 47, which is expected, but TestClass.number returns nil.

How can I initialize number variable for both TestClass class and TestClass instances? So far, I've done it like this:

class TestClass
  module ClassMethods
    attr_accessor :number

    def initialize
      @number = 47
    end
  end

  include ClassMethods
  extend ClassMethods
  @number = 47
end

I do not like this approach because number is initialized in two places.

Upvotes: 0

Views: 1627

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110755

Suppose you had this:

class TestClass
  module ClassMethods
    attr_accessor :number    
    def initialize
      @number = 47
    end
  end
  include ClassMethods
  extend  ClassMethods
end

Let's get some information about this class:

TestClass.instance_variables #=> []

No surprise there.

tc = TestClass.new      #=> #<TestClass:0x00000101098a10 @number=47>
p tc.instance_variables #=> [:@number]
tc.number               #=> 47
tc.number = 11

All as expected. The instance variable @number was created by initialize and we can inspect and change its value with the accessor.

extend made TestClass#initialize a class method, but it has not been invoked. Let's invoke it to initialize the class instance variable @number:

TestClass.initialize
  #=> NoMethodError: private method `initialize' called for TestClass:Class

Ah, yes, initializeis a private method.

TestClass.methods.include?(:initialize)         #=> false
TestClass.private_methods.include?(:initialize) #=> true

We cannot invoke private class methods in the usual way. send, however, works with private as well as public methods:

TestClass.send :initialize                      #=> 47
TestClass.instance_variables                    #=> [:@number]
TestClass.instance_variable_get(:@number)       #=> 47

So now the class instance variable has been created set equal to 47. Has it changed the value of the instance variable @number?

tc.number                                       #=> 11

It's not changed. Now let's change the value of the class instance variable and then see if the value of instance variable is affected:

TestClass.instance_variable_set(:@number, -5)   #=> -5
tc.number                                       #=> 11

If you'd like to add an accessor for the class instance variable @number, add this line to the class or module:

Module.instance_eval("attr_accessor :number")

(For an explanation, see my answer here.)

Then test:

TestClass.number       #=> -5
TestClass.number = 107
TestClass.number       #=> 107

Upvotes: 1

Related Questions