36

I Just started learning ruby and I don't see the difference between an @instace_variable and an attribute declared using attr_accessor.

What is the difference between the following two classes:

class MyClass  
  @variable1 
end

and

class MyClass
  attr_accessor :variable1
end

I searched lot of tutorials online and everybody uses different notation, Does it have to do anything with the ruby version? I also searched few old threads in StackOverflow

What is attr_accessor in Ruby?
What's the Difference Between These Two Ruby Class Initialization Definitions?

But still I am not able to figure out what is the best way to use.

Community
  • 1
  • 1
Rajesh Pantula
  • 10,061
  • 9
  • 43
  • 52
  • 3
    `attr_accessor` will make an instance variable, _plus_ methods to read and write the instance variable. – Alex Wayne Oct 16 '12 at 22:40
  • 4
    Actually, @AlexWayne, `attr_accessor` just declares the methods. Instance variables don't have to be declared; they spring into existence when you try to access them (and their value is `nil` if that first access is a read instead of a write). – Mark Reed Oct 16 '12 at 22:53
  • Further to @Mark's point, `class A; attr_accessor :a; end; A.new.instance_variables #=> []`. – Cary Swoveland Dec 17 '20 at 05:09

6 Answers6

58

An instance variable is not visible outside the object it is in; but when you create an attr_accessor, it creates an instance variable and also makes it visible (and editable) outside the object.

Example with instance variable (not attr_accessor)

class MyClass
  def initialize
    @greeting = "hello"
  end
end

m = MyClass.new
m.greeting #results in the following error:
  #NoMethodError: undefined method `greeting' for #<MyClass:0x007f9e5109c058 @greeting="hello">

Example using attr_accessor:

class MyClass
  attr_accessor :greeting

  def initialize
    @greeting = "hello"
  end
end

m2 = MyClass.new
m2.greeting = "bonjour" # <-- set the @greeting variable from outside the object
m2.greeting #=> "bonjour"   <-- didn't blow up as attr_accessor makes the variable accessible from outside the object

Hope that makes it clear.

Nat Ritmeyer
  • 5,634
  • 8
  • 45
  • 58
  • 3
    actually I am from java background and in java an instance variable can be made visible to outside using access modifiers(public, protected), thats why I was not able to understand why instance variables in ruby cannot be accessed from outside. Thanks for explaining, cheers!!! – Rajesh Pantula Oct 17 '12 at 10:09
  • 1
    m2.greeting at the end turns into "hello" – dtc Apr 04 '15 at 10:21
  • Nat - @greeting = "hello" should be changed to greeting = "hello" because as you said, attr_accessor already creates the instance variable and accessor functions. There is no need to use the @ symbol. – Tony Jiang Nov 18 '16 at 22:24
  • @RajeshPantula attr_accessor is exactly that access modifier you were looking for. – Magne Nov 16 '17 at 11:49
  • @TonyJiang: but simply doing `greeting = "hello"` won't work (as expected). – Sergio Tulentsev Mar 22 '18 at 09:09
  • You should mention that `attr_accessor` can be used to create private getters and setters (`private; attr_accessor :dog`), a pattern favoured by some (not by me). – Cary Swoveland Jun 04 '18 at 04:18
  • Well, attr_accesor is a method that runs against the class, and defines an instance variable and a getter and setter for the variable. It isn't making it public though as the public modifier in Java does. It is a different language mechanism. – kisai Aug 28 '18 at 03:07
  • See the comments on the question about when instance variables are created. – Cary Swoveland Dec 17 '20 at 05:11
31

Instance variables are not directly visible outside of the class.

class MyClass
  def initialize
    @message = "Hello"
  end
end

msg = MyClass.new
@message
#==> nil   # This @message belongs to the global object, not msg
msg.message
#==> NoMethodError: undefined method `message'
msg.@message
#==> SyntaxError: syntax error, unexpected tIVAR

Now, you can always do this:

msg.instance_eval { @message }

or ask for the variable directly like this:

msg.instance_variable_get :@message

But that's awkward and sort of cheating. Poking around someone else's class may be educational, but your client code shouldn't be required to do it to get reliable results. So if you want clients to be able to see those values, don't make them use the above techniques; instead, define a method to expose the value explicitly:

class MyClass
  def message 
    return @message
  end
end
msg.message
# ==> "Hello"

Because you so often want to do that, Ruby provides a shortcut to make it easier. The code below has exactly the same result as the code above:

class MyClass
  attr_reader :message
end

That's not a new type of variable; it's just a shorthand way to define the method. You can look at msg.methods and see that it now has a message method.

Now, what if you want to allow outsiders to not only see the value of an instance variable, but change it, too? For that, you have to define a different method for assignment, with a = in the name:

class MyClass
  def message=(new_value)
    @message = new_value
  end
end
msg.message = "Good-bye"
msg.message
# ==> "Good-bye"

Note that the assignment operators are semi-magical here; even though there's a space between msg.message and =, Ruby still knows to call the message= method. Combination operators like += and so on will trigger calls to the method as well.

