Accessing properties of an object in Ruby are a bit different than they are in JavaScript. In JavaScript, if you defined an object constructor like so:
function Foo() {
this.x = 1;
this.y = 2;
}You could access the properties of an instance of Foo like so:
> foo = new Foo();
{ x: 1, y: 2 }
> foo.x
1
> foo.y
2But if you define a similar class in Ruby:
class Foo
def initialize
@x = 1
@y = 2
end
endTrying to access the properties has a very different result:
[1] pry(main)> foo = Foo.new
=> #<Foo:0x007fb421911888 @x=1, @y=2>
[2] pry(main)> foo.x
NoMethodError: undefined method 'x' for #<Foo:0x007fb421911888 @x=1, @y=2>
from (pry):8:in '__pry__'
[3] pry(main)> foo.y
NoMethodError: undefined method 'y' for #<Foo:0x007fb421911888 @x=1, @y=2>
from (pry):9:in '__pry__'What happened here? Well, the meaning of @x = 1 in Ruby, and the meaning of
this.x = 1 in JavaScript are basically the same. But the meaning of foo.x
in JavaScript and Ruby is very different. When you type foo.x in Ruby, this is
equivalent to foo.x() -- Ruby is trying to call a method named x on your
instance, not access the x property.
You can think of properties (or "instance variables") in Ruby as private. They are something you create that your class uses internally. You need to create methods if you want other things to access them.
In JavaScript, that would mean doing something more like this:
function Foo() {
this._x = 1;
this._y = 2;
}
Foo.prototype.x = function() {
return this._x;
};
Foo.prototype.y = function() {
return this._y;
};Which ends up doing the same thing as before:
> foo = new Foo()
{ _x: 1, _y: 2 }
> foo.x()
1
> foo.y()
2In Ruby, you could do that like this:
class Foo
def initialize
@x = 1
@y = 2
end
def x
@x
end
def y
@y
end
endAnd now things work as expected:
[4] pry(main)> foo = Foo.new
=> #<Foo:0x007f8f44b99ee0 @x=1, @y=2>
[5] pry(main)> foo.x
=> 1
[6] pry(main)> foo.y
=> 2But what if you wanted to set the value in Ruby?
[7] pry(main)> foo.x = 3
NoMethodError: undefined method 'x=' for #<Foo:0x007f8f44b99ee0 @x=1, @y=2>
from (pry):18:in '__pry__'Well, that's still broken. But the error we see here is a bit of a hint: it says
that the method x= is undefined. That's because whenever you set an attribute
of an object in Ruby, such as foo.x = 1, it's actually trying to evaluate
foo.x=(1), where x= is a named method on the object! That means you can just
redefine Foo like so:
class Foo
def initialize
@x = 1
@y = 2
end
def x
@x
end
def x=(value)
@x = value
end
def y
@y
end
endAnd now you can set the value of x:
[8] pry(main)> foo = Foo.new
=> #<Foo:0x007f8f469750e0 @x=1, @y=2>
[9] pry(main)> foo.x = 3
=> 3But note that y still gives an error, because we didn't define a setter method
for that:
[10] pry(main)> foo.y = 4
NoMethodError: undefined method 'y=' for #<Foo:0x007f8f469750e0 @x=3, @y=2>
from (pry):40:in '__pry__'This is useful if you want to make an attribute read-only.
This is all pretty verbose, though. This Ruby object is much longer than the original JavaScript object. Luckily, Ruby provides shortcuts that allow us to specify this behavior in a much simpler way.
Ruby has three helpful functions you can use to do things like this:
attr_accessor :xis equivalent to definingdef xanddef x=methodsattr_reader :xis equivalent to defining just adef xmethodattr_writer :xis equivalent to defining just adef x=method
For example, if you had this class instead:
class Foo
attr_accessor :a, :b
attr_reader :c
attr_writer :d
def initialize
@a = 1
@b = 2
@c = 3
@d = 4
end
endYou can read and write a and b:
[11] pry(main)> foo = Foo.new
=> #<Foo:0x007f8f46a25210 @a=1, @b=2, @c=3, @d=4>
[12] pry(main)> foo.a
=> 1
[13] pry(main)> foo.a = 10
=> 10
[14] pry(main)> foo.b
=> 2
[15] pry(main)> foo.b = 20
=> 20You can only read c:
[18] pry(main)> foo.c
=> 3
[19] pry(main)> foo.c = 30
NoMethodError: undefined method 'c=' for #<Foo:0x007f8f46a25210 @a=100, @b=200, @c=3, @d=4>
from (pry):71:in '__pry__'And you can only set d:
[20] pry(main)> foo.d
NoMethodError: undefined method 'd' for #<Foo:0x007f8f46a25210 @a=100, @b=200, @c=3, @d=4>
from (pry):72:in '__pry__'
[21] pry(main)> foo.d = 40
=> 40