Checking Variables In The Constructor - ruby

I am learning ruby and know that in other languages I can ensure that variables are not empty when initializing.
How would I do this in Ruby with this example?
class Person
attr_reader :name
def initialize name
#name = name
end
end

Without setting a default name, and raising an exception if an empty string is sent (not nil) as the argument, then you could raise an exception.
class Person
attr_reader :name
def initialize name
raise "Name can not be an empty String" if name.empty?
#name = name
end
end
Then if you were to send an empty string you would get an error similar to this:
temp.rb:5:in `initialize': Name can not be an empty String (RuntimeError)
from temp.rb:11:in `new'
from temp.rb:11:in `<main>'
shell returned 1
As far as the part of "It makes them enter another name until valid" as you mentioned in another comment... you should let the running code do this.
This is allowing no default, but forcing the user to use something other than an empty string.
class PersonNameError < Exception
end
class Person
attr_reader :name
def initialize name
#name = name
end
end
begin
print "What is your name? "
name = gets.chomp
raise PersonNameError if name.empty?
person = Person.new(name)
rescue PersonNameError
if name.empty?
puts "You can not have a name that is ''"
retry
else
raise
end
end
puts person.name

It all depends on what you mean by empty. If you mean calling Person.new without the name variable set like:
a = Person.new
then you should define a default value for it:
def initialize( name = 'Jeffrey' )
#name = name
end
If you mean a nil value then you can do:
def initialize name
#name = (name.nil?) ? 'Jeffrey' : name
end
then from here I think you know how to go with empty strings ( #name = (name == '') ? 'Jeffrey' : name ) and so on.

I'm sure people who throw some interesting and much better answers but here's mine.
class Person
attr_reader :name
# By setting name = nil, we are letting initialize
# know that the parameter name may or may not be given.
def initialize(name = nil)
if name
#name = name
else
# This is what you do when the user
# did not add a name in the initializer.
#name = "No name"
end
end
end
So if you do something like
person = Person.new
the person will have the name No name.
However, if you do
person = Person.new("Bob")
then person will have the name Bob.
Finally if you set a variable as nil, for example
name = nil
then pass the name into the new method
Person.new(name)
then the person will have the name No name.

Based on what the other answers are doing, the best way IMO is:
def initialize(name = 'Default name')
#name = name
end
After your comment...
Is this Rails? That would change a few things. But you can just nil check the value:
def initialize(name)
if name.nil? || name.empty?
# raise an exception
else
#name = name
end
end
If you're using Rails, you can also just use blank? which will check both nil? and empty?.

Why not accept the user name from within the constructor / initialize method?
def initialize name
begin
while name.empty?
puts "You can not have a name that is ''"
print "What is your name? "
name = gets.chomp
end
#name = name
end
end

Related

How do I pull a name from a class in an Array?

Okay, this is a little hard to explain but I will try (For starters I am only just learning to code so it may be something super simple I'm missing..)
I created a few classes, I put a name in those classes, I put them in an array, I then chose one at random and try to puts the name, and it outputs blank.
Am I doing this all completely wrong? I've been learning ruby for about 3 months now so I'm sure there is a lot I don't know.
class A
attr :name
def set_name
#name = "Aaa"
end
def get_name
return #name
end
end
class B
attr :name
def set_name
#name = "Bbb"
end
def get_name
return #name
end
end
class C
attr :name
def set_name
#name = "Ccc"
end
def get_name
return #name
end
end
name_a = A.new
name_b = B.new
name_c = C.new
which_name = Array.new
which_name[0] = name_a
which_name[1] = name_b
which_name[2] = name_c
roll = rand(max 3)
puts which_name[roll].get_name
I then chose one at random and try to puts the name, and it outputs
blank.
You never called the #set_name method in your code. You can add this:
name_a.set_name
name_b.set_name
name_c.set_name
Also, you probably want to look into #attr_accessor.

Ruby object initialization using instance_eval

Consider the following class:
class Person
attr_accessor :first_name
def initialize(&block)
instance_eval(&block) if block_given?
end
end
When I create an instance of Person as follows:
person = Person.new do
first_name = "Adam"
end
I expected the following:
puts person.first_name
to output "Adam". Instead, it outputs only a blank line: the first_name attribute has ended up with a value of nil.
When I create a person likes this, though:
person = Person.new do
#first_name = "Adam"
end
The first_name attribute is set to the expected value.
The problem is that I want to use the attr_accessor in the initialization block, and not the attributes directly. Can this be done?
Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.
You don’t need to experiment with such an overcomplicated example, the below won’t work as well:
class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end
only this will:
class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end
For your example, you must explicitly call the method on a receiver:
person = Person.new do
self.first_name = "Adam"
end
If the code is run with warnings enabled (that is ruby -w yourprogram.rb)
it responds with : "warning: assigned but unused variable - first_name", with a line-number pointing to first_name = "Adam". So Ruby interprets first_name as a variable, not as a method. As others have said, use an explicit reciever: self.first_name.
Try this:
person = Person.new do |obj|
obj.first_name = "Adam"
end
puts person.first_name
I want to use the attr_accessor in the initialization block, and not the attributes directly
instance_eval undermines encapsulation. It gives the block access to instance variables and private methods.
Consider passing the person instance into the block instead:
class Person
attr_accessor :first_name
def initialize
yield(self) if block_given?
end
end
Usage:
adam = Person.new do |p|
p.first_name = 'Adam'
end
#=> #<Person:0x00007fb46d093bb0 #first_name="Adam">

Trying to figure out what's wrong with this code

I can't come up with a solution.
class Person
def initialize(name)
#name = name
end
def greet(other_name)
#other_name
print "Hi #{#other_name}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
I would appreciate a helping hand.
You have to make a decision:
Do you want to provide both names when initializing the Person:
class Person
def initialize(name, other)
#name = name
#other = other
end
def greet
puts "Hi #{#other}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
#=> Hi Jerry, my name is Tom
Or do you want to provide the second name when calling the greet method:
class Person
def initialize(name)
#name = name
end
def greet(other)
puts "Hi #{other}, my name is #{#name}"
end
end
kit = Person.new("Tom")
kit.greet("Jerry")
#=> Hi Jerry, my name is Tom
In the initialize method, you should take in two parameters, because you are calling it with two. You were declaring the #other_name variable inside the greet function instead of the initialize one.
This will work.
class Person
def initialize(name, other_name)
#name = name
#other_name = other_name
end
def greet
print "Hi #{#other_name}, my name is #{#name}"
end
end
kit = Person.new("Tom", "Jerry")
kit.greet
https://repl.it/C5wn
Consider writing your code like this:
class Person
attr_reader :name
def initialize(name)
#name = name
end
def greet(person)
puts "Hi #{person.name}, my name is #{name}"
end
end
tom = Person.new("Tom")
jer = Person.new("Jerry")
tom.greet(jer) #=> Hi Jerry, my name is Tom.
This way you actually have another person as an object instead of just a name.

Undefined local variable error with simple program that adds items to array

class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names

Set instance variable

If I have a specific value assigned to an instance variable, how do I define a method to reassign the value of this variable? When I run the code below in rspec, I keep getting the original value.
class Test
def name
#name = 'me'
end
def name=(input)
#name = input
end
end
def name
#name = 'me'
end
Every time you call the method above, you set #name to 'me' and return it.
I believe you are looking for the ||= operator
def name
#name ||= 'me' # only set #name to 'me' if it is not already set
end
IMO, the best way to accomplish a default value for #name is:
class Test
attr_accessor :name
def initialize
#name = 'me'
end
end
example:
t = Test.new
t.name
# => "me"
t.name = 'foo'
# => "foo"
t.name
# => "foo"
Because you're setting the #name variable in the getter, where you should only be returning it. Like so:
class Test
def name
#name
end
def name=(input)
#name = input
end
end
Or more simply you should just use the attr_accessor method to declare boilerplate versions of the getter and setter methods. Like so:
class Test
attr_accessor :name
end
The initial value should be set in the constructor method.
class Test
def initialize
#name = 'me'
end
def name
#name
end
def name=(input)
#name = input
end
end
And you could use attr_accessor to make you code simple:
class Test
attr_accessor :name
def initialize
#name = 'me'
end
end

Resources