Using a one-line unless or if statement in Ruby - ruby

I am new to Ruby. I am looking for an elegant one line solution to this line of code:
puts "Year: ".colorize(:light_blue) + "#{instrument.year}"
I would like it to print "N/A" if instrument.year is "" (an empty string). I imagine an unless, if or || might work, but so for nothing has quite worked for me. Still learning! I have several fields that need this treatment, so I'd like to avoid an if/else/end statement.
Thanks in advance!

I'm not sure what exactly you want but what I understood is you want to display instrument.year or N/A if instrument.year is an empty string.
So what I would do is use a ternary:
puts "Year: ".colorize(:light_blue) + "#{instrument.year.empty? ? 'N/A' : instrument.year}"

Related

Datatype conversion error in Ruby for-loop

I'm looking for some help understanding why I get an error (no implicit conversion of nil into String) when attempting to use a for-loop to search through an array of letters (and add them to a resulting string, which seems to be the real problem), but not when I use a while-loop or 'each' for the same purposes. I've looked through a lot of documentation, but haven't been able to find an answer as to why this is happening. I understand that I could just use the "each" method and call it a day, but I'd prefer to comprehend the cause as well as the effect (and hopefully avoid this problem in the future).
The following method works as desired: printing "result" which is the original string, only with "!" in place of any vowels.
s="helloHELLO"
result=""
vowels=["a","e","i","o","u","A","E","I","O","U"]
string_array=s.split("")
string_array.each do |i|
if vowels.include?(i)
result+="!"
else
result+=i
end
end
puts result
However, my initial attempt (posted below) raises the error mentioned above: "no implicit conversion of nil into String" citing lines 5 and 9.
s="helloHELLO"
result=""
vowels=["a","e","i","o","u","A","E","I","O","U"]
string_array=s.split("")
for i in 0..string_array.length
if vowels.include?(string_array[i])
result+= "!"
else
result+=string_array[i]
end
end
puts result
Through experimentation, I managed to get it working; and I determined--through printing to screen rather than storing in "result"--that the problem occurs during concatenation of the target letter to the string "result". But why is "string_array[i]" (line #9) seen as NIL rather than as a String? I feel like I'm missing something very obvious.
If it matters: This is just a kata on CodeWars that lead me to a fundamental question about data types and the mechanics of the for..in loop. This seemed very relevant, but not 100% on the mark for my question: "for" vs "each" in Ruby.
Thanks in advance for the help.
EDIT:
Okay, I think I figured it out. I'd still love some answers though, to confirm, clarify, or downright refute.
I realized that if I wanted to use the for-loop, I should use the array itself as the "range" rather than "0..array.length", like so:
s="helloHELLO"
result=""
vowels=["a","e","i","o","u","A","E","I","O","U"]
string_array=s.split("")
for i in string_array
if vowels.include?(i)
result+= "!"
else
result+=i
end
end
puts result
So, is it that since the "each" method variable (in this case, "i") doesn't exist outside the scope of the main block, its datatype become nil after evaluating whether it's included in the 'vowels' array?
You got beaten by the classical error when iterating an array starting with index 0, instead of length as end position it should be length-1.
But it seems like you come from some other programming language, your code is not Rubyesque, a 'For' for example is seldom used.
Ruby is a higher language than most others, it has many solutions build in, we call it 'sugared' because Ruby is meant to make us programmers happy. What you try to achieve can be done in just one line.
"helloHELLO".scan(/[aeoui]/i).count
Some explanation: the literal array "hello HELLO" is a String, meaning an object of the String class and as such has a lot of methods you can use, like scan, which scans the string for the regular expression /[aeoui]/ which means any of the characters enclosed in the [], the i at the end makes it case insentitive so you don't have to add AEOUI. The scan returns an array with the matching characters, an object of the Array class has the method count, which gives us the ... Yeah once you get the drift it's easy, you can string together methods which act upon each other.
Your for loop:
for i in 0..string_array.length
loops from 0 to 10.
But string[10] #=> nil because there is no element at index 10. And then on line 9 you try to add nil to result
result = result + string_array[i] #expanded
You can't add nil to a string like this, you have to convert nil to a string explicitly thus the error. The best way to fix this issue is to change your for loop to:
for i in 0..string_array.length-1
Then your loop will finish at the last element, string[9].

Ruby if regex help needed

I am attempting to modify someone's script.
I have managed to modify everything but there is one problem left I am unable to solve:
disp_status("\tAnswer: #{convert_err(results["status"])}")
This produces various outputs as it is run, however, when the output is "ERROR", I want it to do an action. I am not sure how to limit it to "Error", as it appears to always run the method no matter the output.
What I tried was:
if #{convert_err(results["status"])} =~ /ERROR/
and a lot of similar iterations without any luck. Can anyone help?
In ruby interpolation doesn't work without double-quotes. But using interpolation here is an over kill, so just change the line in question from:
if #{convert_err(results["status"])} =~ /ERROR/
To
if convert_err(results["status"]) =~ /ERROR/
And should it should work! :-)
I think the .include? method might be helpful. You could do:
if "#{convert_err(results["status"])}".include?("ERROR")
Furthermore if convert_err returns a string you could just call:
if convert_err(results["status"]).include?("ERROR")
And another option would be to call .to_s which will convert the result of convert_err to a string. So that would look like:
if convert_err(results["status"]).to_s.include?("ERROR")
For further reference read: http://www.ruby-doc.org/core-2.1.4/String.html#method-i-include-3F

