Sena Yevenyo
Sena Yevenyo

Reputation: 49

Why am I getting NoMethodError undefined method for nil::NilClass

['board'].each{|script| require_relative script}

class GameRunner

    @board = Board.new

    def initialize
    end

    def getBoard
        @board
    end

end

This piece of code generates an error when getBoard is called. But when I move the instantiation of @board to the initialize block there is no errors. Why?

Edit: An answer with a more clear explanation on what is going on: Ruby class instance variable vs. class variable

Upvotes: 0

Views: 991

Answers (2)

Jörg W Mittag
Jörg W Mittag

Reputation: 369594

Here @board is an instance variable. Instance variables belong to an object (instance), hence why they are called instance variables.

You have two references to an instance variable named @board in your code. Now, ask yourself: which object do they belong to? In other words: what is self at the point where you reference the instance variable?

class GameRunner
  # Here, `self` is `GameRunner`
  @board = Board.new

  def getBoard
    # Here, `self` is an *instance* of `GameRunner`
    @board
  end
end

At the first reference to @board, self is the GameRunner class itself. Remember, class are objects just like any other object; they are instances of the Class class just like strings are instances of the String class, integers are instances of the Integer class, and game runners are instances of the GameRunner class.

You can easily see that the instance variable has been defined and initialized:

GameRunner.instance_variables
#=> [:@board]

GameRunner.instance_variable_get(:@board)
#=> #<Board:0x0000deadbeef1230>

At your second mention, however, self is an instance of GameRunner, and not the GameRunner class itself.

Or, to put it differently: you have two completely independent instance variables of two completely independent objects. The instance variables just happen to have the same name.

It's exactly the same as if you did:

game_runner1 = GameRunner.new
game_runner2 = GameRunner.new

The instance variables of game_runner1 and game_runner2 are private to each of those two objects. game_runner1 does not know anything about the instance variables of game_runner2 and vice versa. The same thing is true about game_runner1 and GameRunner.

Again, it is important to remember that classes are just objects like any other object.

It looks like what you actually want is to have both references refer to the same instance variable, namely an instance variable of an instance of GameRunner. You can achieve that by moving the assignment into an instance method, something like this:

class GameRunner
  def initializeBoard
    # Here, `self` is an *instance* of `GameRunner`
    @board = Board.new
  end

  def getBoard
    # Here, `self` is an *instance* of `GameRunner`
    @board
  end
end

However, this is somewhat annoying because you always have to remember to call initializeBoard before you can use the object, and you have to make sure that once you have called initializeBoard, you never call it again.

To make initialization tasks like this easier, Ruby has a convention: the default implementation of Class#new will call a method named initialize on the newly allocated object:

class Class
  def new(...)
    obj = allocate
    obj.initialize(...)
    obj
  end
end

[This is not quite accurate because initialize is private by default, so it would be more like obj.__send__(:initialize, ...), but you get the idea.]

So, if we simply rename the initializeBoard method to initialize, that will ensure that our instance variable is always initialized by GameRunner::new:

class GameRunner
  def initialize
    # Here, `self` is an *instance* of `GameRunner`
    @board = Board.new
  end

  def getBoard
    # Here, `self` is an *instance* of `GameRunner`
    @board
  end
end

Note that your code violates multiple Ruby community coding standards:

  • Ruby uses 2 spaces for indentation, not 4.
  • There should be no empty line after class or before end
  • Method names use snake_case, not camelCase. IOW, your getter method should be called get_board.
  • … Except it shouldn't, because getters should simply be called noun, not get_noun, i.e. your getter method should be called simply board.
  • Lastly, trivial getters should not be defined by hand, but using the core Module#attr_reader method.

If we combine all of this, your class should look like this:

class GameRunner
  attr_reader :board

  def initialize
    @board = Board.new
  end
end

I, personally, prefer to avoid referring to instance variables directly as much as possible, and only use getters and setters. However, that is not a majority coding style, that is just my personal preference:

class GameRunner
  attr_reader :board

  private

  def initialize
    self.board = Board.new
  end

  attr_writer :board
end

Upvotes: 1

tadman
tadman

Reputation: 211720

Here @board is a class variable, not an instance variable.

What you probably mean is:

class GameRunner
  # Anything declared here is assumed to be class-level

  def initialize
    # Anything inside an instance method is an instance variable
    @board = Board.new
  end

  def getBoard
    @board
  end
end

Since classes are objects, the class can also have its own instance variables. Confusingly they also use the same @ prefix.

It's worth noting that accessors like this are usually declared in Ruby as:

attr_reader :board

Which makes the method for you. The get prefix is almost always omitted because mutator methods (e.g. set) are the same but with the = suffix.

Upvotes: 0

Related Questions