How can I solve undefined method `[]' on Ruby? - ruby

I'm trying to get an if statement for users who put incorrect data.
Here's my code:
class Breweries::CLI
def start
puts "Hello!"
puts "---------------------------"
puts "Please enter your location:"
input = gets.strip.downcase
#data = Breweries::API.get_breweries(input)
#objects = Breweries::HoppyCode.all
if input.length < 1
puts "Sorry!!"
puts "```````"
start
else
display_info
end
end
def display_info
puts "You'll love the following spots!"
puts "********************************"
#objects.each.with_index(1) {|brewery, index| puts "#{index}. #{brewery.name}"}
puts "Please make a selection by index number for more information:"
input = gets.strip.downcase
if(input.to_i > 0)
#brewery = #objects[input.to_i - 1]
puts "name: #{#brewery.name}"
puts "street: #{#brewery.street}"
puts "city: #{#brewery.city}"
puts "phone: #{#brewery.phone}"
puts "website_url: #{#brewery.website_url}"
display_info
elsif (input == "quit")
quit
elsif (input == "menu")
start
end
end
def quit
puts "Goodbye. Drink responsibly and enjoy."
end
end
When I put something that would generate an error, it returns the following:
Please enter your location: nvifpejvf80ejvip
Traceback (most recent call last):
2: from bin/breweriesCLI:6:in `<main>'
1: from /home/munificent-format-5297/Development/breweries/lib/breweries/cli.rb:8:in `start' /home/munificent-format-5297/Development/breweries/lib/breweries/api.rb:6:in `get_breweries': undefined method `[]' for nil:NilClass (NoMethodError)
How can I solve the undefined method '[]' error? Here's the API code in case that's necessary.
class Breweries::API
def self.get_breweries(input)
#breweries_hash = HTTParty.get("https://api.openbrewerydb.org/breweries?by_city=#{input}")
breweries_obj = {
name: #breweries_hash[1]["name"],
street: #breweries_hash[3]["street"],
city: #breweries_hash[4]["city"],
phone: #breweries_hash[10]["phone"],
website_url: #breweries_hash[11]["website_url"]
}
Breweries::HoppyCode.new(breweries_obj)
end
end

When the input is invalid, the call to
#breweries_hash = HTTParty.get("...")
returns not the object you expect (I’d suggest it returns an empty hash.) That makes it impossible to get to details in the following lines. Depending on how are you to handle it, you might decide to e. g. early return from this function, or raise, or do something else.
To approach this, start with debugging the issue, like this:
#breweries_hash = HTTParty.get("...")
puts #breweries_hash.inspect
...
That way you’ll see what gets returned and get the ideas of how to handle it.
If I am right, and what is returned is an empty hash, you might want to early return from this function.
#breweries_hash = HTTParty.get("...")
return if #breweries_hash.empty?
...

Identifying the Problem
There are lots of ways to solve for the nil problem, but at a quick glance it seems like part of the problem here is that you're somehow expecting input to return a valid Hash object from your API call, but an empty String or an instance of FalseClass may not do that. Consider the following:
input = gets.strip.downcase # <RETURN> here gets an empty string
input #=> ""
input.to_i > 0 #=> false
Then consider that some downstream of Breweries::API.get_breweries is expecting to operate on a Hash object instead if an instance of NilClass. In this case, that looks like #breweries_hash[1]["name"] and other operations on #breweries_hash.
Some Options
Without knowing more about your code, I don't want to be prescriptive here. But in general, you can do one or more of the following:
Coerce arguments into the expected class in the method call, the method signature, or the method body. For example, for Array objects:
# coerce a String to an Array, raising an exception if it can't
input = ""
Array(input)
#=> [""]
# coerce some Array to a Hash
array = [:name, "foo", :street, "bar"]
Array(array.each_slice 2).to_h
#=> {:name=>"foo", :street=>"bar"}
Explicitly check that you have an Hash object:
fail "#breweries is not a Hash" unless #breweries.is_a? Hash
Raise an exception rather than return 0 if input isn't actually a valid Integer representation in the first place:
input = Integer(gets.strip.downcase)
Check if your Hash or Array object responds to the relevant method calls, and raise a more helpful exception message:
raise sprintf("#brewery: %s", #brewery.class) unless #brewery.respond_to? :[]
There are other things you might do as well. Broadly speaking, you need to adjust your code to check the return value of your call to ensure it's not nil, then branch/raise/rescue appropriately depending on whether or not you ever expect nils as a valid return value from Breweries::API.get_breweries.
A Note on Using Exceptions for Non-Exceptional Circumstances
As a rule of thumb, you should only raise exceptions for truly unexpected circumstances, or when the program should halt because some condition can't (or shouldn't) be handled within the program during runtime. Which is best in your particular use case is really a design decision, and outside the scope of the original question. However, you might want to read Avdi Grimm's Exceptional Ruby for a deeper explanation of when exceptions might better than branching or handlers (or vice versa), but the choice in your code is a little left of center of the problem you're actually dealing with right now.

Related

Does it matter which way a string method is used?

Codeacademy teaches that you can chain multiple methods together as such:
user_input.method1.method2.method3
However, in a later lesson they display some methods like this:
user_input = gets.chomp
user_input.downcase!
I combined them:
user_input = gets.chomp.downcase!
When I use it this way:
user_input = gets.chomp.downcase!
if user_input.include? "s"
...
I receive an error "undefined method `include?'". If I change it to the following, it works fine:
user_input = gets.chomp
user_input.downcase!
if user_input.include? "s"
...
I'm at a loss. I'm concerned whether or not this is a quirk with their console or if this is just how I should be doing it in Ruby. If someone could tell me which way is right, I'd appreciate it. If both are right, that's OK too.
Firstly, in case you do not yet fully understand, assignment of values to variables are done through =, and that you could inspect what variable type it is by appending .class to anything.
Consider the following:
name = 'John'
puts name
# => John
puts name.class
# => String
Now, secondly, it should be noted that the return values of ALL methods are ALL different. But all of them can be identified into two types:
Methods that:
return self
return anything other than self
Example for 1.
-- methods that return self, which you could say methods that return the same type of object which in our specific case, a String
name = 'John'
puts name
# => 'John'
puts name.class
# => String
downcased_name = name.downcase
puts downcased_name
# => john
puts downcased_name.class
# => String
deleted_downcased_name = downcased_name.delete('h')
puts deleted_downcased_name
# => jon
puts deleted_downcased_name.class
# => String
# All of the above can be just simplified into:
deleted_downcased_name2 = 'John'.downcase.delete('h')
puts deleted_downcased_name2
# => jon
puts deleted_downcased_name2.class
# => String
Notice that deleted_downcased_name and deleted_downcased_name2 are the same, because you could treat the chained methods as if you are chaining the return values which is 'John' -> 'john' -> 'jon'.
Example for 2
-- methods that return anything but self, which you could say methods that return a different type.
In our specific case, String's downcase! returns either a String or NilClass (reference here)
returning String if the string changes, or
returning nil if string is already downcased to begin with (no change).
or another String's method: start_with? (reference here)
returning true or false
This is where chaining of methods will not work (raises an error), when you try to use a String method as a chain to nil value.
Consider the following
name = 'mary'
puts name
# => 'mary'
puts name.class
# => String
downcased_name = name.downcase!
puts downcased_name
# => nil
puts downcased_name.class
# => NilClass
downcased_name.delete('h')
# => This will raise the following error
# NoMethodError: undefined method `delete' for nil:NilClass
The error above is because downcased_name is a type of NilClass where you are expecting it to be a type of String. Therefore you cannot chain any string method on it anymore. You can only chain String methods on a String type of value. Similarly, you can only chain Number methods on a Number type of value.
Whenever in doubt, you could always check the documentation to check what a method does, and what its return value and type.
The problem you are encountering is with the bang method downcase!.
This is basically saying "mutate the original string so that it is downcase".
The important part is that this returns nil. As such you are actually calling include? on nil.
If you use the non bang method downcase instead, it is saying "downcase the previously chained thing but do not mutate the original". The key difference is that it returns the result rather than nil.
Here is an example:
str = "ABCD"
str.downcase!
=> nil
str
=> "abcd"
str = "ABCD"
str.downcase
=> "abcd"
str
=> "ABCD" # Note str is still unchanged unless you set str = str.downcase
Welcome to Ruby! While your apprenticeship at Codeacademy may be limited, you'll continue to refer to language API documentation throughout your career. API documentation is a description of what the language (or a library) does for you. In this case, you're using downcase! which, as one commenter points out, does not always return a String. When it takes no action, it returns nil. Nil is an Object in Ruby (like everything else), but the 'include?' method isn't defined for nil, which explains your error. (It's one of the most common errors in Ruby, learn its meaning.)
So, in fact, what's breaking here isn't your method chain. It's that one of the intermediate methods isn't returning a value of the type you expect (nil instead of some kind of String).
Chaining non destructive methods like:
string.chomp.downcase...
has the advantage that the code is concise, but is not efficient if you are not interested in the original state of the object, and just want the final result because it creates intermediate objects during the chain.
On the other hand, applying destructive methods sequentially to the same object:
string.chomp!
string.downcase!
...
is more efficient if you do not need to keep the original state of the object, but is not concise.
Combining methods that may return an object of a different class (particularly nil) as:
string = gets.chomp!.downcase!...
is wrong because the result can become nil at some point in the chain.
Applying a potentially nil-returning method at only the last position as you did:
string = gets.chomp.downcase!
is still not useful if you expect string to always be a string, and can easily lead to an error as you did.
If you want to chain these methods in you example, perhaps you can do this:
user_input = gets.tap(&:chomp!).tap(&:downcase!)
if user_input.include?("s")
...

