Ruby variable scopes - ruby

# 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"?

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.

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

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

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.

Related

Ruby: Why is an instance variable defined inside a class nil?

class Something
#b = [4432]
def screen
puts #b.class
end
end
s = Something.new
s.screen
outputs 'Nilclass'. Was wondering, why does an instance variable which is defined inside a class always part of NilClass?
Instance variables belong to an object (aka an instance), that's why they are called instance variables. Every instance has its own instance variables.
In your case, there are two objects: Something (which is an instance of Class) and s (which is an instance of Something). Each of those two objects has its own set of instance variables. Something has an instance variable called #b which points to [4432]. s has no instance variable named #b because you never assign to it, and uninitialized instance variables evaluate to nil.
You need to set it like this:
class Something
def initialize
#b = [4432]
end
def screen
puts #b.class
end
end
The way you did it, the variable belongs to Something class itself, not its instance. Observe:
class Something
#b = [4432]
end
s = Something.new
s.instance_variable_get(:#b) # => nil # !> instance variable #b not initialized
Something.instance_variable_get(:#b) # => [4432]
Generally the instance variable must be defined inside the constructor whereas in ruby the default constructor is initialize the syntax is
def initialize
end #these is the default constructor in ruby
so when we define the insatnce variable inside the constructor and when we create the instance of a class then that instance/object will contain the copy of instance variables
most important thing is that though the instance/object contains the instance variable the instance/object cannot access it why because by default the instance data is private so in order to access it we need to define the getters and setter for those instance variable
class Something
attr_accessor:b
def initialize
#b = [4432]
end
s=Something.new
puts"#{s.b}"
Because the variable #b does not exist!. For e.g. the following would produce the same results you see.
class Something
#b = [4432]
def screen
puts #a.class #=> note #a which is non-existent
end
end
s = Something.new
s.screen
Whereas
class Something
#b = [4432]
def screen
puts #a.class
end
def self.screen
puts #b.class
end
end
s = Something.new
s.screen #=> NilClass
Something.screen #=> Array
if you initialize #b outside the initializer, you want #b scope to be a class variable, so you have to call it ##b :
##b has the same value for all Instance of your Something Class
like :
class Somthing
##b = [4432]
def initialize
#[...]
end
def screen
puts ##b.class
end
end
#Jörg W Mittag answer is correct. I just wont to add, that defining an instance variable in class != defining instance variable in an instance of that class. To create an instance variable, in your case in s instance you need to add an initialize method witch gets triggered when new method is called on a class.
def initialize(b_value = default_value)
#b = b_value
end

Rails and class variables

class MainController < ApplicationController
#my_var = 123
def index
var1 = #my_var
end
def index2
var2 = #my_var
end
end
Why is neither var1 no var2 equal to 123?
Variables with # are instance variables in ruby. If you're looking for class variables, they're prefixed with ##, so you should be using ##my_var = 123 instead.
And the reason you can't use instance variables that way, is because if you define instance variables outside methods, they don't live in the same scope as your methods, but only live while your class is interpreted.
var1 in your example is a local variable, which will only be visible inside the index method.
Examples:
class Foo
##class_variable = "I'm a class variable"
def initialize
#instance_variable = "I'm an instance variable in a Foo class"
local_variable = "I won't be visible outside this method"
end
def instance_method_returning_an_instance_variable
#instance_variable
end
def instance_method_returning_a_class_variable
##class_variable
end
def self.class_method_returning_an_instance_variable
#instance_variable
end
def self.class_method_returning_a_class_variable
##class_variable
end
end
Foo.new
=> #<Foo:0x007fc365f1d8c8 #instance_variable="I'm an instance variable in a Foo class">
Foo.new.instance_method_returning_an_instance_variable
=> "I'm an instance variable in a Foo class"
Foo.new.instance_method_returning_a_class_variable
=> "I'm a class variable"
Foo.class_method_returning_an_instance_variable
=> nil
Foo.class_method_returning_a_class_variable
=> "I'm a class variable"
#my_var, in your sample code, is an instance variable on the class MainController. That is, it's a class-level instance variable, and not an instance-level instance variable. It exists in a totally different scope to the instance variable associated with an instance of the class.
Within the body of your instance methods, index and index2, you are attempting to dereference an instance variable on an object that is an instance of class MainController, but you have not defined that instance variable anywhere, so you get back nil.
If you want to use #my_var as a class-level instance variable, you can get its value from within an instance of the class like this:
var1 = self.class.instance_variable_get(:#my_var)
Class variables are indicated with a ## prefix, and their use is not entirely encouraged. A couple of minutes with Google will tell you why.
Because code executes in different context. You can see here:
class MainController
puts self
def print_self
puts self
end
end
#=> MainController
MainController.new.print_self #=> <MainController:0x00000001761140>
As you can see in first print the self is MainController, in second print the self is the object which derived from MainController class.
In the assignment to #my_vay this variable belongs to MainController, and in the second cases, the #my_var belongs to object (not a class) and these varaibles are different.

In Ruby, isn't self.user_name the same as #user_name?

In Ruby, isn't an instance variable like #foo and a class variable, ##bar?
In some code, I see a few
self.user_name = #name
or even
a += 1 if name != user_name # this time, without the "self."
# and it is the first line of a method so
# it doesn't look like it is a local variable
what is the self for? I thought it might be an accessor, but then can't it be just user_name instead of self.user_name? And I don't even see any code to make it an accessor, like attr_accessor, and not in the base class either.
In Ruby:
#foo is an instance variable
##bar is a class variable
Instance and class variables are private by default. It means, you can't set or get the value outside the class or the module itself.
If you want to set a value for foo, you need an attribute accessor.
class Model
def foo
#foo
end
def foo=(value)
#foo = value
end
end
For convenience (and performance reason), this is the same of
class Model
attr_accessor :foo
end
Then you can do
m = Model.new
m.foo = "value"
And within the class you can doo
class Model
# ...
def something
self.foo = "value"
# equivalent to
#foo = value
end
end
what is the self for? I thought it might be an accessor
It's a method at least - probably an accessor. self.name = something will call the method name= and self.name, or if no local variable called name exists, just name will call the method name.
but then can't it be just user_name instead of self.user_name?
When invoking the name= method you need the self because name = something would just create a local variable called name. When invoking the name method, it doesn't matter whether you write name or self.name unless there is also a local variable called name.
And I don't even see any code to make it an accessor, like attr_accessor, and not in the base class either.
If there is no call to attr_accessor and no explicit definition of name and name= anywhere, they might be handled by method_missing or defined by a different method than attr_accessor.
isn't self.user_name the same as #user_name
Only if it's defined to be. If you define user_name and user_name=? usingattr_accessorthey will get and set#user_name`. However if you define them through other means (or manually), they can do whatever you want.
For example ActiveRecord uses method_missing to "define" getter and setter methods that correspond to data base columns. So if your ActiveRecord class belongs to a table with a user_name column, you'll have user_name and user_name= methods without defining them anywhere. The values returned by user_name and set by user_name = will also not correspond to instance variables. I.e. there will be no instance variable named #user_name.
Another way to automatically define user_name and user_name= without using instance variables is Struct:
MyClass = Struct.new(:user_name)
Here MyClass will have the methods user_name and user_name=, but no instance variable #user_name will be set at any point.
Welcome in the "scope" hell!
Of course, if we have the full code, it should be easier to explain what you see.
Let me try:
class Woot
attr_accessor :name
def initialize
self.name = 'Bistromathic Drive'
end
def foo
puts name # hum… I don't know name. An attr? Oh Yes!
name = '42' # New local var named 'name'
puts name # Yeah! I know a local named 'name'
puts self.name # I know the attr named 'name'
name = 'Improbability Drive' # Change the local
puts name
puts self.name # attr didn't move
self.name = 'What was the question?'
puts name
puts self.name
end
end
w = Woot.new
w.foo
# => Bistromathic Drive
42
Bistromathic Drive
Improbability Drive
Bistromathic Drive
Improbability Drive
What was the question?
The partial code you show is "suspect" to me, and I'd prefer to explain the base of var scope.

Can I initialize a class variable using a global variable? (ruby)

Do I have create extra method for this kind of assignment? ##variable = #global_variable Why? I want to have some variables that hold values and definitions to be accessible all through my script and have only one place of definition.
#global_variable = 'test'
class Test
##variable = #global_variable
def self.display
puts ##variable
end
end
Test.display #gives nil
In Ruby, global variables are prefixed with a $, not a #.
$global = 123
class Foo
##var = $global
def self.display
puts ##var
end
end
Foo.display
correctly outputs 123.
What you've done is assign an instance variable to the Module or Object class (not sure which); that instance variable is not in scope of the class you've defined.

When to use 'self' in Ruby

This method:
def format_stations_and_date
from_station.titelize! if from_station.respond_to?(:titleize!)
to_station.titleize! if to_station.respond_to?(:titleize!)
if date.respond_to?(:to_date)
date = date.to_date
end
end
Fails with this error when date is nil:
NoMethodError (You have a nil object when you didn't expect it!
The error occurred while evaluating nil.to_date):
app/models/schedule.rb:87:in `format_stations_and_date'
app/controllers/schedules_controller.rb:15:in `show'
However, if I change date = date.to_date to self.date = self.date.to_date, the method works correctly.
What's going on? In general, when do I have to write self?
Edit: It's not related to the question, but please note that there is no "titleize!" method.
Whenever you want to invoke a setter method on self, you have to write self.foo = bar. If you just write foo = bar, the ruby parser recognizes that as a variable assignment and thinks of foo as a local variable from now on. For the parser to realize, that you want to invoke a setter method, and not assign a local variable, you have to write obj.foo = bar, so if the object is self, self.foo = bar
You disambiguiate between the instance method name and a local variable using self (it is allowed to have both with the same name in the same scope). In other words, there will be a method name resolution only if there is no local or block variable of the same name in scope. Behold:
class Foo
attr_accessor :boo
def do_boo
boo = 123
puts "Locvar: #{boo} Method: #{self.boo}"
end
end
Foo.new.do_boo
Here's why: imagine you have a module which implements a method. This method assigns something to it's internal local variable
"foo" which is used for some computation. If you skip the "self" part, the method will make a "foo=" method call on the object
whose class includes the module, which was not the intention of the author and can be downright disastrous.
class Foo
def bar=(new_value_of_bar)
set_off_nukes(new_value_of_bar / 3)
end
end
module InnocentModule # written by a different author elsewhere
def do_useful_stuff
...
bar = Math.sin(something) # we're dead
end
end
Foo.send(:include, InnocentModule)
Another crucial part where you have to use self is when invoking the Object#class method, because simply saying "class" means a class keyword for Ruby.

Resources