The bug on line 11 is put there on purpose. I am curious about how pry works in this example.
In the code below, when I enter pry, if I type name I get nil, which means that pry is outputting the value of the local variable which will be initialized on line 11. However, there is a getter method name and if I output its value on line 9 I get "Nemo".
Does pry first looks for local variables in a method? If so, how come that name is not undefined on line 9?
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
def change_name
binding.pry
p name
name = name.upcase
end
end
fish = Animal.new('Nemo')
p fish.name # => 'Nemo'
p fish.change_name
name = is a variable assignment, which means name is a local variable.
Ruby anticipates this and interprets all instances of name in that method to be as such. It seems like psychic knowledge, but remember Ruby has already read and compiled this function long before it is actually executed.
Related
I want to instantiate an object from a class I wrote on a different file. What I got is wrong number of arguments (given 1, expected 0) (ArgumentError)
Here is the main code
# ./lib/parking_lot
require_relative './lot.rb'
class ParkingLotInterface
def initialize(input: $stdin, output: $stdout)
#input, #output = input, output
#lot = nil
end
def prompt_input
#lot = Lot.new(10)
end
end
parking_lot_interface = ParkingLotInterface.new(input: $stdin, output: $stdout)
parking_lot_interface.prompt_input
And here is the object class
# ./lib/lot
class Lot
attr_reader :slots,
def initialize(size)
#slots = Arrays.new(size)
end
end
The error was thrown at the line where I tried to instantiate a new Lot object. Looking at the internet, people who had the same problem got told that they didn't specify def initialize in the class, or they mistyped it. However, I did what they all said and I still faced wrong number of arguments (given 1, expected 0) (ArgumentError)
What did I do wrong?
In Ruby, method definitions are expressions as well (in fact, in Ruby, everything is an expression, there are no statements), so they evaluate to an object. Method definition expressions evaluate to a Symbol denoting the name of the method that was defined.
So,
def initialize(*) end
#=> :initialize
In your code, you have a comma after attr_reader :slots, which means that you pass two arguments to attr_reader, namely the symbol :slots and the expression def initialize(…) … end. Since Ruby is a strict language, the arguments to attr_reader will be evaluated first, before attr_reader itself is executed.
So, what happens first is that the method definition expression gets evaluated. This defines a (private) method named initialize. It also evaluates to the symbol :initialize.
Next, the expression attr_reader :slots, :initialize gets evaluated, which defines two methods named slots and initialize, thus overwriting the method you just defined. Note that this will print a warning:
lot.rb:3: warning: method redefined; discarding old initialize
lot.rb:5: warning: previous definition of initialize was here
You should always read the warnings, the Ruby developers don't spend the hard work putting them in just for the fun of it!
The solution is to remove the comma telling Ruby to look for a second argument.
There is a second error in your code, namely that you misspelt Array within Lot#initialize.
And, there are a couple of stylistic improvements that you could make:
There is no need to pass a path and a filename extension to require_relative. It should be require_relative 'lot'.
Un-initialized instance variables evaluate to nil, so there is no need to initialize #lot to nil.
$stdin and $stdout are the default argument values of the stdin: and stdout: keyword parameters, so there is no need to pass them explicitly.
It is seldom necessary to create an array of a specific size, since Ruby arrays are dynamic and can change their size at any time.
With all this taken in to account, your code would look something like this:
# ./lib/parking_lot
require_relative 'lot'
class ParkingLotInterface
def initialize(input: $stdin, output: $stdout)
#input, #output = input, output
end
def prompt_input
#lot = Lot.new(10)
end
end
parking_lot_interface = ParkingLotInterface.new
parking_lot_interface.prompt_input
# ./lib/lot
class Lot
attr_reader :slots
def initialize(size)
#slots = Array.new(size)
# could be #slots = []
# depending on how you use `#slots` later
end
end
Delete the comma after
attr_reader :slots,
it would be
attr_reader :slots
And take a look, you are trying to instance Arrays (and must not to be in plural) on lot.rb
def initialize(size)
#slots = Arrays.new(size)
end
it would be
def initialize(size)
#slots = Array.new(size)
end
I'm brand-new to Ruby coming from a Java background. I don't understand the relationship between instance methods and variables yet, apparently. I just had some code like this:
class Problem022
FILENAME = "resources/p022_names.txt"
#name_array = [] # <--- class line 3
def do_problem
read_input()
sort_input()
# do more stuff
end
def read_input
# <--- read_input line 2
File.open(FILENAME, "r") do |file|
file.each_line do |line|
scanner = StringScanner.new(line)
while scanner.exist?(/,/) do
name = scanner.scan_until(/,/)
#name_array.push(name) # <--- marked line
end
# more logic
end
def sort_input()
#name_array.sort!
end
# other methods
end
puts problem = Problem022.new.do_problem()
I confirmed this worked correctly up until "marked line" (see comment above), but on that line, I got the error
in `block (2 levels) in read_input': undefined method `push' for nil:NilClass (NoMethodError)
I was able to solve it by (kinda blindly) adding the line
#name_array = []
at "read_input line 2", but I don't understand why this worked. Why doesn't the method recognize the declaration of instance variable #name_array in the class body? It seems to be able to use the constant FILENAME just fine, and that's defined in basically the same place. The method and the variable are both instance, not class, so I wouldn't expect any problems there. I just realized that the program still runs fine even if the declaration on "class line 3" is commented out, which only confuses me more.
I've read multiple definitions of scope in Ruby but none of them cleared this up for me. What am I misunderstanding?
Here's some code that should illuminate what's going "wrong".
class Problem
#name = 'foo' # this belongs to the class, because that's what current `self` is.
# instance method
# inside of this method, `self` will be an instance of the class
def action
#name
end
# class method (or class instance method)
# inside of this method, `self` will refer to the class
def self.action
#name
end
end
p1 = Problem.new # an instance of the class
p2 = Problem # the class itself
p1.action # => nil, you never initialize #name in the context of class instance
p2.action # => "foo"
Key points:
classes are objects and can have instance variables too. These variables are not in any way related to those of instances of the class.
concept of "current self" is one of ruby's fundamentals (related to "implicit receiver method calls"). You won't get very far without understanding it.
This question already has answers here:
Why do Ruby setters need "self." qualification within the class?
(3 answers)
Closed 5 years ago.
Excuse me for the noob question.Please explain me outputs of the below ruby programme for implementing attr_accessor.
class SimpleService
attr_accessor :name
def initialize(name)
#name = name
end
def process
if false # some condition met
name = 'Akshay'
end
name
end
end
When I execute this class
SimpleService.new('John Doe').process
=> nil
Why is the result nil?
when I use self explicitly to name
def process
if false # some condition met
self.name = 'Akshay'
end
name
end
Now the output is
SimpleService.new('John Doe').process
=> "John Doe"
why is the result now "John Doe"?
I am a beginner in ruby.
Thanks in advance!
The thing is when you call name = you implicitly declare new local variable. Try this:
def process
name = 'Akshay'
puts local_variables.inspect
end
Why is it that way is a complicated question, discussed many times there and here. The setter always requires in explicit receiver. Period.
Once you have the line name = 'Akshay' inside a method, you introduce a new local variable and this method’s scope gets extended with new local variable name, despite how is was declared. It’s basically done by ruby parser.
And local variables take precedence over instance methods. That is why what is returned in the last line is a local variable. That was apparently not set, due to falsey condition above. Hence nil.
Here're the codes from about_classes.rb. I'm not very sure about why the answers are [ ] and [:#name]
class Dog2
def set_name(a_name)
#name = a_name
end
def test_instance_variables_can_be_set_by_assigning_to_them
fido = Dog2.new
assert_equal [ ], fido.instance_variables
#In this case, fido doesn't have any instance_variables,
because it is assigned to a new "Dog2" Hash/class which has none methods?
fido.set_name("Fido")
assert_equal [:#name], fido.instance_variables
#In this case, fido has an instance_variable,
because it uses the set_name methods inherited from "Dog2" classes?
end
assert_raise(SyntaxError) do
eval "fido.#name"
# NOTE: Using eval because the above line is a syntax error.
end
#So Eval here means that if the above returns "fido.#name", give it a SyntaxError?
I added some comments under those 2 cases, see if I understand it correctly.
When the first assert_equal is called, the Dog2 instance (fido) has no instance variables, because none were defined in the initializer or in any other way.
When set_name is called the #name instance variable gets set, so by the time the second assert_equal is called the fido instance does have an instance variable, one, #name, so the fido.instance_variables method returns an array with that one symbol.
Update
In response to the questions you pose in the comments in your code sample:
No, your first two comments are not accurate. It's not because it has no methods, it's because it has no instance variables. The fido instance does have a method, the set_name method.
Your second comment is not accurate because there's no inheritance going on here, fido is an instance of Dog2 and so once the set_name method has been called it has an instance variable, because #name is initialized in that set_name method.
Your final comment/question about the eval is just because if the authors had actually written just fido.#name then the code itself would have failed to run, they wanted it to run but display that if you'd written fido.#name in your code then Ruby would have exited and refused to run your code because of that syntax error.
Another Update
After another question from the OP I wanted to just add, although #name exists inside the set_name method, the point really of this example is to show that in Ruby until set_name is called that #name variable doesn't exist yet. In other languages you would define all the instance variables up front, so they'd always exist for any instantiated object of that class.
Ruby is a much more dynamic language and so until that #name = a_name line of code is actually executed, the #name variable doesn't exist, so is not returned in fido.instance_variables
This would also be true even if that method is called, but that #name = a_name line isn't executed, so, eg.
class Dog2
def set_name a_name
if false
#name = a_name
end
end
end
fido = Dog2.new
fido.set_name "Fido"
fido.instance_variables # => []
Hope that helps.
I'm trying out some of the exercises over on exercism. Each exercise comes with a set of pre-written tests that we need to make pass. The problem I'm currently working on asks us to write a Robot class. Each robot should come with a method called name that sets a default name. I'm doing that like this:
class Robot
attr_accessor :name
def self.name
#name = DateTime.now.strftime("%y%^b%k%M%S")
#name
end
end
The problem is that the first test (I'm skipping over the rest for now) keeps failing. Here's the test:
def test_has_name
# rubocop:disable Lint/AmbiguousRegexpLiteral
assert_match /^[A-Z]{2}\d{3}$/, Robot.new.name
# rubocop:enable Lint/AmbiguousRegexpLiteral
end
I'm not using the rubocop gem so I've left the commented lines as is. The test fails with this error:
1) Failure:
RobotTest#test_has_name [robot-name/robot_name_test.rb:7]:
Expected /^[A-Z]{2}\d{3}$/ to match nil.
I suppose the biggest problem is that I don't really understand the error and I don't know if I need to install rubocop and uncomment those lines above or of my code is just plain wrong. Any help at all with this would be much appreciated.
There is number of issues with your code.
First, you define accessor :name, but you don't have initialize method.
What you have defined, is a class method name, which would work if you call Robot.name.
To make your class work, it should look like this:
class Robot
def initialize
#name = DateTime.now.strftime("%y%^b%k%M%S")
end
end
Robot.new.name
#=> "15MAY150035"
Or You would do
class Robot
def name
DateTime.now.strftime("%y%^b%k%M%S")
end
end
Robot.new.name
#=> "15MAY150649"
Also, in Ruby last line in method is already what would be returned, so you don't have to write #name here:
def self.name
#name = DateTime.now.strftime("%y%^b%k%M%S")
#name # useless
end
Furthermore, variable #name is useless here, since method name either way will return DateTime object.
You have to make sure you understand what is what in Ruby.
Your code defines name to be a method on the Robot class (that's what the self indicates; and #name here will be setting a member variable on the class object). You need to provide a way for each instance to have a name.
As a secondary concern, your method changes the name everytime name is called. You probably want to set it once (when the robot is initialized, probably!), then return that each time.
Your method is a class method. Which means that Robot.name will give you the name, while Robot.new.name is nil.
You want to use:
def name
#code
end
Instead of self.name.
You can also set name in the initialize method:
def initialize
#name = 'RB123'
end