Why the ruby code will generate an ArgumentError? - ruby

Hi I'm working on Ruby Koans. I was wondering why the ArgumentErrorwould be raised if the Dog6.new is returned in the code down below?
class Dog6
attr_reader :name
def initialize(initial_name)
#name = initial_name
end
end
def test_initialize_provides_initial_values_for_instance_variables
fido = Dog6.new("Fido")
assert_equal "Fido", fido.name
end
def test_args_to_new_must_match_initialize
assert_raise(ArgumentError) do
Dog6.new
end
end
Is it because Dog6.newdoesn't have any arguments? Thank you!!

Yes, your assumption is correct.
Dog6.new implicitly calls Dog6#initialize to initialize the newly created instance (one might think about MyClass#initialize as about the constructor for this class,) which apparently has one required argument. Since no argument was given to the call to Dog6.new, the ArgumentError is being raised.

Just adding that if you want to have a constructor with no arguments (after all - some dogs don't have a name....) you could have a default value for the name parameter.
def initialize(name = nil)
#name = name
end

In the initializer for the Dog6 class, initial_name is defined as a parameter required for object construction. If this class were to be instantiated without this argument, an ArgumentError would be raised because the class definition has a method signature such that Dog6.new is invalid, like you guessed. In this case the error you would see would be:
ArgumentError: wrong number of arguments (0 for 1)
Read more about the ArgumentError exception here.

Related

Why can't a class method have the same name as a non-class method?

I'm learning ruby, and noticed that I cannot create a class method called puts:
class Printer
def initialize(text="")
#text = text
end
def puts
puts #text
end
end
The error is:
`puts': wrong number of arguments (given 1, expected 0)
My expectation was that I could use the code like this:
p = Printer.new("hello")
p.puts
It's not just because puts is a built-in method, though. For instance, this code also gives a syntax error:
def my_puts(text)
puts text
end
class Printer
def initialize(text="")
#text = text
end
def my_puts
my_puts #name
end
end
tldr; within the scope of the instance, the puts resolves to self.puts (which then resolves to the locally defined method, and not Kernel#puts). This method overriding is a form of shadowing.
Ruby has an 'implicit self' which is the basis for this behavior and is also how the bare puts is resolved - it comes from Kernel, which is mixed into every object.
The Kernel module is included by class Object, so its methods [like Kernel#puts] are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form [such as puts, except when they are overridden].
To call the original same-named method here, the super keyword can be used. However, this doesn't work in the case where X#another_method calls X#puts with arguments when it expects to be calling Kernel#puts. To address that case, see Calling method in parent class from subclass methods in Ruby (either use an alias or instance_method on the appropriate type).
class X
def puts
super "hello!"
end
end
X.new.puts
P.S. The second example should trivially fail, as my_puts clearly does not take any parameters, without any confusion of there being another "puts". Also, it's not a syntax error as it occurs at run-time after any language parsing.
To add to the previous answer (https://stackoverflow.com/a/62268877/13708583), one way to solve this is to create an alias of the original puts which you use in your new puts method.
class Printer
alias_method :original_puts, :puts
attr_reader :text
def initialize(text="")
#text = text
end
def puts
original_puts text
end
end
Printer.new("Hello World").puts
You might be confused from other (static) programming languages in which you can overwrite a method by creating different signatures.
For instance, this will only create one puts method in Ruby (in Java you would have two puts methods (disclaimer: not a Java expert).
def puts(value)
end
def puts
end
If you want to have another method with the same name but accepting different parameters, you need to use optional method parameters like this:
def value(value = "default value")
end

(Argument Error) thrown when trying to instantiate a custom object in Ruby

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

Test to check if attribute is assigned fails

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

ArgumentError: wrong number of arguments in Ruby

Trying to solve this problem,
class Person
def initialize(name)
#name=name
end
def greet(other_name)
puts "Hi #{other_name}, my name is #{name}"
end
end
initialize("ak")
greet("aks")
but I am getting the error like:
ArgumentError: wrong number of arguments calling `initialize` (1 for 0)
I don't understand what is asking here, if its just the argument then why the error is like (1 for 0). can someone help me understand this problem.
Look at this code:
class Person
attr_reader :name
def initialize( name )
puts "Initializing Person instance #{object_id}"
#name = name
end
def greet( name )
puts "Hi #{name}, I'm #{name()}"
end
end
When you wrote initialize without explicit receiver:
initialize( "ak" )
It was only a matter of luck that your message was recognized. Look who has responded:
method( :initialize ).owner
#=> BasicObject
BasicObject, the foremother of all Object instances, herself responded to your call, by scolding you about wrong number of arguments, because:
method( :initialize ).arity
#=> 0
Not only this method does not expect any arguments, but also you are not expected to call it at all. In fact, you are not expected to call #initialize on any object by yourself, save for exceptional situations. Class#new method handles calling of Person#initialize method for you:
A = Person.new( 'Abhinay' )
Initializing Person instance -605867998
#=> #<Person:0xb7c66044 #name="Abhinay">
Person.new handled creation of a new instance and automatically called its #initialize method. Also, #initialize method is created private, even if you did not specify it explitcitly. The technical term for such irregular behavior is magic. Person#initialize is magically private:
A.initialize( 'Fred' )
NoMethodError: private method `initialize' called for #<Person:0xb7c66044 #name="Abhinay">
You cannot just reinitialize yourself to 'Fred', you know. All other methods are public unless prescribed otherwise:
A.greet "Arup"
Hi Arup, I'm Abhinay
#=> nil
You need to call the methods on the object (not just call the methods) and initialize is automatically called when creating a new object:
p = Person.new("ak")
p.greet("aks") #=> "Hi aks, my name is ak"
The problem is that to create new object you need to call method new on class, and not initialize on the object.
So code looks like this:
p = Person.new("John")
Please, take a look at code below:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def greet(other_name)
puts "Hi #{other_name}, my name is #{name}"
end
end
person = Person.new("ak")
person.greet("aks")
#=> Hi aks, my name is ak

Constructor overriding

I have a class:
class One
def initialize; end
end
I need to create a new class with my own constructor like this:
class Two < One
def initialize(some)
puts some
super
end
end
Two.new("thing")
but when I launch the code, I got an error:
thing
test.rb:10:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
super in this case (without parentheses) is a special form. It calls the superclass method with the original params.
Instead try calling
super()

Resources