0
['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

  • Note: Ruby is a case-sensitive language and capital letters have specific meaning in terms of syntax. Variables and method names should be lower-case letters. Capitals indicate constants of the form  `ClassName` or `CONSTANT_NAME`. – tadman May 14 '21 at 00:00
  • @SenaYevenyo : You referenced the instance variable `@board`, but did not define it anywhere. If you want it to be an instance variable, you could set it to some value inside `initialize`, or add `attr_accessor :board` to your class. If you want to use the class variable `@board`, you must make `getBoard` a class method, i.e. `def self.getBoard`. – user1934428 May 14 '21 at 07:54

2 Answers2

1

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
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • 1
    Thanks for the info but not really answering what I was interested in. Apparently I assumed that Ruby instance variable declaration/instantiation was similar to Java. In this case '@@' referred to 'static' and a single '@' referred to instance variables. Apparently that was not the case as explained in the comments in the answer below and hence has been accepted. – Sena Yevenyo May 14 '21 at 18:07
  • I'm not sure what you are talking about. Variables that start with `@` are indeed instance variables. They are just like instance fields in Java, except for the fact that they are always private. I am not sure what you mean by 'static' variables. Ruby doesn't have static variables. Also, I am not sure what you mean by "instance variable declaration/instantiation". There are no variable declarations in Ruby. Also, note that the accepted answer is wrong: it claims that `@board` is a class variable, but it is not. It is an instance variable. Class variables start with `@@`. – Jörg W Mittag May 14 '21 at 18:11
  • In Java and C++ you can initialize member variables with defaults but it's done at what in Ruby is at the class level. In Ruby that's the wrong context. Conflicting declaration styles here for anyone that's familiar with the other kind. – tadman May 14 '21 at 18:45
  • @JörgWMittag A similar question with its explanation: https://stackoverflow.com/questions/15773552/ruby-class-instance-variable-vs-class-variable I believe my question can be deleted or left over for reference – Sena Yevenyo May 21 '21 at 02:40
0

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.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • 2
    Note for OP's code: `@board` defined inside class is an instance variable of `class GameRunner`, where `GameRunner` is an instance of `class Class`. That's another different concept. – iBug May 14 '21 at 00:29
  • @iBug It can get real confusing in a hurry to go down that rabbit hole, so left it with a very superficial explanation. – tadman May 14 '21 at 00:34
  • Sure, but don't class level properties begin with '@@' instead of '@'? When I do replace '@board' with '@@board' in the script in the original question, there are no errors. I am trying to define an instance variable which I would like to make it private using the private keyword. – Sena Yevenyo May 14 '21 at 00:40
  • Those are different in scope as the class *and* the instances can see those, and every instance uses the same `@@board`. They're also a lot more tricky to use effectively as they get shared far, far more broadly. Do you need an instance of that board per "runner", or do all runners share exactly the same board? Normally that's not what you want. – tadman May 14 '21 at 01:02