When is the do keyword required in Ruby? - ruby

For example, does the presence or absence of do in the following code affect the behavior of the program at all?
while true do
puts "Hi"
break
end
while true
puts "Hi"
break
end

According to The Ruby Programming Language book Section 5.2.1:
The do keyword in a while or until loop is like the then keyword in an
if statement: it may be omitted altogether as long as a newline (or
semicolon) appears between the loop condition and the loop body.
So, no, it won't change the behavior, it's just optional syntax.

Let's find out!
For a quick answer we can look at Ruby's documentation and find http://www.ruby-doc.org/core-2.1.1/doc/syntax/control_expressions_rdoc.html#label-while+Loop which states that
The do keyword is optional.
Ok so these two examples are equivalent but are they identical? They might do the same thing but maybe there's a reason to favor one over the other. We can look at the AST these examples generate and see if there's any difference.
> gem install ruby_parser
> irb
> require 'ruby_parser'
=> true
> with_do = <<-END
while true do
puts "Hi"
break
end
END
=> "while true do\n puts \"Hi\"\n break\nend\n"
> without_do = <<-END
while true
puts "Hi"
break
end
END
=> "while true\n puts \"Hi\"\n break\nend\n"
> RubyParser.new.parse with_do
=> s(:while, s(:true), s(:block, s(:call, nil, :puts, s(:str, "Hi")), s(:break)), true)
> RubyParser.new.parse without_do
=> s(:while, s(:true), s(:block, s(:call, nil, :puts, s(:str, "Hi")), s(:break)), true)
Nope. These two examples execute the exact same instructions so we can pick whichever style we find easier to read. A common preference is to omit the do when possible: https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do

Related

What condition is my Ruby conditional in?

The following conditional syntax displays the string 'is true' in irb without using puts
irb(main):001:0> if true
irb(main):002:1> 'is true'
irb(main):003:1> else
irb(main):004:1* 'is false'
irb(main):005:1> end
=> "is true"
...yet when I invoke the same syntax in a script and run it from the command line, it gets ignored. Why?
# Odd behaviour:
puts "Why do only two of the three conditionals print?"
# This doesn't put anything to screen:
if true
'is true_1'
else
'is false'
end
puts "Seriously, why? Or better yet: how?"
# But this does:
if true
puts 'is true_2'
else
puts 'is false'
end
# And this works without "puts":
def truthiness
if 1.send(:==, 1)
'is true_3'
else
'is false'
end
end
puts truthiness
puts "Weird."
When I run this as a script, it displays:
"Why do only two of the three conditionals print?
Seriously, why? Or better yet: how?
is true_2
is true_3
Weird."
FWIW, I am following along with Sandi Metz's talk "Nothing is Something"
https://youtu.be/zc9OvLzS9mU
...and listening to this:
https://youtu.be/AULOC--qUOI
Apologies as I am new to Ruby and trying to wrap my head around how it does what it does.
EDIT:
Useful resources:
http://ruby-doc.org/core-2.3.1/Kernel.html#method-i-puts
https://softwareengineering.stackexchange.com/questions/150824/is-the-puts-function-of-ruby-a-method-of-an-object
The IRB output here is showing the return value of the operation, which is not necessarily what is printed to STDOUT (i.e. the terminal) during execution.
Your script is just throwing the return value away, you would have to do this:
val = if true
'is true_1'
else
'is false'
end
puts val

Ruby way to check if a string is not blank?

