Unexpected Ruby behaviour
Serhii Potapov August 10, 2012 #unexpected #ruby #behaviour #lambda #proc #time #ensure #DelegateClass #regexp #superRuby is a cool language with intuitive grammar. However there are a number of things which don't seem to be expected. It might take long hours to debug some weird issues for unenlightened newbies.
Implicitly variable declaration
Variable mentioned in conditional block of code become declared and initialized with nil
even if the declaration was not executed.
if false
var =
end
var # => nil
Expected: addressing to var
raises NameError: undefined local variable or method 'var'
Calling #utc and #gmt on Time object removes time zone information
t = Time.new # => Sat Aug 11 01:11:52 +0300 2012
t.utc # => Fri Aug 10 22:11:52 UTC 2012
t # => Fri Aug 10 22:11:52 UTC 2012, WTF? O_o
t = Time.new # => Sat Aug 11 01:17:06 +0300 2012
t.gmtime # => Fri Aug 10 22:17:06 UTC 2012
t # => Fri Aug 10 22:17:06 UTC 2012, WTF? O_o
IMHO, this methods should be called utc!
and gmtime!
instead.
I have an experience when it caused a really voodoo thing: a test failed only from 20:00 to 00:00
in USA on CI server, and could never be reproduced in my time zone.
Instead it's better to use getutc
and getgm
methods which return UTC and GMT time accordingly,
but don't change Time object:
t = Time.new # => Sat Aug 11 01:11:52 +0300 2012
t.getutc # => Fri Aug 10 22:11:52 UTC 2012
t # => Sat Aug 11 01:11:52 +0300 2012
t = Time.new # => Sat Aug 11 01:17:06 +0300 2012
t.getgm # => Fri Aug 10 22:17:06 UTC 2012
t # => Sat Aug 11 01:17:06 +0300 2012
Methods do not return value from ensure statement
Usually ruby methods return the value of the last method line unless return
is called explicitly.
But how about this?
1
ensure
puts
2
end
run # => 1
# pints `ensure block...`
So if you want to return a value from ensure
statement use return
word:
1
ensure
return 2
end
run # => 2
Anchors ^ and $ do not mean a start and an end of a string
Most of script languages uses anchors ^
and $
of regular expressions as a start and an end of string accordingly.
But not in Ruby! In Ruby they are a start and an end of a line.
See the difference:
pattern = # 3-12 alphabetic characters
valid_name =
invalid_name =
valid_name =~ pattern # => 0, matchers
invalid_name =~ pattern # => 3, matchers
Instead use \A
and \z
anchors. They are a start and an end of a string.
pattern = # 3-12 alphabetic characters
valid_name =
invalid_name =
valid_name =~ pattern # => 0, matchers
invalid_name =~ pattern # => nil
Pay attention when you write validations. Read Egor Homakov's article to get more information about it.
\m regexp option
When other languages use \s
option to make .
match newline, Ruby uses \m
:
=~ # => nil
=~ # => 0
Calling super and super() are not the same
There is no matter for ruby methods do you use parentheses or not. But be careful with super
since it's not a method, but a key word.
Let me show an example when parentheses matter:
puts
end
puts
end
end
puts
super
end
puts
super()
end
end
child = Child.new
child.m1()
child.m2()
Output:
Child m1: arg = "foo"
Parent m1: arg = "foo"
Child m2: arg = "bar"
super.rb:6:in `m2': wrong number of arguments (0 for 1) (ArgumentError)
from super.rb:19:in `m2'
from super.rb:25:in `<main>'
When you use super
it calls same method of parent class passing same arguments to it.
But when you use super(...)
you have to pass arguments manually. In my example ArgumentError
was raised because Parent#m2
expects to receive exactly one argument, but nothing was passed to super()
However super()
still delegates a passed block. If you don't wanna pass
a block you have to do it explicitly using super(&nil)
:
yield
end
yield
end
end
super()
end
super(&nil)
end
end
child = Child.new
child.m1 { puts }
child.m2 { puts }
Output: Hi, m1 /tmp/super.rb:7:in `m2': no block given (yield) (LocalJumpError)
lambda and Proc.new act differently
It's a well known thing but I want to remind.
There 2 differences between proc objects created with lambda
and Proc.new
:
lambda
raisesArgumentError
if parameter is missing whenProc.new
usesnil
instead.
lm = lambda { }
pr = Proc.new { }
lm.call(10) # => ArgumentError: wrong number of arguments (1 for 2)
pr.call(10) # => "10 and nil"
- For
lambda
wordreturn
means returning from proc object, when forProc.new
it means returning from scope where proc is defined.
= lambda { return 10 } # return from lambda
half = lm.call
half * 2
end
pr = Proc.new { return 10 } # return from proc_method
half = pr.call
half * 2
end
lambda_method # => 20
proc_method # => 10
lm
Note there is also method proc
. In Ruby 1.8 it's a synonym for lambda
but in Ruby 1.9 it's a synonym for Proc.new
. So avoid using proc
to keep you code compatible
for both ruby versions.
DelegateClass instance does not eql itself
Ruby standard library provides DelegateClass which can be pretty useful. But some things are not so obvious about it:
end
(Animal)
end
animal = Animal.new
dog = Dog.new(animal)
dog.eql?(dog) # => false, WTF? O_o
It happens because eql?
is delegated to base object(animal):
dog.eql?(animal) # => true
On other hand equal?
is not:
dog.equal?(dog) # => true
dog.equal?(animal) # => false
dup method do not work for all objects
As you might know every object in Ruby has dup
method inherited from Object
class. It's convenient, I like it. But it does not mean that every object
can be duplicated.
[:symbol, true, false].all? { obj.respond_to? :dup } # => true
:symbol.dup # => TypeError: can't dup Symbol
true.dup # => TypeError: can't dup TrueClass
false.dup # => TypeError: can't dup FalseClass
It means you can't make a deep copy of an array (with one level depth) like this:
copy = array.map(&:dup)
Or like this:
copy = array.map { |val| val.respond_to?(:dup) ? val.dup : val }
Since all your objects respond to #dup
.
ActiveSupport provides duplicable?
method to inspect an ability
to duplicate an object, so finally your solution would look the next way:
copy = array.map { val.duplicable? ? val.dup : val }
Internally ruby symbols and booleans point to constant memory addresses, and
there is no reason to copy them since they are unchangable.
But I wish the implementation of their #dup
methods looked like this:
self
end
end
So I hope the article was useful for you. If you know some fancy Ruby things I haven't mentioned please let me know.
Thanks.