2
# encoding: utf-8
class Person
  attr_reader :short_name
  def initialize(short_name)
    @short_name = short_name
  end

  def greeting_line
    short_name = short_name.downcase
    "Hello #{short_name}"
  end
end

person = Person.new("MS. LEE")

puts person.short_name  => "MS. LEE"
puts person.greeting_line => NoMethodError: undefined method `downcase' for nil:NilClass

The exception occurs at "short_name = short_name.downcase" since (short_name = short_name) makes short_name become nil.

Why is "short_name" on the right side is not getting the value from the instance method "short_name"?

0

4 Answers 4

3

When you say

var = value

it always refers to the local variable var, even if you have the methods var and var= defined. (In your case, you have the method short_name defined by attr_reader, but you don't have short_name= defined.)

You have a couple ways around this. You can use the instance variable directly:

@var = value

Or you can use the var= method with an explicit self receiver:

self.var = value

This second form only works if you have a var= method defined, either explicitly, or with attr_accessor or attr_writer.

Now, when you do something like

foo = foo

This always introduces a local variable, foo on the left hand side. When ruby sees foo on the right hand side of the =, it resolves to the local variable foo, since foo is now in scope. So this always just sets foo back to its default value, nil.

Sign up to request clarification or add additional context in comments.

8 Comments

the "short_name" (on the left side) in the "greeting_line" is intended to be a local variable. The "short_name.downcase" (on the right side) in intended to be the value.
However, one of the reason I wrote it that way was to leave the instance variable unchange. If I use @short_name = .... this will change it.
It was intended not to have short_name= being defined, so the short_name = in the greeting_line should be a local variable--as intended.
@user608800 Once the local variable short_name is in scope, then using short_name is always treated as the local variable. It comes into scope from the left hand side of the =, and is then in scope in the right hand side (with its default value of nil.
Chris, would you please put it as an answer instead of a comment so others could vote on it. Also, if you could provide a reference/link to prove your conclusion is not coming from your own observation then it would be great.
|
1

In your case in greeting_line scope short_name is pointing to local variable and not to instance method short_name, for this example to work you should use self.short_name (this will tell ruby to use instance method)

def greeting_line
 short_name = self.short_name.downcase
 "Hello #{short_name}"
end

4 Comments

Thank you. I guess Ruby is always parsing the left side of the "=" first. And this cause the "short_name" on the right side point the the local variable "short_name" instead of the instance method "short_name"?
My previous comment doesn't make sense either. What happen if I have n = 0; n = n + 1. The n on the right side is obviously NOT looking at the n on the left side.
Nonetheless, I like this answer because self.short_name.downcase gives a hint of avoiding ambiguous. But it fails on answering WHY my code was ambiguous.
When you use = operator ruby tries to find var= method, then it looks for variable var if not present it will define it and assign it to result of right side of expresion. In your case at the moment right side is executed, short_name in current scope is local variable.
0

You simply forgot the @ when referencing short_name

@short_name

is an instance variable

short_name

is a local variable bound to the scope of the greeting_line function

Try this:

def greeting_line
  short_name = @short_name.downcase
  "Hello #{short_name}"
end

8 Comments

All these short_names... maybe just "Hello #{@short_name.downcase}".
You mean "Hello #{@short_name.downcase}"
Thank you for replying. I know @short_name.downcase would work. However, I am more interested on the variable scopes.
ie: "short_name" on the left side should be a local variable to the method greeting_line; in contrary, "short_name.downcase" on the right side is not. This "short_name" should be a method I declared earlier using "attr_reader :short_name"
To elaborate my point, if I change the line to "n = short_name.downcase" and "Hello #{n}", it would work too. This is why I choose the title "Ruby variable scopes" because I can't explain to myself why "short_name = short_name.downcase" doesn't work.
|
0

You should use:

  attr_accessor :short_name

And not attr_reader.

attr_reader produces the following method in your class:

def short_name
  @short_name
end

attr_accessor will also produce the method:

def short_name=(val)
  @short_name = val
end

Which will allow you to freely set the short_name as it was a local variable.

This is a nice blog post that demonstrates attr_reader, attr_writer and attr_accessor statements in ruby.

2 Comments

depends on if you want to be able to write to that variable as well as read from it
I believe you must use self.short_name=(val) for this to work. Otherwise, Ruby will still treat short_name as a local variable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.