What's the best way to check if a variable is not blank in an else if condition in Ruby (not Rails)?
elsif not variable.to_s.empty?
# do something
end
or
elsif !variable.to_s.empty?
# do something
end
or
elsif variable.to_s.length > 0
# do something
end
string = ""
unless string.to_s.strip.empty?
# ...
end
I just found out that ''.empty? returns true but ' '.empty? returns false. Even to_s.length for ' ' is not zero.
Maybe it is better to use strip as ' '.strip.empty?
You can use either
unless var.empty?
#do sth
end
or
unless var == ""
#do sth
end
or all of these with if and a negator !.
The source of the empty? method is analogous to the following:
def empty?
return length == 0
end
So, you can safely use
any_string.length != 0
Anyway, using that code inside an else if is a bit verbose, I would encourage you to define the present? method inside the String class.
class String
def present?
!empty?
end
end
Now you can write your code the following way:
if some_condition
# do something
elsif variable.to_s.present?
# do something else
end
This way you get a clear code, without using negations or unless who are hard to read.
Of course, there is one problem here, I took the present? name (and method) from Rails. present? returns true if the object is not blank, but strings with tabs or spaces (white characters) are considered blanks. So, this present? will return true to for the following strings:
"".present? # => false
" ".present? # => true
"\t\n\r".present? # => true
" blah ".present? # => true
It depends on what you want, high chances are that you want to get true for the first 3 strings, and false for the later. You could use #RamanSM approach and use strip to avoid empty spaces
class String
def present?
!strip.empty?
end
end
now, present? returns false for strings with white spaces
"".present? # => false
" ".present? # => false
"\t\n\r".present? # => false
" blah ".present? # => true
Note: Consider that String.present? is present in the ActiveSupport library (which ships with rails) if you add ActiveSupport or use Rails you should use ActiveSupport implementation instead.
If you prefer if to unless...
If you know your variable will be a String...if str[0]
With nil check...if str && str[0] OR if str&.[](0) (I prefer the latter but it might look odd to some people and requires Ruby >= 2.3).
Also...I'd be very careful about calling #to_s on anything because you could end up with unexpected results. If str turns out to be something that you weren't expecting...
str = false
str.to_s[0] # => 'f' (i.e. truthy)
str.to_s.empty? # => false
str = nil
str.to_s[0] # => nil (i.e. falsey)
str.to_s.empty? # => true
I think this caution applies to usage of #to_s in the other answer here as well. Exceptions can be your friend.
For the string (say abc) which is not defined/undefined we should check for abc.nil?
otherwise abc.blank? will throw (NoMethodError) undefined method empty? for nil:NilClass error

What's the major difference between if statements and unless statements?

I am new to programming and I don't understand what the difference is. I am coding in Ruby.
if should be obvious: execute a block of code if the condition is true.
unless is the opposite: execute a block of code if the condition is false.
http://www.codecademy.com/glossary/ruby/if-unless-elsif-and-else
unless is equal to if not
for example:
a= false
unless a
puts "hello"
end
=> hello
if not a
puts "hello"
end
=> hello
One of the goals of Ruby language is to become more closer to the real English. And keywords if and unless are really good examples of that. Look at this:
if animal.can_speak?
animal.say 'Woof!'
end
# moreover, it can be transformed in one-line, which looks pretty awesome:
animal.say 'Woof!' if animal.can_speak?
unless is an opposite of the if and instead of writing:
if not animal.can_speak?
puts "This animal can't speak"
end
we can use unless, that usually considered as more natural way:
unless animal.can_speak?
puts "This animal can't speak"
end
# it also has one-line approach:
puts "..." unless animal.can_speak?

Ruby case statement on a hash?

This is going to sound weird, but I would love to do something like this:
case cool_hash
when cool_hash[:target] == "bullseye" then do_something_awesome
when cool_hash[:target] == "2 pointer" then do_something_less_awesome
when cool_hash[:crazy_option] == true then unleash_the_crazy_stuff
else raise "Hell"
end
Ideally, I wouldn't even need to reference the has again since it's what the case statement is about. If I only wanted to use one option then I would "case cool_hash[:that_option]", but I'd like to use any number of options. Also, I know case statements in Ruby only evaluate the first true conditional block, is there a way to override this to evaluate every block that's true unless there is a break?
You could also use a lambda:
case cool_hash
when -> (h) { h[:key] == 'something' }
puts 'something'
else
puts 'something else'
end
Your code is very close to being valid ruby code. Just remove the variable name on the first line, changing it to be:
case
However, there is no way to override the case statement to evaluate multiple blocks. I think what you want is to use if statements. Instead of a break, you use return to jump out of the method.
def do_stuff(cool_hash)
did_stuff = false
if cool_hash[:target] == "bullseye"
do_something_awesome
did_stuff = true
end
if cool_hash[:target] == "2 pointer"
do_something_less_awesome
return # for example
end
if cool_hash[:crazy_option] == true
unleash_the_crazy_stuff
did_stuff = true
end
raise "hell" unless did_stuff
end
I think, following is the better way to do the stuff you want.
def do_awesome_stuff(cool_hash)
case cool_hash[:target]
when "bullseye"
do_something_awesome
when "2 pointer"
do_something_less_awesome
else
if cool_hash[:crazy_option]
unleash_the_crazy_stuff
else
raise "Hell"
end
end
end
Even in case's else part you can use 'case cool_hash[:crazy_option]' instead of 'if' if there are more conditions. I prefer you to use 'if' in this case because there is only one condition.
in ruby 3.0 you can do the following with pattern matching
# assuming you have these methods, ruby 3 syntax
def do_something_awesome = "something awesome 😎"
def do_something_less_awesome = "something LESS awesome"
def unleash_the_crazy_stuff = "UNLEASH the crazy stuff 🤪"
you can do
def do_the_thing(cool_hash)
case cool_hash
in target: "bullseye" then do_something_awesome
in target: "2 pointer" then do_something_less_awesome
in crazy_option: true then unleash_the_crazy_stuff
else raise "Hell"
end
end
will return
do_the_thing(target: "bullseye")
=> "something awesome 😎"
do_the_thing(target: "2 pointer")
=> "something LESS awesome"
do_the_thing(crazy_option: true)
=> "UNLEASH the crazy stuff 🤪"
in ruby 2.7 it still works
# need to define the methods differently
def do_something_awesome; "something awesome 😎"; end
def do_something_less_awesome; "something LESS awesome"; end
def unleash_the_crazy_stuff; "UNLEASH the crazy stuff 🤪"; end
# and when calling the code above to do the switch statement
# you will get the following warning
warning: Pattern matching is experimental, and the behavior may change
in future versions of Ruby!

