Getting a NameError in very simple Ruby code [duplicate] - ruby

This question already has answers here:
Ruby local variable is undefined
(3 answers)
Closed 7 years ago.
I'm getting a NameError, which supposedly indicates that my array "pentagonals" is undefined. But I'm a monkey's uncle if I didn't define it in line 1. What am I forgetting/misunderstanding? The goal is to write a method that tells me whether a given number is pentagonal.
pentagonals = []
def pent?(num)
pentagonals.include?(num)
end
(1..1000).each {|i|
pentagonals << (i * (3 * i - 1) / 2)
}
puts pent?(1)

Global variables in Ruby are distinguished from every other program name (like regular variables, class names, methods names, module names, etc.) by an initial $, so you should change your program in this way:
$pentagonals = []
def pent?(num)
$pentagonals.include?(num)
end
(1..1000).each {|i|
$pentagonals << (i * (3 * i - 1) / 2)
}
puts pent?(1)
Note that global variables should be used sparingly, in fact they are dangerous because they can be written to from anywhere in your program.

Variables in a method are local to the method scope, unless they are passed in as parameters.
The method can also access global, class variables and other methods in the same scope.
class MyClass
# calling methods in the same class
def method1
method2
end
def method2
puts 'method2 was called'
end
# getter / setter method pair for class variables
# #class_variable is only accessible within this class
def print_class_variable
puts #class_variable
end
def set_class_variable(param)
#class_variable = param
end
# global variables can be accessed from anywhere
def print_global_var
puts $global_variable
end
def self.some_class_method
# cannot be directly accessed by the instance methods above
end
end
Note that global variables are not recommended as they can easily result in conflicts and ambiguity.
class Dog
$name = "Dog"
end
class Cat
$name = "Cat"
end
puts $name
# which one does $name refer to?

Related

