3
class Artist
 @@song_count = []
 attr_accessor :name, :songs

 def initialize(name)
  @name = name
  @songs = []
 end

 def add_song(song)
  @songs << song
 end

 def print_songs
  songs.each {|song| puts song.name}
 end
end

So in this example, it uses all two types, @songs and songs.

I'm having a hard time understanding why these are used, instead of using @songs for everything.

And then in this example,

def add_song(song)
 self.songs << song
 song.artist = self
 @@song_count +=1
end

Why is self.songs used instead of @songs?

Ok, so I forgot to say one more thing. In the first code snippet above,for method print_songs, why am I able to use songs.each instead of @songs.each? I was expected it to generate an error undefined songs.

LeongZeno
  • 63
  • 5

2 Answers2

2

Why is self.songs used instead of @songs

Using the method is more flexible. You're abstracting yourself from knowing how exactly it gets/stores data. The less you rely on implementation details, the easier it will be for you to change code later.

One small example, consider this implementation of songs

def songs
  @songs ||= []
  @songs
end

@songs may or may not have been assigned value prior to invocation of this method. But it doesn't care. It makes sure that @songs does have a sane default value. The concept is called "lazy initialization" and it's very tedious and error-prone to do if you use instance variables directly.

So, when in doubt, always use methods.

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

Difference between foo and @foo

Instance variables

Instance variables are defined within instance methods, and their names begin with @. Their value is only accessible within the specific object on which it was set. In other words, when we modify the value of an instance variable, the change only applies to that particular instance. Unlike local variables which are only available within the method where they were defined, instance variables are accessible by all methods within the object (instance methods of the class). Instance variables are the most commonly used type of variable in Ruby classes.

class Car
  attr_reader :color

  def set_color(color_receiverd_as_argument)
    @color = color_receiverd_as_argument
  end
end

car1 = Car.new
car1.color     # Output: => nil
car1.set_color "black"
car1.color     # Output: => "black"

car2 = Car.new
car2.set_color "silver"
car2.color    # Output: => "silver"

In the example above, notice that:

  • Trying to access an instance variable before it's initialized will not raise an exception. Its default value is nil.
  • Changing the value of the color variable in one instance of the Car class does not affect the value of the same variable in the other instances.

Local variables

A local variable within a class is like any other local variable in Ruby. It is only accessible within the exact scope on which it's created. If defined within a method, it is only available inside that method.

class Car
  def initialize
    wheels = 4
  end

  def print_wheels
    print wheels
  end
end

c = Car.new
c.print_wheels        # Output: NameError: undefined local variable or method `wheels'…


The self keyword

The self keyword is always available, and it points to the current object. In Ruby, all method calls consist of a message sent to a receiver. In other words, all methods are invoked on an object. The object on which the method is called is the receiver, and the method is the message. If we call "foo".upcase, the "foo" object is the receiver and upcase is the message. If we don't specify an object (a receiver) when calling a method, it is implicitly called on the self object.

Self keyword at class level

When used within a class but outside any instance methods, self refers to the class itself.

class Foo
  @@self_at_class_level = self

  def initialize
    puts "self at class level is #{@@self_at_class_level}"
  end
end

f = Foo.new     # Output: self at class level is Foo

Self keyword at instance methods

When inside an instance method, the self keyword refers to that specific instance. In other words, it refers to the object where it was called.

class Meditation
  def initialize
    puts "self within an instance method is #{self}"
  end
end

zazen = Meditation.new     # Output: self within an instance method is #<Meditation:0x00000000ab2b38>

Notice that #<Meditation:0x00000000ab2b38> is a string representation of the zazen object, which is an instance of the Meditation class.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367
BrunoF
  • 3,239
  • 26
  • 39