In Ruby why won't `foo = true unless defined?(foo)` make the assignment?

What's going on here? What is the subtle difference between the two forms of "unless"?
> irb(main):001:0> foo = true unless defined?(foo)
=> nil
irb(main):002:0> unless defined?(fooo) ; fooo = false ; end
=> false
thx
Apparently, ruby creates local variable at parse time setting them to nilso it is defined and this is done whether the code is executed or not.
When the code is evaluated at your first line, it doesn't execute the assignment part since foo is set to nil. In the second line, because fooo has not been parsed yet, defined?returns nil letting the code inside the block execute and assign fooo.
As an example you can try this:
if false
foo = 43
end
defined? foo
=> "local-variable"
This is taken from a forum post at ruby-forum.
Let's start with something simpler:
# z is not yet defined
irb(main):001:0> defined?(z)
=> nil
# Even though the assignment won't execute,
# the mere presence of the assignment statement
# causes z to come to life.
irb(main):002:0> z = 123 if false
=> nil
irb(main):003:0> defined?(z)
=> "local-variable"
irb(main):004:0> z
=> nil
Now we can figure out your first example.
foo = true unless defined?(foo)
Is foo defined? Before we press ENTER in irb, no. However, the presence of the assignment statement causes foo to come to life. That means the assignment statement won't be executed, leaving foo in existence but having nil as its value. And what is the last expression evaluated in the irb line? It is unless defined?(foo), which evaluates to nil.
For more info on how assignments (even those that do not get executed) cause variables to exist, see this discussion of Variable/Method Ambiguity.
In your second example, there is nothing mysterious at all: fooo is not defined, so the code in the block executes, setting fooo to false. That assignment is the last expression evaluated, so false is the return value of our block.
irb(main)> foo = true unless defined?(Integer)
=> nil
irb(main)> foo = true unless defined?(thisIsUndefined)
=> true
Your first block is returning nil because the way it's written leaves 2 options:
foo is not defined --> assign true
foo is defined --> do nothing
Here, foo must be defined when the line is evaluated. Thus, nothing happens and nil is returned.
irb(main)> unless defined?(Integer) ; fooo = false ; end
=> nil
irb(main)> unless defined?(thisIsUndefined) ; fooo = false ; end
=> false
Your second block operates the same way your first one does. If fooo is not defined, the block is entered and fooo is set to false. The result of the last line of the block is the return value of the block, thus the false you are seeing. If fooo does exist, then the block is skipped over and nothing happens, therefore there is nothing to return, therefore the nil.
Based on your code, I would say that foo was defined when this code was run and fooo was not (test code shown was generated in Ruby 1.8.6). If you did not define either of these before running this code, then you may have something called foo that is defined by default (do defined?(foo) by itself to check). Try using a different name and see if you get the same results.
Edit:
irb(main)> defined?(bar)
=> nil
irb(main)> bar = true unless defined?(bar)
=> nil
irb(main)> defined?(bar)
=> "local-variable"
Apparently, defined?() is returning true since it has already seen bar (at the beginning of the line), even though you are still in the process of defining it.
in the first instance you call foo into existence in the assignment statement. Maybe this will clarify:
bar = if true
puts bar.class
else
puts "not reached"
end
NilClass
=> nil
baz = if true
puts baz.class
42
else
puts "not reached"
end
NilClass
=> 42
August, all look fine in 1.8.7:
$ irb
irb(main):001:0> unless defined?(fooo); fooo = true; end
=> true
irb(main):002:0> fooo
=> true
irb(main):003:0> `ruby --version`
=> "ruby 1.8.7 (2008-06-20 patchlevel 22) [i486-linux]\n"
Well.. One form is a block and one form isn't. The second part, the block, returns the last statement evaluated. The first one.. Hrm.. I don't know exactly what it's doing.
In Ruby 1.8.7:
foo = true unless defined?(foo)
p foo # => nil
unless defined?(fooo); fooo = true; end
p foo # => nil
I don't have an explanation for the behaviour you are seeing.

Resources