What's the difference between `puts` and `return` result?

When I run the following code:
class GermanShepard
attr_accessor :name
def bark
puts "Loud Bark"
end
end
max = GermanShepard.new
max.name = "Max"
puts "#{max.name} goes #{max.bark}"
the result is:
Loud Bark
Max goes
When I change puts to return at the GermanShepard. It gives:
Max goes Loud Bark
I don't understand the difference between the two command in the class.
Expression
In most languages, return in a method means giving a value to the caller.
In Ruby, everything is an expression. In an essence, the last line of a method is automatically called with return. As an example, the following methods are equivalent:
def bark_a
'Woof!'
end
def bark_b
return 'Woof!'
end
However, some methods may not be returning anything.
def bark_c
end
In this case, ruby is actually returning a nil object. An example would be the
puts method. The puts method simply displays whatever you've given it.
So in your example,
def bark
puts "Loud Bark"
end
is actually doing 2 things.
It's calling the puts method (displaying Loud Bark to the terminal screen)
then it's giving a nil value back to the method caller.
You can try running puts nil and see what's printed out!
"#{}"
Calling #{} in ruby is called interpolation, which is basically putting the variables together in their closest string representation value. The following statements are roughly equivalent:
puts "One plus one equals #{1 + 1}"
puts "One plus one equals " + (1 + 1).to_s
Your example
With all the information above, we can now go through your example step-by-step.
The line puts "#{max.name} goes #{max.bark}" can be separated into a few steps
Calls the name method
The value returned is converted into the closest string representation (In this case we don't need to do anything since name is already a string)
The value is then saved to a temporary variable
At this point, our temporary variable is Max goes.
Calls the bark method
The line puts "Loud Bark" gets executed since we're calling it in the bark method.
Terminal(console) displays "Loud Bark"
Since the puts method returns a nil value, the bark method is also going to return a nil value because it's the last line of the bark method. (Read "Expression" above)
nil is then converted to the closest string representation (which is "")
It is then saved to a temporary variable
The temporary variable is now Max goes
Now, the temporary variable is passed into the puts method, and the terminal(console) displays "Max goes "
Program finishes
Therefore, your example prints out
Loud Bark
Max goes
However, if we change the puts inside the bark method to return,
Step 6 will not happen.
Instead of returning a nil value in step 7, it will return "Load bark"
Step 8 will not happen since "Loud bark" already a string value
and the temporary value after step 9 will become Max goes Loud bark
Hope this helps!
Ray's answer is probably correct, but since it is too verbose (and I didn't even feel like reading it), I will post a concise answer.
There are two things to have in mind.
All arguments passed to a method are evaluated prior to the method call.
All methods in Ruby have a return value. In addition, some methods have side effect, an example of which is to output on the terminal. In any case, returning a value is done after a method has done all its side effects.
With your code as is, the argument of puts in
puts "#{max.name} goes #{max.bark}"
is first evaluated. In order to do so, max.bark (among other things like max.name) would have to be evaluated in advance. The method bark prints "Loud Bark", then returns nil. So the argument mentioned above becomes "#{"Max"} goes #{nil}", and eventually to "Max goes ". That is printed after "Loud Bark".
When you change the puts in the method to return, the method bark will have no side effect, and returns "Loud Bark"; that is the function of return (and by the way, return is not a method but is a keyword). So the argument mentioned above becomes "#{"Max"} goes #{"Loud Bark"}", and eventually to "Max goes Loud Bark". That would be the only thing printed.
puts(string) in ruby writes the string value into $stdout. For example if you run ruby console in terminal, string will be written into your terminal. At the same time every method in ruby returns something and method puts returns nil. So, when we want to interpolate string like "Hello #{some_method}" it will interpolate a returning value of some_method, in case of puts it will interpolate nil value.
Loud Bark # output of max.bark method
Max goes # output of last puts
puts "Loud Bark" # => returns nil
return "Loud Bark" # => returns "Loud Bark"

