Domani Tomlindo
Domani Tomlindo

Reputation: 249

Ruby - How to access instance variables from classes with "self" methods?

Sorry that I have no clue how to title this, I'm having a hard time looking this up because I don't know how to say this. Anyway...

Let's say I have a class that looks like this for example:

class Run
    def self.starting
        print "starting..."
    end
    def self.finished
        print "Finished!"
    end
end

All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting. Now let's say that I wanted to add some instance variables...

class Run
    attr_accessor :starting, :finished
    def self.starting
        print "starting..."
        @starting = true
        @finished = false
    end
    def self.finished
        print "finished!"
        @starting = false
        @finished = true
    end
end

What if I wanted to access those instance variables from outside the class? I know that something like print "#{Run.finished}" or print "#{Run.starting}" won't do anything. Can I do that without run = Run.new? Or should I just remove self and then use run = Run.new? (Sorry if this question is a mess.)

Upvotes: 2

Views: 6002

Answers (3)

Jörg W Mittag
Jörg W Mittag

Reputation: 369594

You can't access instance variables from outside the instance. That is the whole point of instance variables.

The only thing you can access from outside the instance are (public) methods.

However, you can create a public method that returns the instance variable. Such a method is called an attribute reader in Ruby, other languages may call it a getter. In Ruby, an attribute reader is typically named the same as the instance variable, but in your case that is not possible since there are already methods with the names starting and finished. Therefore, we have to find some other names for the attribute readers:

class Run
  def self.starting?
    @starting
  end

  def self.finished?
    @finished
  end
end

Since this is a common operation, there are helper methods which generate those methods for you, for example Module#attr_reader. However, they also assume that the name of the attribute reader method is the same as the name of the instance variable, so if you were to use this helper method, it would overwrite the methods you have already written!

class << Run
  attr_reader :starting, :finished
end

When you do this, you will get warnings (you always have warning turned on when developing, do you?) telling you that you have overwritten your existing methods:

run.rb:19: warning: method redefined; discarding old starting
run.rb:2: warning: previous definition of starting was here
run.rb:19: warning: method redefined; discarding old finished
run.rb:5: warning: previous definition of finished was here

Upvotes: 1

max
max

Reputation: 102248

In Ruby instance variables are just lexical variables scoped to an instance of a class. Since they are scoped to the instance they always act like a private variable.

If you want to provide access to an instance variable from the outside you create setter and getter methods. Thats what attr_accessor does.

class Person
  attr_accessor :name

  def initialize(name:)
    @name = name
  end

  def hello
    "Hello my name is #{@name}"
  end
end

john = Person.new(name: 'John')
john.name = "John Smith"
puts john.hello # "Hello my name is John Smith"
puts john.name # "John Smith"

Methods defined with def self.foo are class methods which are also referred to as singleton methods. You can't access variables belonging to an instance from inside a class method since the recipient when calling the method is the class itself and not an instance of the class.

Ruby also has class variables which are shared by a class and its subclasses:

class Person
  @@count = 0

  def initialize
    self.class.count += 1
  end

  def self.count
    @@count
  end
  def self.count=(value)
    @@count = value
  end
end

class Student < Person
end

Person.new
Student.new
puts Person.count # 2 - wtf!

And class instance variables that are not shared with subclasses:

class Person
  @count = 0 # sets an instance variable in the eigenclass

  def initialize
    self.class.count += 1
  end

  def self.count
    @count
  end
  def self.count=(value)
    @count = value
  end
end

class Student < Person
  @count = 0 # sets its own class instance variable
end

Person.new
Student.new
puts Person.count # 1

Class variables are not used as often and usually hold references to things like database connections or configuration which is shared by all instances of a class.

Upvotes: 1

Greg
Greg

Reputation: 6649

All of the methods in Run have self before them, meaning that I don't have to do run = Run.new and I can just do Run.starting

There's much more to it than this. In your case you're calling class methods. If you did runner = Runner.new - then you'd be calling instance methods (those are defined without self.

In general, if you need "the thing" to hold some kind of state (like @running = true) then you'd rather want to instantiate an object, and call those methods.

Now, @whatever are instance variables, and you don't have the access to them in class methods.

class Run
   attr_reader :running
   def start
      @running = true
   end

   def stop
      @running = false
   end
end

runner = Run.new
runner.running # nil
runner.start
runner.running # true
runner.stop 
runner.running # false

I'd recommend you doing some tutorial or basic level book on rails programming, find a chapter about objects and classes. Do some exercises.

Upvotes: 3

Related Questions