Ruby: Working with instance variables within a case statement [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 2 months ago.
I'm perplexed as to why I need to selectively refer to an instance variable with "self" inside a case statement inside a class method:
I have a class with an instance method #working_dir:
class FileSystem
attr_accessor :sizes, :working_dir
attr_reader :input
def initialize(input)
#input = input.split("\n")
#sizes = Hash.new(0)
#working_dir = []
end
...
end
I've defined a method parse_cmd that performs an operation on #working_dir depending on the outcome of a case statement:
...
def parse_cmd(str)
cmd_arr = str.split(' ')
return unless cmd_arr[1] == 'cd'
case cmd_arr[2]
when '..'
working_dir.pop
when '/'
self.working_dir = ['/']
else
working_dir << cmd_arr[2]
end
end
...
Rubocop/the interpreter yells at me if I exclude the self on self.working_dir = ['/']. Why is this? Why do I need to include it here, but not on other references to #working_dir within the case statement?
Consider a simple example:
class A
attr_accessor :b
def initialize(b)
#b = b
end
def c
b = 42
end
end
a = A.new(27)
# => #<A:0x00007f7999088bc0 #b=27>
a.c
# => 42
a.b
# => 27
Calling a.c is assigning 42 to a local variable b, and is not modifying the instance variable #b.
I'd either need to use self.b = 42 or #b = 42 to ensure I am modifying the instance variable.
In your case, you don't need to use self.working_dir elsewhere because those uses cannot be construed as assigning to a local variable. Because no local variable working_dir exists, the accessor method is used.
In your case statement, you are NOT directly refering to the #working_dir instance variable. Instead, you are using the accessor methods defined by attr_accessor :working_dir at the top of your class.
When calling attr_accessor, it will effectively define two methods on your class:
def working_dir
#working_dir
end
def working_dir=(value)
#working_dir = value
end
This allows you to access the value of the instance variable via the method call as if it were a local variable (but it's not, it's always a method call).
Now, in order to call the setter method working_dir=, Ruby requires that you call it with an explicit receiver (self in your case).
This is because without an explicit receiver, if you assign some value, Ruby will always assign to local variable. With working_dir = 'value', you are thus always creating a local variable named working_dir and assign a value to it.
If you use an explicit receiver however, e.g. self.working_dir = 'value', Ruby knows that this can not be a variable assignment anymore and will thus call your setter method.

Confused about the assignment method in Ruby (defined method with equal sign) [duplicate]

This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 6 years ago.
I am really confused about the assignment method in Ruby. In the documentation, it says:
Methods that end with an equals sign indicate an assignment method.
For assignment methods, the return value is ignored and the arguments
are returned instead.
I know this can be use as setter method in Class. However, I still feel confused about the concept. In the code below:
class Foo
# setter method
def foo=(x)
puts "OK: x=#{x}"
end
end
f = Foo.new
f.foo = 123 # OK: x=123
def a=(value)
puts "value is #{value}"
end
a = 123 # why there is no output?
p a # 123, why this only return the argument?
p a = 123 # 123, why this only return the argument?
Why the method with equal sign run differently in the Class and outside the class?
a = 123
In principle, there is ambiguity here. Is it a setter invocation or a local variable assignment? Ruby resolves this by choosing local variable assignment. If you want the method, be explicit.
self.a = 123
In the other case, there's no ambiguity.
Seeing something like a = 123, the interpreter assumes that this is always an assignment operation. However, you can call self.a = 123- as this cannot be a proper assignment (variable name cannot include a dot); it will invoke a method you defined.
Note that the same happens inside the class, so it is not a different behaviour:
class Foo
def foo=(x)
puts "OK: x=#{x}"
end
def bar
foo = 1
end
def baz
self.foo = 1
end
end
Calling bar will not print your message, it will just perform an assignment. baz will execute your setter method.

How to Initialize Class Arrays in Ruby

I want to create an empty array as a class instance variable in Ruby. However, my current method does not seem to work.
Here is my code:
class Something
#something = []
def dosomething
s = 5
#something << s
end
end
When I call the function, it gives me an undefined method traceback.
However, if I do something similar with class variables, i.e.:
class Something
##something = []
def dosomething
s = 5
##something << s
end
end
This works perfectly.
I know I can use the initialize method to actually create an empty list for #something, but is there another way of doing this without using the initialize method? And why does this work for class variables?
EDIT: Fixed typo
You need to use initialize as a constructor as below code and is there any reason why not to use initialize/constructor. And please fix a typo error in class definition Class Something to class Something no camel case or first letter capitalize while in class
class Something
def initialize
#something = Array.new
end
def dosomething
s = 5
#something << s
end
end
class variable ## are available to the whole class scope. so they are working in the code and if you want to use instance variable # you need to initialize it as above. The instance variable is share with instance/objects of a class
for more details visit the link Ruby initialize method
At first you have a typo. Change Classto class. Next I suggest to use the initialize method. While creating a new object this is the perfect place to initialize instance variables.
class Something
##my_class_variable = [1]
def initialize
#something = []
end
def dosomething
s = 5
#something << s
end
def self.get_my_class_variable
##my_class_variable
end
end
Your script will be read and executed from top to bottom and after this,
you can access the class Something. While the parser reads your script/class/module you can define class variables (##), execute mixins and extend the class with other modules. This is why you can define a class variable, but you can not define an instance variable. Because actually you have no instance object from your class. You only have a class object. In ruby everything is an object. And your class object has a defined class variable now:
Something.get_my_class_variable
# => [1]
Now you can create an instance from your class. With Something.new the initialize method will be invoked and your instance variable will be defined.
something = Something.new
something.dosomething
# => [5]
Later, if you are familar with this you can define getter and setter methods with attr_reader, attr_writer and attr_accessor for instance objects or cattr_reader, cattr_writer and cattr_accessor for class objects. For example:
class Something
attr_reader :my_something
def initialize
#my_something = []
end
def dosomething
s = 5
#my_something << s
end
end
something = Something.new
something.my_something
# => []
something.dosomething
# => [5]
something.my_something
# => [5]
Your problem in trying to access #something in your instance method is that, in the scope of instance methods, # variables refer to instance variables, and your #something is a class instance variable.
# variables are instance variables of the instance that is self when they are created. When #something was created, self was the class Something, not an instance of Something, which would be the case inside an instance method.
How then to access a class instance variable in an instance method? Like regular instance variables, this must be done via a method, as in attr_accessor. One way to do this is to use class << self to tell the Ruby interpreter that the enclosed code should be evaluated with the class (and not the instance) as self:
class C
#foo = 'hello'
class << self
attr_accessor :foo # this will be a class method
end
def test_foo # this is, of course, an instance method
puts self.class.foo # or puts C.foo
end
end
We can show that this works in irb:
2.3.0 :005 > C.foo
=> "hello"
2.3.0 :006 > C.new.test_foo
hello
You have correctly created a class instance variable, #something, and initialized it to an empty array. There are two ways for instances to obtain or change the value of that variable. One is to use the methods Object#instance_variable_get and Object#instance_variable_set (invoked on the class):
class Something
#something = []
def dosomething
s = 5
self.class.instance_variable_get(:#something) << s
end
end
sthg = Something.new
sthg.dosomething
Something.instance_variable_get(:#something)
#=> 5
The other way is to create an accessor for the variable. There are several ways to do that. My preference is the following:
Something.singleton_class.send(:attr_accessor, :something)
Something.something #=> [5]
In your dosomething method you would write:
self.class.something << s

question about parameter passing in Ruby

Comparing the following two code snippets:
class Logger
def self.add_logging(id_string)
define_method(:log) do |msg|
now = Time.now.strftime("%H:%M:%S")
STDERR.puts "#{now}-#{id_string}: #{self} (#{msg})"
end
end
end
class Song < Logger
add_logging "Tune"
end
song = Song.new
song.log("rock on")
class Logger
def self.add_logging(id_string)
def log(msg)
now = Time.now.strftime("%m")
puts "#{now}-#{id_string}: #{self}(#{msg})"
end
end
end
class Song < Logger
add_logging "Tune"
end
s = Song.new
s.log("can't smile with you")
#=> NameError: undefined local variable or method `id_string' for #<Song:0x000001018aad70>
I can't figure out why the second case gets the NameError error, and why the id_string can't be passed to it.
A def creates a new scope; a block does not. A new scope cuts off the visibility of the surrounding variables. ruby has two other 'new scope creators': class and module.
x = 10
3.times do |i|
puts i * x
end
def do_stuff
puts x * 10
end
do_stuff
--output:--
0
10
20
`do_stuff': undefined local variable or method `x'
id_string is local to method add_logging. In your latter implementation, log-method can not see it, hence the error. In the former implementation, you dynamically define log-method within add_logging.
In other words, local variable is visible within the scope it is defined in (in this case, a method). In that latter implementation, you have nested scopes (=a method declaration within a method), and inner scope can not access variables that are local to outer scope.
As suggested in answer by #stef, you might get around this my widening the scope of the variable. I would recommend keeping variable scopes as 'tight' as possible, and therefore prefer your first implementation.
Try this with a class variable?
class Logger
def self.add_logging(id_string)
##my_id = id_string
define_method(:log) do |msg|
now = Time.now.strftime("%H:%M:%S")
STDERR.puts "#{now}-#{##my_id}: #{self} (#{msg})"
end
end
end
Class variables should be avoided in ruby due to their problematic nature. The ruby way is to use 'class instance variables' instead.

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.

Resources