0

I'm brand-new to Ruby coming from a Java background. I don't understand the relationship between instance methods and variables yet, apparently. I just had some code like this:

class Problem022
  FILENAME = "resources/p022_names.txt"
  @name_array = [] # <--- class line 3

  def do_problem
    read_input()
    sort_input()

    # do more stuff
  end

  def read_input
    # <--- read_input line 2

    File.open(FILENAME, "r") do |file|
      file.each_line do |line|
        scanner = StringScanner.new(line)

        while scanner.exist?(/,/) do
          name = scanner.scan_until(/,/)

          @name_array.push(name) # <--- marked line
        end

    # more logic
  end

  def sort_input()
    @name_array.sort!
  end

  # other methods
end

puts problem = Problem022.new.do_problem()

I confirmed this worked correctly up until "marked line" (see comment above), but on that line, I got the error

in `block (2 levels) in read_input': undefined method `push' for nil:NilClass (NoMethodError)

I was able to solve it by (kinda blindly) adding the line

@name_array = []

at "read_input line 2", but I don't understand why this worked. Why doesn't the method recognize the declaration of instance variable @name_array in the class body? It seems to be able to use the constant FILENAME just fine, and that's defined in basically the same place. The method and the variable are both instance, not class, so I wouldn't expect any problems there. I just realized that the program still runs fine even if the declaration on "class line 3" is commented out, which only confuses me more.

I've read multiple definitions of scope in Ruby but none of them cleared this up for me. What am I misunderstanding?

SOLO
  • 868
  • 9
  • 19
  • Yes, it's been asked countless number of times. Lemme find you a good duplicate. – Sergio Tulentsev Mar 17 '18 at 15:45
  • I figured, but my search skills aren't up to it. I've seen a few posts and they haven't helped me. – SOLO Mar 17 '18 at 15:46
  • Here, this one should be close enough: https://stackoverflow.com/questions/25025235/ruby-class-instance-variables-vs-instance-variables – Sergio Tulentsev Mar 17 '18 at 15:47
  • Take a look. If good, I'll close the question as duplicate – Sergio Tulentsev Mar 17 '18 at 15:47
  • Looks like a similar topic, but I don't see how it answers my question about why the initial declaration wasn't recognized at all...? – SOLO Mar 17 '18 at 15:52
  • Because that variable belongs to another object, the class itself. An object totally separate from class instances. You must have not scrolled down to [Jorg's answer](https://stackoverflow.com/a/25025442/125816), excellent as always. – Sergio Tulentsev Mar 17 '18 at 15:56
  • I'm trying to tell you that I don't understand those answers. I _do_ see why the other asker's code works the way it does, but not how the whole thing translates to my situation. It doesn't help that the other answers don't even agree with each other; one says "There is no such thing as a class instance variable" while another defines the term "class instance variable" (part of that other asker's question was about terminology). – SOLO Mar 17 '18 at 16:00
  • Alright, lemme make an illustration – Sergio Tulentsev Mar 17 '18 at 16:02
  • This has been more helpful (but still leaves a lot unexplained): https://stackoverflow.com/questions/43650407/where-to-initialize-and-declare-instance-variables-in-ruby – SOLO Mar 17 '18 at 16:05
  • Two more posts for the linked sidebar list: https://stackoverflow.com/questions/42761827/ruby-scope-of-instance-variable-values-within-a-class and https://stackoverflow.com/questions/9311347/using-instance-variables-in-class-methods-ruby – SOLO Mar 17 '18 at 16:24

1 Answers1

2

Here's some code that should illuminate what's going "wrong".

class Problem
  @name = 'foo' # this belongs to the class, because that's what current `self` is.

  # instance method
  # inside of this method, `self` will be an instance of the class
  def action
    @name
  end

  # class method (or class instance method)
  # inside of this method, `self` will refer to the class
  def self.action
    @name
  end
end

p1 = Problem.new # an instance of the class
p2 = Problem # the class itself

p1.action # => nil, you never initialize @name in the context of class instance
p2.action # => "foo"

Key points:

  • classes are objects and can have instance variables too. These variables are not in any way related to those of instances of the class.
  • concept of "current self" is one of ruby's fundamentals (related to "implicit receiver method calls"). You won't get very far without understanding it.
Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
  • IIRC, the concept of "current self" is quite well explained in the book called "metaprogramming ruby". But it may be a bit too advanced for the moment. – Sergio Tulentsev Mar 17 '18 at 16:11
  • It's been some years since I read beginner-level ruby books, maybe they explain it too. (on a second thought, they _must_. ) – Sergio Tulentsev Mar 17 '18 at 16:12
  • Okay, your first in-code comment helps a lot. I now gather that even though @-vars are called "instance variables", they're not something where each instance of the class gets its own copy; instead, they're variables of the class, and the class itself is an instance of a superclass. Kind of similar (but probably not directly analogous, if I look deep enough) to Java's `static` vars? Regardless, I guess I know I need to read about Ruby's `self` and class structure next. – SOLO Mar 17 '18 at 16:22
  • @SOLO: one minor correction, though. "instead, they're variables of the class" - nope, they're variables of an object. Class is an object. So are instances of that class. Different objects, different independent sets of variables. – Sergio Tulentsev Mar 17 '18 at 16:25
  • I can say the words "classes are objects in Ruby" but I haven't fully wrapped my head around the concept yet. Beyond the scope of this question though. – SOLO Mar 17 '18 at 16:33
  • @SOLO: no worries, it'll click one day :) – Sergio Tulentsev Mar 17 '18 at 16:35