I have a Author class and need to make some validations before initializing
class Author
include Validations
attr_accessor :name, :biography
def initialize(name, biography = nil)
validate_author
#name = name
#biography = biography
end
def to_s
"#{name}\n#{biography}"
end
end
I use module for this
module Validations
def validate_author
raise ::StandardError, 'Name is required' if name.strip.empty?
end
end
And i get this error
extentions/validations.rb:8:in `validate_author': undefined method `strip' for nil:NilClass (NoMethodError)
It's ruby application
The error gives you the clue, "for nil:NilClass (NoMethodError)", you can't apply the method .split to a Nil, what's happening that you are not passing anything so #name is nil by default, i don't know the rest of the code, but one think that you can do is use the "||" conditional writing something like.
raise ::StandardError, 'Name is required' if name.strip.empty? || name == nil
Name is not yet assigned
class Author
include Validations
attr_accessor :name, :biography
def initialize(name, biography = nil)
#name = name
#biography = biography
validate_author
end
def to_s
"#{name}\n#{biography}"
end
end
Or you can pass name to validate_author(name)
I think because the validate_author runs before setting the name instance variable. So if you move validate_author after setting instance variables, then it should work. However undefined method strip' for nil:NilClass (NoMethodError)` will still be raised as long name is nil.
Related
I have a class called RubyCsvRow, which holds a row of a CSV file, in a hash. I am using method_missing to allow any column to be used as a function to return the value of the row at that column. However, I get a method_missing error when I run I attempt to use it.
I wasn't sure exactly what was happening, so I replaced the call to one with a call to class.
m = RubyCsv.new
m.each{|row| puts row.class}
I edited the method missing in RubyCsvRow so that I could see what happens when it prints and see the name of the missing method:
def self.method_missing(name, *args, &block)
puts "Called Method Missing"
puts name.to_s
end
The return only confused me more.
Called Method Missing
to_ary
RubyCsvRow
Called Method Missing
to_ary
RubyCsvRow
It calls method missing. I don't know why it prints name as to_ary, which when I searched I found this, but I am not sure when it is being implicitly converted or why.
I searched around and have looked at these links. The labels where why I thought they didn't fit.
I have my private variable defined as a :attr_accesssor
Mine is a method of a class and I am using it like one
I am calling my method after defining it
I am not sure about this one. I am already converting my symbol to_s, but I had trouble determining if this fit
Why I decided to format my each method in RubyCsv the way I did
class RubyCsvRow
attr_accessor :values
def initialize(start)
#values = start
end
def self.method_missing(name, *args, &block)
if #values.key?(name.to_s)
#values[name.to_s]
else
puts "No Column with name: ", name.to_s, " found!"
end
end
def to_s
self.inspect
end
end
r = RubyCsvRow.new({"one" => "dog", "two" => "cat" })
puts r.one
RubyCsvRow is used in RubyCsv's each, but I get the same error with just this code. I can post the RubyCsv code, but this is the minimum code to reproduce the error.
I get a NoMethodError for one instead of printing dog.
Try to use def method_missing instead of self. You call method on instance not class it self.
If you define method with self you define the method as class not instance. In your code you create new instance of RubyCsvRow Class and you need to define method_missing as instace method.
Modify code here:
class RubyCsvRow
attr_accessor :values
def initialize(start)
#values = start
end
# Define method missing on instance
def method_missing(name, *args, &block)
return #values[name.to_s] if #values[name.to_s]
# return my error message
"No Column with name: #{name} found!"
end
def to_s
inspect
end
end
r = RubyCsvRow.new({ "one" => "dog", "two" => "cat" })
puts r.one
# => dog
puts r.test
# => "No Column with name: test found!"
BTW: If you need the original error message use super in method_missing method
def method_missing(name, *args, &block)
return #values[name.to_s] if #values[name.to_s]
# call original method
super
end
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">
I have an undefined method.
rb:31:in `add_song': undefined method `<<' for nil:NilClass (NoMethodError)
I do understand that #library[artist] gives nil, but I don't understand why and do not know how to fix it. Any advice?
module Promptable
def prompt(message = "What music would you like to add", symbol = ":>")
print message
print symbol
gets.chomp
end
end
class Library
attr_accessor :artist, :song
def initialize
#library = {}
end
def add_artist(artist)
#library[artist] = []
end
def add_song(song)
#library[artist] << song
end
def show
puts #library
end
end
class Artist
attr_accessor :name, :song
def initialize(artist)
#name = artist[:name]
#song = artist[:song]
end
def to_s
"#{name}, #{song}"
end
end
if __FILE__ == $PROGRAM_NAME
include Promptable
include Menu
my_library = Library.new
my_library.add_artist(Artist.new(:name => prompt("What it the artist name ?")))
my_library.add_song(Artist.new(:song => prompt("What is the song name ?")))
my_library.show
end
You're calling add_artist with one instance of Artist and add_song with another. When you look up the artist's list of songs in add_song with #library[artist] you're using a hash key (the second instance of Artist) which is not equivalent to the hash key under which you stored the list (the first instance of Artist), so you're not getting the list back, but nil.
To use two different instances of Artist as equivalent hash keys, you'll need to decide when two instances of Artist should be equal and implement eql? and hash appropriately.
Below is a program that implements a tree.
class Tree
attr_accessor :children, :node_name
def initialize(name_children=[])
#children = children
#node_name = name
end
def visit_all(&block)
visit &block
children.each {|c| c.visit_all &block}
end
def visit(&block)
block.call self
end
end
ruby_tree = Tree.new( "Ruby", [Tree.new("Reia"), Tree.new("MacRuby")] )
puts "Visiting a node"
ruby_tree.visit {|node| puts node.node_name}
puts
puts "visiting entire tree"
ruby_tree.visit_all {|node| puts node.node_name}
When I run this code it errors at this line
ruby_tree = Tree.new( "Ruby", [Tree.new("Reia"), Tree.new("MacRuby")] )
The error I'm receiving is
tree.rb:6:in `initialize': undefined local variable or method `name' for #<Tree:0x007f94020249f8 #children=nil> (NameError)
from tree.rb:19:in `new'
from tree.rb:19:in `<main>'
Any help would be awesome.
You have a typo in your initialize method accepts a single argument named name_children, but from the body of that method it looks like the underscore should have been a comma - name, children.
Here's the problem - line 6 #node_name = name. Where do you define that variable?
In your constructor, (initialize), why aren't you entering the variables children, and name as such instead of a children_name ? What it seems to me is that when you are trying to instantiate the class by creating the objects, when it goes to the initialize constructor, it does not find a name in there.
def initialize(name, children=[])
#children = children
#node_name = name
en
As you have define attr_accessor :name in your Model so there you have to define Children method in you model like as
def self.name
# here will be you code which you want
end
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