What are the Ruby Gotchas a newbie should be warned about?
RubyRuby Problem Overview
I have recently learned the Ruby programming language, and all in all it is a good language. But I was quite surprised to see that it was not as simple as I had expected. More precisely, the "rule of least-surprise" did not seem very respected to me (of course this is quite subjective). For example:
x = true and false
puts x # displays true!
and the famous:
puts "zero is true!" if 0 # zero is true!
What are the other "Gotchas" you would warn a Ruby newbie about?
Ruby Solutions
Solution 1 - Ruby
From the article:
-
Names which begin with a capital letter are treated as constants, so local variables should begin with a lowercase letter.
-
The characters
$
and@
do not indicate variable data type as in Perl, but rather function as scope resolution operators. -
To denote floating point numbers, one must follow with a zero digit (
99.0
) or an explicit conversion (99.to_f
). It is insufficient to append a dot (99.
), because numbers are susceptible to method syntax. -
Boolean evaluation of non-boolean data is strict:
0
,""
and[]
are all evaluated totrue
. In C, the expression0 ? 1 : 0
evaluates to0
(i.e. false). In Ruby, however, it yields1
, as all numbers evaluate totrue
; onlynil
andfalse
evaluate tofalse
. 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, butnil
on failure (e.g., mismatch). This convention is also used in Smalltalk, where only the special objectstrue
andfalse
can be used in a boolean expression. -
Versions prior to 1.9 lack a character data type (compare to C, which provides type
char
for characters). This may cause surprises when slicing strings:"abc"[0]
yields97
(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 alreadytrue
. This is becausestatement until expression
is actually syntactic sugar overuntil 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.
- Because constants are references to objects, changing what a constant refers to generates a warning, but modifying the object itself does not. For example,
Greeting << " world!" if Greeting == "Hello"
does not generate an error or warning. This is similar tofinal
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
andor
, do not follow the normal rules of precedence:and
does not bind tighter thanor
. Ruby also has expression operators||
and&&
which work as expected. -
def
insidedef
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 aProc
.
Language features
- Omission of parentheses around method arguments may lead to unexpected results if the methods take multiple parameters. The Ruby developers have stated that omission of parentheses on multi-parameter methods may be disallowed in future Ruby versions; the current (November 2007) Ruby interpreter throws a warning which encourages the writer not to omit
()
, 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 calledmethod_missing()
.
Solution 2 - Ruby
Newbies will have trouble with equality methods:
- a == b : checks whether a and b are equal. This is the most useful.
- a.eql? b : also checks whether a and b are equal, but it is sometimes more strict (it might check that a and b have the same type, for example). It is mainly used in Hashes.
- a.equal? b : checks whether a and b are the same object (identity check).
- a === b : used in case statements (I read it as "a matches b").
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
Solution 3 - Ruby
-
Monkey patching. Ruby has open classes, so their behaviour can be dynamically changed at runtime...
-
Objects might respond to undefined methods if
method_missing
orsend
has been overridden. This exploits Ruby's message-based method invocation. Rails' ActiveRecord system uses this to great effect.
Solution 4 - Ruby
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.
Solution 5 - Ruby
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()
.
Solution 6 - Ruby
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
Solution 7 - Ruby
Solution 8 - Ruby
I had a lot of trouble understanding class variables, class attributes and class methods. This code might help a newbie:
class A
@@classvar = "A1"
@classattr = "A2"
def self.showvars
puts "@@classvar => "+@@classvar
puts "@classattr => "+@classattr
end
end
A.showvars
# displays:
# @@classvar => A1
# @classattr => A2
class B < A
@@classvar = "B1"
@classattr = "B2"
end
B.showvars
# displays:
# @@classvar => B1
# @classattr => B2
A.showvars
# displays:
# @@classvar => B1 #Class variables are shared in a class hierarchy!
# @classattr => A2 #Class attributes are not
Solution 9 - Ruby
-
Blocks are really important to understand, they're used everywhere.
-
You don't need parentheses around method parameters. Whether you use them or not is up to you. Some say you should always use them.
-
Use raise and rescue for exception handling, not throw and catch.
-
You can use
;
but you don't have to unless you want to put multiple things on one line.
Solution 10 - Ruby
one thing i learned was to use the operator ||= carefully. and take special care if you are dealing with booleans. i usually used a ||= b as a catch all to give 'a' a default value if everything else failed and 'a' remained nil. but if a is false and b is true, then a will be assigned true.
Solution 11 - Ruby
I had trouble with mixins which contain instance methods and class methods. This code might help a newbie:
module Displayable
# instance methods here
def display
puts name
self.class.increment_displays
end
def self.included(base)
# This module method will be called automatically
# after this module is included in a class.
# We want to add the class methods to the class.
base.extend Displayable::ClassMethods
end
module ClassMethods
# class methods here
def number_of_displays
@number_of_displays # this is a class attribute
end
def increment_displays
@number_of_displays += 1
end
def init_displays
@number_of_displays = 0
end
# this module method will be called automatically
# after this module is extended by a class.
# We want to perform some initialization on a
# class attribute.
def self.extended(base)
base.init_displays
end
end
end
class Person
include Displayable
def name; @name; end
def initialize(name); @name=name; end
end
puts Person.number_of_displays # => 0
john = Person.new "John"
john.display # => John
puts Person.number_of_displays # => 1
jack = Person.new "Jack"
jack.display # => Jack
puts Person.number_of_displays # => 2
At first, I thought I could have modules with both instance methods and class methods by simply doing this:
module Displayable
def display
puts name
self.class.increment_displays
end
def self.number_of_displays # WRONG!
@number_of_displays
end
[...]
end
Unfortunately, method number_of_displays will never be included or extended because it is a "module class method". Only "module instance methods" can be included into a class (as instance methods) or extended into a class (as class methods). This is why you need to put your mixin's instance methods into a module, and your mixin's class methods into another module (you usually put the class methods into a "ClassMethods" submodule). Thanks to the included magic method, you can make it easy to include both instance methods and class methods in just one simple "include Displayable" call (as shown in the example above).
This mixin will count each display on a per-class basis. The counter is a class attribute, so each class will have its own (your program will probably fail if you derive a new class from the Person class since the @number_of_displays counter for the derived class will never be initialized). You may want to replace @number_of_displays by @@number_of_displays to make it a global counter. In this case, each class hierarchy will have its own counter. If you want a global and unique counter, you should probably make it a module attribute.
All of this was definitely not intuitive for me when I started with Ruby.
I still can't figure out how to cleanly make some of these mixin methods private or protected though (only the display and number_of_displays method should be included as public methods).
Solution 12 - Ruby
Pay attention to the Range notation.
(At least, pay more attention than I initially did!)
There is a difference between 0..10
(two dots) and 0...10
(three dots).
I enjoy Ruby a great deal. But this dot-dot versus dot-dot-dot thing bugs me. I think that such a subtle dual-syntax "feature" that is:
- easy to mistype, and
- easy to miss with your eyes while glancing over the code
should not be able to cause devastating off-by-one bugs in my programs.
[1]: http://www.ruby-doc.org/core/classes/Range.html "http://www.ruby-doc.org/core/classes/Range.html"
Solution 13 - Ruby
I think "and
" and "or
" are nods to Perl, which is one of Ruby's more obvious "parents" (the most prominent other being Smalltalk). They both have much lower precedence (lower than assignment, in fact, which is where the behaviour noted comes from) than &&
and ||
which are the operators you should be using.
Other things to be aware of that aren't immediately obvious:
You don't really call methods/functions, although it kinda looks that way. Instead, as in Smalltalk, you send a message to an object. So method_missing
is really more like message_not_understood
.
some_object.do_something(args)
is equivalent to
some_object.send(:do_something, args) # note the :
Symbols are very widely used. That's those things that start with :
and they're not immediately obvious (well they weren't to me) but the earlier you get to grips with them the better.
Ruby is big on "duck-typing", following the principal that "if it walks like a duck and quacks like a duck..." that allows informal substitution of objects with a common subset of methods without any explicit inheritance or mixin relationship.
Solution 14 - Ruby
If you declare a setter (aka mutator) using attr_writer
or attr_accessor
(or def foo=
), be careful of calling it from inside the class. Since variables are implicitly declared, the interpreter always has to resolve foo = bar
as declaring a new variable named foo, rather than calling the method self.foo=(bar)
.
class Thing
attr_accessor :foo
def initialize
@foo = 1 # this sets @foo to 1
self.foo = 2 # this sets @foo to 2
foo = 3 # this does *not* set @foo
end
end
puts Thing.new.foo #=> 2
This also applies to Rails ActiveRecord objects, which get accessors defined based on fields in the database. Since they're not even @-style instance variables, the proper way to set those values individually is with self.value = 123
or self['value'] = 123
.
Solution 15 - Ruby
Understanding the difference between Time and Date class. Both are different and have created issues while using them in rails. The Time class sometimes conflicts with other Time class libraries present in standard ruby/rails library. It personally took me a lot of time to understand what was exactly going on in my rails app. Later, I figured when I did
Time.new
It was referring to some library in a location that I was not even aware of.
Sorry if I am not clear with what I want to say exactly. If others have faced similar problems, please re-explain.
Solution 16 - Ruby
One that's caught me out in the past is that the newline character (\n
) escape sequence—amongst others—isn't supported by strings within single quotes. The backslash itself gets escaped. You have to use double quotes for the escaping to work as expected.
Solution 17 - Ruby
x = (true and false) # x is false
0 and '' are true, as you pointed out.
You can have a method and a module/class by the same name (which makes sense, because the method actually gets added to Object and thus has its own namespace).
There is no multiple inheritance, but frequently "mixin modules" are used to add common methods to multiple classes.
Solution 18 - Ruby
Methods can be redefined and can become a mind-scratcher until you discover the cause. (Admittedly, this error is probably a bit "harder" to detect when a Ruby on Rails controller's action is re-defined by mistake!)
#demo.rb
class Demo
def hello1
p "Hello from first definition"
end
# ...lots of code here...
# and you forget that you have already defined hello1
def hello1
p "Hello from second definition"
end
end
Demo.new.hello1
###Run:
$ ruby demo.rb
=> "Hello from second definition"
###But call it with warnings enabled and you can see the reason:
$ ruby -w demo.rb
demo.rb:10: warning: method redefined; discarding old hello1
=> "Hello from second definition"
Solution 19 - Ruby
I think it is always good to use .length on things... since size is supported by nearly everything and Ruby has dynamic types you can get really weird results calling .size when you have the wrong type... I would much rather get a NoMethodError: undefined method `length', so I generally never call size on objects in Ruby.
bit me more than once.
Also remember objects have ids, so I try not to use variables call id or object_id just to avoid confusion. If I need an id on a Users object it is best to call it something like user_id.
Just my two cents
Solution 20 - Ruby
I'm new to ruby, and on my first round I hit an issue regarding changing floats/strings to an integer. I started with the floats and coded everything as f.to_int. But when I continued on and used the same method for strings I was thrown a curve when it came to run the program.
Aparently a string doesn't have a to_int method, but floats and ints do.
irb(main):003:0* str_val = '5.0'
=> "5.0"
irb(main):006:0> str_val.to_int
NoMethodError: undefined method `to_int' for "5.0":String
from (irb):6
irb(main):005:0* str_val.to_i
=> 5
irb(main):007:0> float_val = 5.0
=> 5.0
irb(main):008:0> float_val.to_int
=> 5
irb(main):009:0> float_val.to_i
=> 5
irb(main):010:0>
Arbitrary parenthesis threw me at first too. I saw some code with and some without. It took me awhile to realize that either styles are accepted.
Solution 21 - Ruby
Related to monkut's response, Ruby's to_foo
methods hint at how strict a conversion they'll do.
Short ones like to_i
, to_s
tell it to be lazy, and convert them to the target type even if they're not able to be represented accurately in that format. For example:
"10".to_i == 10
:foo.to_s == "foo"
The longer explicit functions like to_int
, to_s
mean that the object can be natively represented as that type of data. For example, the Rational
class represents all rational numbers, so it can be directly represented as a Fixnum (or Bignum) integer by calling to_int
.
Rational(20,4).to_int == 5
If you can't call the longer method, it means the object can't be natively represented in that type.
So basically, when converting, if you're lazy with the method names, Ruby will be lazy with the conversion.
Solution 22 - Ruby
From In Ruby why won't foo = true unless defined?(foo)
make the assignment?
foo = true unless defined?(foo) #Leaves foo as nil
Because foo
is defined as nil
when defined?(foo)
is called.
Solution 23 - Ruby
Iteration over ruby hashes aren't guaranteed to happen in any particular order. (It's not a bug, it's a feature)
Hash#sort
is useful if you need a particular order.
Related question: Why are Ruby’s array of 1000 hashes' key and value pairs always in a particular order?
Solution 24 - Ruby
1..5.each {|x| puts x}
doesn't work. You have to put the range into parentheses, like
(1..5).each {|x| puts x}
so it doesn't think you're calling 5.each
. I think this is a precedence issue, just like the x = true and false
gotcha.
Solution 25 - Ruby
This one made me mad once:
1/2 == 0.5 #=> false
1/2 == 0 #=> true