Disappearing instance var

时间:2015-09-01 21:15:22

标签: ruby

Here is my code:

class Klass
  ["thing", nil].each do |i|
    instance_variable_set("@#{i}reqs", {})
  end

  def initialize(var)
    @reqs[var] = self
  end
end

Klass.new("hello")

Which gives me the error:

in initialize': undefined method[]=' for nil:NilClass (NoMethodError)

I shouldn't be getting this error because the loop at the top should have initialized @reqs in it's second iteration. What is going on?

3 个答案:

答案 0 :(得分:7)

Instance variables belong to particular instances. That's why they are called instance variables.

In line 3, you set the instance variable called @reqs of the object Klass. In line 6, you access the instance variable called @reqs of an instance of the class Klass. Those are two completely different, distinct objects each with its own set of instance variables. Heck, those two objects don't even have the same class! (Klass's class is Class, whereas Klass.new's class is Klass.)

In line 6, @reqs is uninitialized, and uninitialized instance variables evaluate to nil.

There are many different ways to fix this, depending on your exact circumstances and requirements, the easiest way would be to initialize the instance variables in the initialize method, after all, that's what that method is there for:

class Klass
  def initialize(var)
    ['thing', nil].each do |i|
      instance_variable_set(:"@#{i}reqs", {})
    end

    @reqs[var] = self
  end
end

Klass.new('hello')

Remember, the problem was that the instance variables were initialized in one object, and accessed in another. This solution moves the initialization to the same object that was doing the reading.

However, the dual is also possible: move the reading to where the initialized variables are:

class Klass
  ['thing', nil].each do |i|
    instance_variable_set(:"@#{i}reqs", {})
  end

  def initialize(var)
    self.class.instance_variable_get(:@reqs)[var] = self
  end
end

Klass.new('hello')

This is kind of ugly, so let's add an attr_reader:

class Klass
  ['thing', nil].each do |i|
    instance_variable_set(:"@#{i}reqs", {})
  end

  class << self; attr_reader :reqs end

  def initialize(var)
    self.class.reqs[var] = self
  end
end

Klass.new('hello')

Obviously, these two do very different things. It is unclear from your question which of the two you actually want.

A third possibility would be using class variables:

class Klass
  ['thing', nil].each do |i|
    class_variable_set(:"@@#{i}reqs", {})
  end

  def initialize(var)
    @@reqs[var] = self
  end
end

Klass.new('hello')

Note that this does yet another different thing. Again, whether you want that or not is not clear from your question.

答案 1 :(得分:1)

The loop at the top is defining an instance variable for the Class, not for any object of the class.

So for the object, it doesn't exist.

From the looks of it, you want a hash common to the whole class where you store each created object in the hash. Assuming you don't have issues with class inheritance, you'd be better of with a class variable.

so...

class Klass
    ["thing",nil].each do |i|
        class_variable_set("@@#{i}reqs", {})
    end
    def initialize(var)
        @@reqs[var] = self
    end
end
Klass.new("hello")

答案 2 :(得分:0)

If you define class like this:

class Klass
  instance_variable_set(:@name, 'dog')

  def self.name
    @name
  end

  def name
    @name
  end
end

then

Klass.name # => 'dog'

instance = Klass.new
instance.name # => nil

Now you can see the difference. Your variable is defined on class level, not instance level.