Wikipedia Ruby gotchas
From the article:
$
and @
do not indicate variable data type as in Perl, but rather function as scope resolution operators.99.0
) or an explicit conversion (99.to_f
). It is insufficient to append a dot (99.
), because numbers are susceptible to method syntax.0
, ""
and []
are all evaluated to true
. In C, the expression 0 ? 1 : 0
evaluates to 0
(i.e. false). In Ruby, however, it yields 1
, as all numbers evaluate to true
; only nil
and false
evaluate to false
. A corollary to this rule is that Ruby methods by convention — for example, regular-expression searches — return numbers, strings, lists, or other non-false values on success, but nil
on failure (e.g., mismatch). This convention is also used in Smalltalk, where only the special objects true
and false
can be used in a boolean expression.char
for characters). This may cause surprises when slicing strings: "abc"[0]
yields 97
(an integer, representing the ASCII code of the first character in the string); to obtain "a"
use "abc"[0,1]
(a substring of length 1) or "abc"[0].chr
.The notation statement until expression
, unlike other languages' equivalent statements (e.g. do { statement } while (not(expression));
in C/C++/...), actually never runs the statement if the expression is already true
. This is because statement until expression
is actually syntactic sugar over
until expression
statement
end
, the equivalent of which in C/C++ is while (not(expression)) statement;
just like statement if expression
is an equivalent to
if expression
statement
end
However, the notation
begin
statement
end until expression
in Ruby will in fact run the statement once even if the expression is already true.
Greeting << " world!" if Greeting == "Hello"
does not generate an error or warning. This is similar to final
variables in Java, but Ruby does also have the functionality to "freeze" an object, unlike Java.Some features which differ notably from other languages:
The usual operators for conditional expressions, and
and or
, do not follow the normal rules of precedence: and
does not bind tighter than or
. Ruby also has expression operators ||
and &&
which work as expected.
def
inside def
doesn't do what a Python programmer might expect:
def a_method
x = 7
def print_x; puts x end
print_x
end
This gives an error about x
not being defined. You need to use a Proc
.
Language features
()
, to avoid ambiguous meaning of code. Not using ()
is still common practice, and can be especially nice to use Ruby as a human readable domain-specific programming language itself, along with the method called method_missing()
.Newbies will have trouble with equality methods:
These examples should clarify the first 3 methods:
a = b = "joe"
a==b # true
a.eql? b # true
a.equal? b # true (a.object_id == b.object_id)
a = "joe"
b = "joe"
a==b # true
a.eql? b # true
a.equal? b # false (a.object_id != b.object_id)
a = 1
b = 1.0
a==b # true
a.eql? b # false (a.class != b.class)
a.equal? b # false
Note that ==, eql? and equal? should always be symmetrical : if a==b then b==a.
Also note that == and eql? are both implemented in class Object as aliases to equal?, so if you create a new class and want == and eql? to mean something else than plain identity, then you need to override them both. For example:
class Person
attr_reader name
def == (rhs)
rhs.name == self.name # compare person by their name
end
def eql? (rhs)
self == rhs
end
# never override the equal? method!
end
The === method behaves differently. First of all it is not symmetrical (a===b does not imply that b===a). As I said, you can read a===b as "a matches b". Here are a few examples:
# === is usually simply an alias for ==
"joe" === "joe" # true
"joe" === "bob" # false
# but ranges match any value they include
(1..10) === 5 # true
(1..10) === 19 # false
(1..10) === (1..10) # false (the range does not include itself)
# arrays just match equal arrays, but they do not match included values!
[1,2,3] === [1,2,3] # true
[1,2,3] === 2 # false
# classes match their instances and instances of derived classes
String === "joe" # true
String === 1.5 # false (1.5 is not a String)
String === String # false (the String class is not itself a String)
The case statement is based on the === method:
case a
when "joe": puts "1"
when 1.0 : puts "2"
when (1..10), (15..20): puts "3"
else puts "4"
end
is equivalent to this:
if "joe" === a
puts "1"
elsif 1.0 === a
puts "2"
elsif (1..10) === a || (15..20) === a
puts "3"
else
puts "4"
end
If you define a new class whose instances represent some sort of container or range (if it has something like an include? or a match? method), then you might find it useful to override the === method like this:
class Subnet
[...]
def include? (ip_address_or_subnet)
[...]
end
def === (rhs)
self.include? rhs
end
end
case destination_ip
when white_listed_subnet: puts "the ip belongs to the white-listed subnet"
when black_listed_subnet: puts "the ip belongs to the black-listed subnet"
[...]
end
Monkey patching. Ruby has open classes, so their behaviour can be dynamically changed at runtime...
Objects might respond to undefined methods if method_missing
or send
has been overridden. This exploits Ruby's message-based method invocation. Rails' ActiveRecord system uses this to great effect.
The following code surprised me. I think it's a dangerous gotcha: both easy to run into, and hard to debug.
(1..5).each do |number|
comment = " is even" if number%2==0
puts number.to_s + comment.to_s
end
This prints:
1
2 is even
3
4 is even
5
But if I just add comment =
anything before the block...
comment = nil
(1..5).each do |number|
comment = " is even" if number%2==0
puts number.to_s + comment.to_s
end
Then I get:
1
2 is even
3 is even
4 is even
5 is even
Basically, when a variable is only defined inside a block, then it is destroyed at the end of the block, and then it gets reset to nil
upon every iteration. That's usually what you expect. But if the variable is defined before the block, then the outer variable is used inside the block, and its value is therefore persistent between iterations.
One solution would be to write this instead:
comment = number%2==0 ? " is even" : nil
I think a lot of people (including me) tend to write "a = b if c
" instead of "a = (c ? b : nil)
", because it's more readable, but obviously it has side-effects.
When calling super
with no arguments, the overridden method is actually called with the same arguments as the overriding method.
class A
def hello(name="Dan")
puts "hello #{name}"
end
end
class B < A
def hello(name)
super
end
end
B.new.hello("Bob") #=> "hello Bob"
To actually call super
with no arguments, you need to say super()
.
Blocks and methods return the value of the last line by default. Adding puts
statements to the end for debugging purposes can cause unpleasant side effects
Inheritence plays no part in determining method visibility in Ruby.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With