Ruby idiom for do_two_simple_things if something_is_true

For instance, this takes 4 lines which is too much space for such a simple operation:
if something_is_true
puts 'error'
return
end
this one is a one-liner but looks awkward.
if something_is_true; puts 'error'; return; end
Can we do something like
# it would be great if this would work because it is short and readable
puts 'error' and return if something_is_true
I'm not sure why you think space is at such a premium that your original code is "too much." Give your code room to breathe, and let it take the space it needs. Getting too tricky in order to "save space" is a false economy. The most important thing is that your code be readable and understandable. Your original code looks great to me.
I agree with #NedBatchelder that your original code is probably best. Others have pointed out that in your particular example, you can use return puts 'error'.
Still, for the sake of learning, you can group multiple statements with parentheses, and therefore use a small number of statements in places where you could otherwise use only one. You said:
# it would be great if this would work because it is short and readable
puts 'error' and return if something_is_true
You can do this with:
(puts 'error'; return) if something_is_true
This is kind of awful but I think it will work because puts returns nil:
puts 'error' || return if something_else
In this specific case, return without a value will return nil; as this happens to also be the return value of puts, you can get the same effect with just:
return puts "error" if something_else
Someday you will probably care less about how many cycles you can spend in a single line of code. I would use the if-end block because it is simple, clear, and... you know, that's what it's there for.
I'd recommend never to use ; to join statements, it tends to be unreadable. But still there are other approaches, two ideas: first one, join error(msg, return_value = nil) and return:
return(error("Message")) if something_is_true
return(error("Message", value_to_be_returned)) if something_is_true
Second one, in Ruby it's idiomatic to signal problems using exceptions, so you can write this perfectly idiomatic one-liner:
raise MyException.new("human explanation of the error") if condition
The same idea used in an assignment:
link = doc.at_css(".content a.link") or raise MyException.new("msg")

Check if file contains string

So I found this question on here, but I'm having an issue with the output and how to handle it with an if statement. This is what I have, but it's always saying that it's true even if the word monitor does not exist in the file
if File.readlines("testfile.txt").grep(/monitor/)
do something
end
Should it be something like == "nil"? I'm quite new to ruby and not sure of what the outputs would be.
I would use:
if File.readlines("testfile.txt").grep(/monitor/).any?
or
if File.readlines("testfile.txt").any?{ |l| l['monitor'] }
Using readlines has scalability issues though as it reads the entire file into an array. Instead, using foreach will accomplish the same thing without the scalability problem:
if File.foreach("testfile.txt").grep(/monitor/).any?
or
if File.foreach("testfile.txt").any?{ |l| l['monitor'] }
See "Why is "slurping" a file not a good practice?" for more information about the scalability issues.
Enumerable#grep does not return a boolean; it returns an array (how would you have access to the matches without passing a block otherwise?).
If no matches are found it returns an empty array, and [] evaluates to true. You'll need to check the size of the array in the if statement, i.e.:
if File.readlines("testfile.txt").grep(/monitor/).size > 0
# do something
end
The documentation should be your first resource for questions like this.
Grep will give you an array of all found 'monitor's. But you don't want an array, you want a boolean: is there any 'monitor' string in this file?
This one reads as little of the file as needed:
if File.open('test.txt').lines.any?{|line| line.include?('monitor')}
p 'do something'
end
readlines reads the whole file, lines returns an enumerator which does it line by line.
update
#lines are deprecated, Use #each_line instead
if File.open('test.txt').each_line.any?{|line| line.include?('monitor')}
p 'do something'
end
if anyone is looking for a solution to display last line of a file where that string occurs just do
File.readlines('dir/testfile.txt').select{|l| l.match /monitor/}.last
example
file:
monitor 1
monitor 2
something else
you'll get
monitor 2
I generally skip ruby for the command-line utilities as they tend to be faster.
`grep "monitor" "testfile.txt" > /dev/null`
$?.success #=> true if zero exit status, false otherwise.

How do I suppress string interpolation issues in ruby

I have the following code:
address = "#{(article/"div.address").inner_html.strip_html.squish}"
(using Hpricot)
And in some instances...
address = "#{(article/"div.address").inner_html.strip_html.squish}"
...is nil
I would like the script to keep chugging along, possibly replacing nil with an empty string.
Any tips?
Edit
I have traced the problem better to:
puts "#{link[0].to_s}\n" unless link.empty?
(.backtrace points to this particular line in the source.)
So the revised question is: why doesn't that line just not get parsed? Why does it throw an error? I thought that using unless will just skip it...
Use :to_s method:
nil.to_s == ''
Is try what you are looking for? http://api.rubyonrails.org/classes/Object.html#method-i-try
Thank you all for the support and helpful tips, in the end it was a matter of using the proper method, I ended up solving my problem by using:
unless uri.query.nil?
But I did come to make use of both .to_s and try in my source, and I wish I could pick two answers as the right one!

Resources