Again, this is a common design, so Ruby provides a shortcut for it, too:

class MyClass
  attr_writer :message
end

Now, if you use attr_writer by itself, you get an attribute that can be modified, but not seen. There are some odd use cases where that's what you want, but most of the time, if you are going to let outsiders modify the variable, you want them to be able to read it, too. Rather than having to declare both an attr_reader and an attr_writer, you can declare both at once like so:

class MyClass
  attr_accessor :message
end

Again, this is just a shortcut for defining methods that let you get at the instance variable from outside of the class.

Mark Reed
  • 91,912
  • 16
  • 138
  • 175
  • 5
    this is a much better & verbose answer that answers the question clearly for people coming from other languages - thanks! – manroe Apr 16 '15 at 06:47
9

attr_accesor gives you methods to read and write the instance variables. Instance variables are deasigned to be hidden from outside world so to communicate with them we should have attr_ibute accesor methods.

megas
  • 21,401
  • 12
  • 79
  • 130
3

In OOPS we have a concept called encapsulation which means, the internal representation of an object is generally hidden from view outside of the object's definition. Only the Object 'itself' can mess around with its own internal state. The outside world cannot.

Every object is usually defined by its state and behavior, in ruby the instance variables is called internal state or state of the object and according to OOPS the state should not be accessed by any other object and doing so we adhere to Encapsulation.

ex: class Foo def initialize(bar) @bar = bar end end

Above, we have defined a class Foo and in the initialize method we have initialized a instance variable (attribute) or (property). when we create a new ruby object using the new method, which in turn calls the initialize method internally, when the method is run, @bar instance variable is declared and initialized and it will be saved as state of the object.

Every instance variable has its own internal state and unique to the object itself, every method we define in the class will alter the internal state of the object according to the method definition and purpose. here initialize method does the same, such as creating a new instance variable.

var object = Foo.new(1)
#<Foo:0x00000001910cc0 @bar=1>

In the background, ruby has created an instance variable (@bar =1) and stored the value as state of the object inside the object 'object'. we can be able to check it with 'instance_variables' method and that methods returns an array containing all the instance variables of the object according to present state of the object.

object.instance_variables
#[
     [0]: @bar
 ]

we can see '@bar' instance variable above. which is created when we called the initialize method on the object. this '@bar' variable should not be visible (hidden) by default and so it cannot be seen by others from outside of the object except the object, from inside. But, an object can mess around with its own internal state and this means it can show or change the values if we give it a way to do so, these two can be done by creating a new instance methods in the class.

when we want to see the @bar variable by calling it we get an error, as by default we cannot see the state of an object.

show = object.bar
#NoMethodError: undefined method `bar' for #<Foo:0x00000001910cc0 @bar=1>
#from (irb):24
#from /home/.rvm/rubies/ruby-2.0.0-p648/bin/irb:12:in `<main>'

But we can access the variables by two methods, these two are called setter and getter methods, which allow the object to show or change its internal state (instance variables/attributes/properties) respectively.

class Foo
  def bar
    @bar
  end

  def bar=(new_bar)
    @bar = new_bar
  end
end

We have defined a getter(bar) and setter(bar=) methods, we can name them any way but the instance variable inside must the same as instance variable to which we want to show or change the value. setters and getters are a violation to OOPS concepts in a way but they are also very powerful methods.

when we define the two methods by re-opening the class and defining them, when we call the object with the methods, we can be able to view the instance variables(here @foo) and change its value as well.

object.bar
1

object.bar=2
2

object.bar
2

Here we have called the bar method (getter) which returns the value of @bar and then we have called bar= method (setter) which we supplied a new_value as argument and it changes the value of instance variable (@bar) and we can look it again by calling bar method.

In ruby we have a method called attr_accessor , which combines the both setter and getter methods, we define it above the method definitions inside the class. attr_* methods are shortcut to create methods (setter and getter)

class Foo
  attr_accessor :bar
end

we have to supply a symbol (:bar) as argument to the attr_accessor method which creates both setter and getter methods internally with the method names as supplied symbol name.

If we need only a getter method, we can call attr_reader :bar If we need only a setter method, we can call attr_writer :bar

attr_accessor creates both attr_writer and attr_reader methods

we can supply as many instance variables as we want to the attr_* methods seperated by commas

class Foo
  attr_writer :bar
  attr_reader :bar
  attr_accessor :bar, :baz
end
Ashok Allu
  • 31
  • 2
  • 4
1

Because attr_accessor defines methods, you can call them from outside the class. A @variable is only accessible from inside the class.

jleeothon
  • 2,907
  • 4
  • 19
  • 35
1

And another answer more compact (for Java developers) attr_accessor :x creates the getters and setters to @x

class MyClassA
  attr_accessor :x
end

is the same as

class MyClassB
  def x=(value) #java's typical setX(..)
    @x=value
  end
  def x
    @x
  end
end
estani
  • 24,254
  • 2
  • 93
  • 76