ruby gotchas and caveats


While learning ruby I’ve run into a few gotchas that I think may be worth discussion/sharing. It’s worth noting that I’m coming from a Java,C#,C based background. These may not seem that odd to people coming from different backgrounds.

  1. strings aren’t auto converted into numbers or strings

    irb(main):001:0> "1" + 1
    TypeError: can't convert Fixnum into String
        from (irb):1:in `+'
        from (irb):1
    
  2. false and nil are the only valid false values, anything else is true

    irb(main):001:0> p "rats" if 1 == true
    => nil
    irb(main):002:0> p "rats" if 0 == false
    => nil
    irb(main):003:0> p "yay?" if nil
    => nil
    irb(main):004:0> p "oro?" if 6
    "oro?"
    => nil
    irb(main):005:0> p "oro?" if -6
    "oro?\n"
    => nil
    irb(main):005:0> p "will print" if 0
    "will print\n"
    => nil
    
  3. && and || are the high priority AND/OR constructs, and and or are low priority versions

    irb(main):016:0> x = nil or 99
    => 99
    irb(main):017:0> x
    => nil
    irb(main):018:0> x = 42 and 99
    => 99
    irb(main):019:0> x
    => 42
    irb(main):020:0> x = nil || 99
    => 99
    irb(main):021:0> x
    => 99
    irb(main):022:0> x = 49 && 100
    => 100
    

    That last one came as a surprise to me, as I expected it to return true. Guess not.

  4. Exceptions and raise vs catch.

catch/throw are not the same as raise/rescue. catch/throw allows you to quickly exit blocks back to a point where a catch is defined for a specific symbol, raise rescue is the real exception handling stuff involving the Exception object. If your exception doesn’t inherit from StandardError it will NOT be caught by default!


        irb(main):001:0> class MyException < Exception
        irb(main):002:1>   end
        => nil
        irb(main):003:0> def exception_miss
        irb(main):004:1>   raise MyException.new
        irb(main):005:1>   rescue
        irb(main):006:1>   p "saved!"
        irb(main):007:1>   end
        => nil
        irb(main):008:0> begin
        irb(main):009:1*     exception_miss
        irb(main):010:1>   rescue Exception
        irb(main):011:1>   p "not so much"
        irb(main):012:1>   end
        "not so much"
        => nil
        irb(main):015:0> def exception_hit
        irb(main):016:1>   raise NewException
        irb(main):017:1>   rescue
        irb(main):018:1>   p "excellent!"
        irb(main):019:1>   end
        => nil
        irb(main):020:0> exception_hit
        "excellent!"
        => nil
        irb(main):021:0> def catcher
        irb(main):022:1>   throw :testing
        irb(main):023:1>   rescue
        irb(main):024:1>   p "gotcha?"
        irb(main):025:1>   end
        => nil
        irb(main):026:0> catcher
        "gotcha?"
        => nil
        irb(main):071:0> def throw_no_rescue
        irb(main):072:1>   throw :gotme
        irb(main):073:1>   end
        => nil
        irb(main):074:0> def fire
        irb(main):075:1>   catch(:gotme) do
        irb(main):076:2*       p "start"
        irb(main):077:2>     throw_no_rescue
        irb(main):078:2>     p "after throw"
        irb(main):079:2>     end
        irb(main):080:1>   p "after catch"
        irb(main):081:1>   end
        => nil
        irb(main):082:0> throw_no_rescue
        NameError: uncaught throw 'gotme'
                from (irb):72:in 'throw'
                from (irb):72:in 'throw_no_rescue'
                from (irb):82
                from ♥:0
        irb(main):083:0> fire
        "start"
        "after catch"
        => nil
  1. no class/module unloading
  2. no native unicode (ruby < 2.0)
  3. private never truely private

    irb(main):001:0> class MonkeyNinja
    irb(main):002:1>   private
    irb(main):003:1>   def secret_weakness
    irb(main):004:2>     p "eep! dead monkey!"
    irb(main):005:2>     end
    irb(main):006:1>   end
    => nil
    irb(main):007:0> m = MonkeyNinja.new
    => #<MonkeyNinja:0x2c01d5c>
    
    
    irb(main):008:0> m.methods
    => [...no secret weakness! ]
    
    
    irb(main):009:0> m.private_methods
    => [stuff, "secret_weakness", "lambda"]
    irb(main):010:0> m.send :secret_weakness
    "eep! dead monkey!"
    => nil
    
    
    irb(main):011:0> m.secret_weakness
    NoMethodError: private method 'secret_weakness' called for #<MonkeyNinja:0x2c01d5c>
            from (irb):10
            from ♥:0
    
    
    irb(main):012:0> m.instance_eval do
    irb(main):013:1*     secret_weakness
    irb(main):014:1>   end
    "eep! dead monkey!"
    => nil
    
    
    irb(main):015:0> def m.kill_you
    irb(main):016:1>   secret_weakness
    irb(main):017:1>   end
    => nil
    irb(main):018:0> m.kill_you
    "eep! dead monkey!"
    => nil
    
  4. 12 != 0.5 unless require ‘mathn’ (this is the default in many languages anyway, sometimes integer math is your friend). With mathn 12 becomes a different type (Rational) than 0.5 (Float).

        irb(main):010:0> 1.0/2
        => 0.5
        irb(main):011:0> 1/2
        => 0
        irb(main):012:0> p "normalcy" if 1/2 == 0.5
        => nil
        irb(main):013:0> require 'mathn'
        => true
        irb(main):014:0> 1/2
        => 1/2
        irb(main):015:0> p "normalcy" if 1/2 == 0.5
        "normalcy"
        => nil
        irb(main):016:0> a = 0.5
        => 0.5
        irb(main):017:0> a.class
        => Float
        irb(main):018:0> b = 1/2
        => 1/2
        irb(main):019:0> b.class
        => Rational
  1. class variables and their inheritance semantics
        irb(main):001:0> class OmgFun
        irb(main):002:1>   @@taco = "testing"
        irb(main):003:1>   def taco
        irb(main):004:2>     @@taco = "me"
        irb(main):005:2>     end
        irb(main):006:1>   def self.to_s
        irb(main):007:2>     @@taco
        irb(main):008:2>     end
        irb(main):009:1>   end
        => nil
        irb(main):010:0> class B < OmgFun
        irb(main):011:1>   @@taco = "B's taco"
        irb(main):012:1>   end
        => "B's taco"
        irb(main):013:0> class C < B
        irb(main):014:1>   @@taco = "C's taco"
        irb(main):015:1>   end
        => "C's taco"
        irb(main):016:0> C.to_s
        => "C's taco"
        irb(main):017:0> B.to_s
        => "C's taco"
        irb(main):018:0> OmgFun.to_s
        => "C's taco"
  1. singleton class modification for the class objects isn’t good or really allowed
        irb(main):001:0> class Daffy
        irb(main):002:1>   @@mine = "all mine"
        irb(main):003:1>   def to_s
        irb(main):004:2>     @@mine
        irb(main):005:2>     end
        irb(main):006:1>   end
        => nil
        irb(main):007:0> class << Daffy
        irb(main):008:1>   def jigga
        irb(main):009:2>     @@mine = "bunny time"
        irb(main):010:2>     end
        irb(main):011:1>   end
        => nil
        irb(main):012:0> d = Daffy.new
        => all mine
        irb(main):013:0> Daffy.jigga
        (irb):9: warning: class variable access from toplevel singleton method
        => "bunny time"
        irb(main):014:0> d
        => all mine
        irb(main):015:0> d = Daffy.new
        => all mine
        irb(main):016:0> def Daffy.wha
        irb(main):017:1>   @@mine = "wabbit"
        irb(main):018:1>   end
        => nil
        irb(main):019:0> Daffy.wha
        => "wabbit"
        irb(main):020:0> d
        => all mine
        irb(main):021:0> d = Daffy.new
        => all mine
or 
        irb(main):001:0> class A
        irb(main):002:1>   @@foo = "bar"
        irb(main):003:1>   def self.print_foo
        irb(main):004:2>     p @@foo
        irb(main):005:2>     end
        irb(main):006:1>   end
        => nil
        irb(main):007:0> A.print_foo
        "bar"
        => nil
        irb(main):008:0> A.class_eval do
        irb(main):009:1*     @@foo = "no go"
        irb(main):010:1>   end
        => "no go"
        irb(main):011:0> A.print_foo
        "bar"
        => nil
        irb(main):012:0> A.class_eval do
        irb(main):013:1*     def self.new_hotness
        irb(main):014:2>     p @@foo
        irb(main):015:2>     end
        irb(main):016:1>   end
        => nil
        irb(main):017:0> A.new
        A.new          A.new_hotness
        irb(main):017:0> A.new
        A.new          A.new_hotness
        irb(main):017:0> A.new_hotness
        "no go"
        => nil
        irb(main):018:0> A.instance_eval do
        irb(main):019:1*     p @@foo
        irb(main):020:1>   end
        "no go"
        => nil
        irb(main):021:0> A.module_eval do
        irb(main):022:1*     p @@foo
        irb(main):023:1>   end
        "no go"
        => nil
        irb(main):024:0> A.print_foo
        "bar"
        => nil
  1. assignment methods always return the assigned value, not the return value of the assignment function
        irb(main):001:0> class Foo
        irb(main):002:1>   def self.bar=(val)
        irb(main):003:2>     return "apeiros!"
        irb(main):004:2>     end
        irb(main):005:1>   end
        => nil
        irb(main):006:0> Foo.bar = "meep"
        => "meep"
        irb(main):007:0> Foo.bar
        NoMethodError: undefined method 'bar' for Foo:Class
                from (irb):7
                from ♥:0
  1. local variable scoping is tricky and can hide other variables and methods (this is slated for a change in ruby 2.0)
        irb(main):008:0> class Pizza
        irb(main):009:1>   def my_hut
        irb(main):010:2>     p "tasty!"
        irb(main):011:2>     end
        irb(main):012:1>   def breadstick
        irb(main):013:2>     if nil
        irb(main):014:3>       my_hut = "never executed"
        irb(main):015:3>       end
        irb(main):016:2>     my_hut
        irb(main):017:2>     end
        irb(main):018:1>   end
        => nil
        irb(main):019:0> m = Pizza.new
        => #<Pizza:0x2c63cc8>
        irb(main):020:0> m.breadstick
        => nil
  1. Another class variable trouble spot. You might think that class_eval would provide access to the class’s context and variables, apparently not so much. This example/issue came from Gary at a New Haven Ruby Brigade meeting.
        irb(main):001:0> class A
        irb(main):002:1>   @@avar = "hello"
        irb(main):003:1>   end
        => "hello"
        irb(main):004:0> A.class_variables
        => ["@@avar"]
        irb(main):005:0> A.class_eval { puts @@avar }
        NameError: uninitialized class variable @@avar in Object
                from (irb):5
                from (irb):5:in 'class_eval'
                from (irb):5
                from ♥:0
        irb(main):006:0> A.instance_eval { puts @@avar }
        NameError: uninitialized class variable @@avar in Object
                from (irb):6
                from (irb):6:in 'instance_eval'
                from (irb):6
                from :0
        irb(main):007:0> A.module_eval { puts @@avar }
        NameError: uninitialized class variable @@avar in Object
                from (irb):7
                from (irb):7:in 'module_eval'
                from (irb):7
                from ♥:0
        irb(main):008:0> class A
        irb(main):009:1>   def my_avar
        irb(main):010:2>     @@avar
        irb(main):011:2>     end
        irb(main):012:1>   end
        => nil
        irb(main):013:0> a = A.new
        => #<A:0x2c27890>
        irb(main):014:0> a.my_avar
        => "hello"
        irb(main):015:0> a.instance_eval { puts @@avar }
        NameError: uninitialized class variable @@avar in Object
                from (irb):15
                from (irb):15:in 'instance_eval'
                from (irb):15
                from :0