ruby gotchas and caveats
Thu, Dec 14, 2006 devWhile 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.
- 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
- 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
- && 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. 
- 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
- no class/module unloading
- no native unicode (ruby < 2.0)
- 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
- 1⁄2 != 0.5 unless require ‘mathn’ (this is the default in many languages anyway, sometimes integer math is your friend). With mathn 1⁄2 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
- 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"
- 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
- 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
- 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
- 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
