Help with class variable example from "Well Grounded Rubyist"

Can someone help me grok this example from “Well Grounded Rubyist”?

class Person
attr_accessor :age
def initialize(options)
self.age = options[:age]
end

def teenager?
(13…19) === age
end
end

As I understand it instantiating an object with, for example:

Person.new(age: 17)

… sets the class variable age to 17. I would have expected age to be
an instance variable, set the usual way with:

def initialize(age)
@age = age
end

… but, for some reason, a class variable is used here though it is
settable.

Next the options hash. This is where I get completely confused. The hash
is passed to the initialize method though it doesn’t yet exist and
object instantiation doesn’t refer to it either. Then, the class
variable age is somehow passed the new options hash using the age value
as the key but … now it’s the symbol :age. WTF???

gvim

On Fri, Mar 14, 2014 at 10:18 PM, gvim [email protected] wrote:

end
@age = age
end

… but, for some reason, a class variable is used here though it is
settable.

It’s not a class variable.
It’s a regular method calling.

attr_accessor :age
has the same effect as

def age
@age
end

def age=(age)
@age=age
end

so…

self.age = options[:age]

calls the method (setter) age=

Next the options hash. This is where I get completely confused. The hash is
passed to the initialize method though it doesn’t yet exist and object
instantiation doesn’t refer to it either.
Then, the class variable age is
somehow passed the new options hash using the age value as the key but
… now it’s the symbol :age. WTF???

The three bellow are the same.

Person.new({:age => 17})
Person.new({age: 17})
Person.new(age: 17)

When passing a Hash you may ommit the curly brackets.
When the key is a symbol, you can use the new (1.9) Hash literal syntax

On 15/03/2014 03:12, Abinoam Jr. wrote:

def age=(age)
@age=age
end

so…

self.age = options[:age]

calls the method (setter) age=

I still don’t understand where the options hash came from and how :age
is being used as a hash key if you say it’s a method call. Is options
some kind of system hash?

gvim

On 15/03/2014 22:00, Matthew K. wrote:

def initialize(options)

It’s explicitly named. No magic required.

It may be explicitly named and obvious to you but, as I said earlier, I
haven’t a clue what the options hash refers to.

gvim

gvim wrote:

I still don’t understand where the options hash came from and how :age is
being used as a hash key if you say it’s a method call. Is options some
kind of system hash?

Read the rest of Abinoam’s earlier response (sorry if I got your name
wrong). He showed that there are multiple ways of passing a hash as a
parameter to a function call.

Also note the line:
def initialize(options)

It’s explicitly named. No magic required.

On Mon, Mar 17, 2014 at 3:58 PM, gvim [email protected] wrote:

Read the rest of Abinoam’s earlier response (sorry if I got your name
haven’t a clue what the options hash refers to.

gvim

Let’s try the explanation from the other direction:

class Person
attr_accessor :age
def initialize(options)
self.age = options[:age]
end

def teenager?
(13…19) === age
end
end

Person.new(age: 17)

When you call a method with this syntax: object.method(key: value)
what it means is:
Create a hash with a key with the symbol :key and the value → value.
So this is equivalent to

a = { :key => value}

It’s a little syntactic sugar to make it look like you are naming
method parameters, but in the end it’s just a simple hash object, with
keys and values, where the keys happen to be symbols.

So, this hash is pass to the method, in this case “new”, which in turn
will pass it internally to “initialize”. Let’s look at that:

def initialize(options)
self.age = options[:age]
end

You have declared that your initialize method receives one parameter
called options. options is the name of the local variable, local to
the method, that is bound to the object that you pass to the “new”
method. In this example it happens to be a hash, remember, so we can
call the method [] on it, with a symbol and get an integer back, cause
that’s what you put in, when calling “new”. You created a hash with
key :age and value 17, so options[:age] returns 17.

Summarizing this part, what you see is equivalent to:

options = { :age => 17}
options[:age] #=> returns 17

Now, the part related to the age instance variable. As Abinoam
explained, attr_accessor creates two methods for you:

def age=(age)
@age = age
end

and

def age
@age
end

So, internally you do have an instance variable called @age. From the
outside, you could set it like

person = Person.new(17)
person.age=18 # birthday → this calls the method age=
puts person.age # this calls the method age

From the inside, you can call the methods on self (the current
instance):

class Person
def test
puts self.age
end
end

Person.new(25).test # should print 25

Now, the age method can be called without self, like this:

class Person
def test
puts age
end
end

There’s no ambiguity here that you mean the method age. So no problem.
But the setter is different. What should Ruby do with this?

class Person
def test_new_age new_age
age = new_age
end
end

As you can see, this looks exactly the same as a regular local
variable assigment. To avoid this ambiguity Ruby tells you to call
methods with the self receiver, in order to differentiate from local
variable assignments. So you need to do:

class Person
def test_new_age new_age
self.age = new_age
end
end

And everything is fine again:

person = Person.new(25)
person.test_new_age(26)
person.age #=> 26

So, coming back to your original example:
def initialize(options)
self.age = options[:age]
end

It’s taking the value assigned to key :age from the hash named
options, and calling the method age= of the object self, passing the
value it got from the hash. The method age= is the one generated by
attr_accessor, which just stores the value in the @age instance
variable.

Hope this helps,

Jesus.

On 17/03/2014 15:29, Jes?s Gabriel y Gal?n wrote:

 (13..19) === age

 self.age = options[:age]

Summarizing this part, what you see is equivalent to:

person.age=18 # birthday --> this calls the method age=
Person.new(25).test # should print 25
But the setter is different. What should Ruby do with this?
variable assignments. So you need to do:
person.test_new_age(26)
attr_accessor, which just stores the value in the @age instance
variable.

Hope this helps,

Jesus.

Yes, very clear. Thanks very much Jesus.

gvim