6

I have been taught to declare my instance variables with def initialize. I have been under the impression that I could declare instance variables only within my initialize methods.

Nevertheless, I declared an instance variable @foo outside my initialize method, and made it work as I intended:

class FooBar
    def initialize(bar)
        @bar = bar
    end

    def foo_as_instance_var
        @foo = @bar.split(' ')
        @foo
    end
end

x = "something wicked this way comes"
y = FooBar.new(x)
puts y.foo_as_instance_var

Why am I able to declare an instance variable outside of initialize method? Since I can declare instance variables in any method, is there a best practices rule I should follow, regarding where to declare instance variables (i.e., declare them within initialize) or does it not matter?

sawa
  • 165,429
  • 45
  • 277
  • 381
foo
  • 195
  • 1
  • 8
  • 3
    Doesn't matter. Do what makes sense. You were under the wrong impression if you think it should only happen in initialize. Read [this](https://stackoverflow.com/q/4370960/3784008) for more info on use of instance variables outside initialize. – anothermh Jan 15 '19 at 04:25
  • @anothermh I was. And the question was definitely lingering at the back of my head... – foo Jan 15 '19 at 04:27
  • To be able to access across instance methods, that is the whole point of instance variables. – sawa Jan 15 '19 at 05:05
  • @anothermh lol, I literally forgot or missed `attr_accessor`. Thanks! – ray Jan 15 '19 at 05:42
  • It's a nice convention, but just that. You can actually declare instance variables from anywhere using `instance_variable_set` (but I wouldn't advise doing that unless you must for metaprogramming) – max pleaner Jan 15 '19 at 06:36
  • @maxpleaner for _dynamic_ programming actually, which is not mandatory _meta_ :) – Aleksei Matiushkin Jan 15 '19 at 07:06
  • [Mandatory reading](https://ruby-doc.org/core-2.6/Object.html#method-i-instance_variable_set) for anyone considering using `instance_variable_set`. – anothermh Jan 15 '19 at 07:17
  • 1
    If you were only allowed to assign to instance variables inside `initialize`, then you could never, ever change them, except by calling `initialize` again. Who told you that? Also, `initialize` is just a method like any other method, so if you weren't allowed to assign to instance variables in methods, then you couldn't assign them in `initialize` either. The fact that you can assign them in `initialize` already proves that you can assign them in methods, because `initialize` is a method. This entire statement makes no sense. – Jörg W Mittag Jan 16 '19 at 18:12

3 Answers3

9

I have been taught to declare my instance variables with def initialize

Since initialize is the first instance method call in an object's life cycle, you typically declare your instance variables right there in order to ensure properly initialized variables. It's also the first place I'd expect instance variables to be defined when reading code.

I have been under the impression that I could declare instance variables only within my initialize methods.

There's no such restriction. You can declare instance variable anywhere within your instance.

A common use is memoization:

class FooBar
  def foo
    @foo ||= expensive_operation
  end
end

On the first call, this would evaluate expensive_operation and assign the result to @foo. On subsequent calls, @foo is returned.

Another popular example is Rails which uses instance variables to pass data from the controller to its view:

class FooController < ApplicationController
  def index
    @foos = Foo.all
  end
end

is there a best practices rule I should follow, regarding where to declare instance variables

It depends on their purpose (see above examples). As a general rule, declare them in a way that avoids undefined variables (nil errors) and structure your code so it is easy to read / follow.

Stefan
  • 109,145
  • 14
  • 143
  • 218
5

Just to add to Stefan's excellent answer

I have been taught to declare my instance variables with def initialize

A common mistake that ruby newbies make is something like this:

class Person
  @name = "John"

  def introduce
    puts "Hi, my name is #{@name}"
  end
end

And then they wonder why their names are not printed. To make this work, one can set the variable @name in the initializer, just as the instruction says.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
3

Lets start with the biggest misnomer - in Ruby there is no separate step of declaring variables - Variables are declared as you set them.

What the difference? Look at Java for example:

public class Bicycle {

    private int cadence;
    private int gear;
    private int speed;

    public Bicycle(int startCadence, int startSpeed, int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
    }
}

We have to declare all the instance variables before we set them in the initializer (Bicycle). The same code in Ruby reads:

class Bicycle
  def initialize(cadence, speed, gear)
    @cadence = cadence
    @speed = speed
    @gear = gear
  end
end

There is no declaration - only assignment. Ruby will even let you access instance variables which have not been set without error.

irb(main):003:0> @not_set
=> nil

You can't do that (generally) in languages where variables must be defined*.

I have been taught to declare my instance variables with def initialize. I have been under the impression that I could declare instance variables only within my initialize methods.

Nonsense. You can assign instance variables anywhere. Its commonly done in everything from setters and mutators (methods that alter an object) to factory methods (class methods that return an instance) or anywhere that you are altering the state of an object.

class Book 

  def initialize(title, author)
    @title = title
    self.author = author # calls the setter.
  end

  # A factory method
  def create_from_csv(filename)
    # ...
  end

  # A very contrived setter
  def author=(author)
    @author = "#{author.forename.upcase}. #{author.surname}"
  end

  # a mutator
  def out_of_print!
    @out_of_print = true
    @last_printed = Date.today
  end
end

However the initialize method is where you should handle initializing your objects (duuh) and is thus the obvious place to set initial values.

max
  • 96,212
  • 14
  • 104
  • 165
  • * well unless you are talking about Javascript which is a very special snowflake. – max Jan 15 '19 at 16:29
  • 1
    _"access instance variables which have not been set without error"_ – there will be a warning, though (if enabled). – Stefan Jan 15 '19 at 18:13