Checking the type of a method parameter

I am not sure if an object I pass to a method is of the proper type. I might pass a string to a function that can only handle integers. What about some kind of runtime ensurance? I couldn't see a better option than:
def someFixNumMangler(input)
raise "wrong type: integer required" unless input.class == FixNum
other_stuff
end
Any better alternatives?
Use the Kernel#Integer method to convert the input before using it. It will raise an ArgumentError when the input could not be converted to an integer in any reasonable fashion.
def my_method(number)
number = Integer(number)
# do something with number, which is now guaranteed to be an integer
end
I recommend Avdi Grimm's new book Confident Ruby for more insight into this.
If you really need to do type checks, then yes, you only have runtime checking. Code in the question is ok. You can also use .is_a?.
def someFixNumMangler(input)
raise "wrong type: integer required" unless input.is_a?(FixNum)
other_stuff
end
The checks may take different forms. If you expect, say, a string and you call string methods on it (upcase, gsub, etc), the code will blow up if anything other than string is passed. Unless, of course, you pass an object that is not a string, but behaves just like one (has the same methods that you call). This is the essence of duck typing.
What if your method looked like this?
def someFixNumMangler(input)
input = input.to_i
puts "got this: #{input} of #{input.class}"
end
someFixNumMangler(10)
someFixNumMangler('42')
someFixNumMangler(File)
# >> got this: 10 of Fixnum
# >> got this: 42 of Fixnum
# ~> -:2:in `someFixNumMangler': undefined method `to_i' for File:Class (NoMethodError)
# ~> from -:9:in `<main>'
As long as an argument responds to #to_i, you don't actually care what its type is.

