0

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.

3
  • The first number is for a given instance of TestClass; the second is a class instance variable. They can coexist, as they are as different as @night and @day. Commented Aug 20, 2014 at 22:46
  • @CarySwoveland: I understand that. My question is why "extend ClassMethods" doesn't initialize class instance variable. And how can I initialize both instance variable and class instance variable without having duplicate code? Commented Aug 21, 2014 at 9:54
  • xx, I hadn't forgotten. It just took me awhile to find time to provide an answer. Let me know if you have any questions. Commented Aug 25, 2014 at 4:30

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, I was on the right track, except I tried calling initialize without using send. Whoever downvoted - can you explain?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.