method undefined?

I am new to ruby. I tried to do a simple method(with parameter) call.
class MeowEncoder
def method(c)
puts c
end
end
print "please enter the thing you want"
s = gets.chomp()
MeowEncoder.method(s)
It is only passing parameter and prints it out. But the terminal keep giving me errors like
:MeowEncoder.rb:9: undefined method `toBinary' for MeowEncoder:Class (NoMethodError)
what is going on here?
I made some enhancement.
class MeowEncoder
def encode(n)
toBianry(?n)
puts ""
end
def toBinary(n)
if n < 2
print n
else
toBinary(n / 2)
print n % 2
end
end
end
o = MeowEncoder.new
print "please enter the thing you want: "
s = gets.chomp()
s.each_char{|c| o.encode(c)} #this doesn't work
o.toBinary(212) # this works
I made some enhancement here. I try to convert a char to its ASCII value then to its binary form. I can made the single toBinary works. But the Encode method also gave me same error. What happened?
You defined an instance method, but you're trying to call it on a class object. Try this:
MeowEncoder.new.method(s)
Also, method is a bad name for a method. It will cause a name clash.
To expand on Sergio's answer, if you actually wanted the method defined on the class, there are several ways to accomplish that, but the most straightforward is to prepend the method definition with self like so:
def self.method(c)
puts c
end
That will allow you to invoke the method the way you are currently.
The reason this works is, in the context of defining the method, self is set to the MeowEncoder class. It's equivalent to saying:
def MeowEncoder.method(c)
puts c
end
This is actually another valid way to declare class methods, but using self is better practice, as refactoring becomes easier if you ever change the name of your class.
Instead of each_char use each_byte and no need of encode method.
s.each_byte{|c| o.toBinary(c)}
Book (title, author)
Author (pseudonym, first_name, last_name)
Book_catalog => collection of books
methods
add_book(book)
remove_book(book)
​borrow_book(borrower, book) => voeg boek toe aan borrower.books_borrowed
return_book(borrower, book) => verwijder boek uit borrower.books_borrowed
book_available?(book)
search(title) => geeft gevonden book-object terug (anders nil)
Book_borrowing
book (read-only), date_of_borrowing (read-only), date_of_return (read-only)
borrow_book(book_to_borrow) : #date_of_borrowing = systeem-datum+uur
return_book(book_to_return) : #date_of_return = systeem-datum+uur
Borrower
member_nbr, first_name, last_name, books_borrowed = collection of Book_borrowing
has_book_by_title(title) => geeft true of false terug
has_book(book) => geeft true of false terug
Person(first_name, last_name)

Adding conversion method to Numeric causes SystemStackError

I'm attempting to add conversion methods to the Numeric class but when I run the following lines of code I get a SystemStackError
puts 5.dollars.in(:euros) # => 6.5
puts 1.dollar.in(:yen)
Here is my Numeric class
class Numeric
##conversion_hash = {:dollar => {:yen => 0.013, :euros => 1.292, :rupees => 0.019}}
def method_missing(method_id)
name = method_id.to_s
if name =~ /^dollar|yen|euros|rupee|$/
self.send(name + 's')
else
super # pass the buck to superclass
end
end
def dollars()
puts "Called Dollars method"
#current_currency = :dollar
return self
end
def in(key)
if ##conversion_hash.has_key?(#current_currency)
puts "Current currency: #{#current_currency}"
conversion_rate = ##conversion_hash[#current_currency]
puts "Current conversion rate: #{conversion_rate}"
if conversion_rate.has_key?(key)
puts "we have that key"
puts"What am I? #{self}"
rate = conversion_rate[key]
puts "Rate to multiply by #{rate}"
return self.to_int * conversion_rate[key]
end
end
end
end
Any help is greatly appreciated.
You're getting infinite recursion in your method_missing because your regex isn't quite right. Try changing the line:
if name =~ /^dollar|yen|euros|rupee|$/
to:
if name =~ /^dollar|yen|euros|rupee$/
That extra | is causing anything to match the regex, so any other method is recursing with a continually extending suffix of s.
In this case it looks like puts seems to be trying to call to_ary when it's trying to determine the type its argument. I'm not exactly sure why it's not just using respond_to? though - it's deep in the C internals so I don't really know what's happening.
Your solution overcomplicated.
- You don't need to modify method_missing. Armando version works fine.
- You should simple dollar definition to hash plus
- find the way to call method_missing again from method in(this is your homework).
Working solution have only 1 line of code + 2 lines def surronding.

Resources