trevmex's tumblings

JavaScripter, Rubyist, Functional Programmer, Agile Practitioner.

The Well-Grounded Nuby

Advice for Ruby newcomers (and their mentors)

David A. Black (@david_a_black)

(These are the notes from the September 2010 Philly.rb meeting)

We want to learn the major points of confusion in the Ruby language and how to navigate them.

There are 7 big points that hurt. Understanding these 7 points can help you along the way while working through your Ruby education:

  • Point #1: Every expression evaluates to an object
    • Not everything is an object (i.e. if statements, argument lists, keywords, etc.), but there is just enough non-objects to make ALMOST everything an object…
    • BUT everything, every statement, will evaluate to an object. The result of any code block will be an object. Even nil is an object.
    • Use irb. It is awesome.
    • puts ALWAYS returns nil, it does NOT return its argument: It prints the argument.
    • a failed if statement returns nil
    • # in Ruby 1.8: 
      p 'string' # evaluates to nil
      # but in Ruby 1.9 it evaluates to the argument: p 'string' # evaluates to 'string'
    • Class definitions return nil.
  • Point #2: It is ALL about message passing
    • all the infix syntax is actually shorthand for message passing:
    • so…
    • 3+2 is actually 3.+(2)
    • 5*3 is actually 5.*(3)
    • a[2] is actually a.[](2)
    • +x is actually x.+@
    • What is cool about this, is that you can roll your own infix operators by just defining them as message.
    • case some_string
      when "abc"
        #...
      when /def/
        #....
      end
      # is the same as...
      if "abc" === some_string #... elseif /def/ === some_string #... end
      # which is actually...
      if "abc".===(some_string) #... elsif /def/.===(some_string) #... end
    • That means if you override the === method, you can create your own case statements!!! (That is pretty awesome.) Like so:
      class Person
        attr_reader :name
      def initialize(name) @name = name end
      def ===(other_person) self.name == other_person.name end end
      trev = Person.new('trevor') trevor = Person.new('trevor') bob = Person.new('bob')
      case trev when bob # not yet... when trevor # this will work, since we overrode the === method! end
    • The === method is mostly the same as the ==, but it is meant to be overwritten for this use.
    • String === String # is false, but...
      String === "abc" # is true
    • So, don’t do: case “abc”.class
  • Point #3: Objects resolve messages into methods
    • Objects do not “have” methods
    • They have the intelligence to find methods when they are required to do so.
    • Methods are stored in classes and modules, not in the objects themselves.
    • Every object has a lookup-path to find methods in that objects class hierarchy:
      • The singleton class: this is where you define a method for an individual object. (Woah, this is cool)
        str = "abc"
        def str.make_big
          str.upcase
        end
        
        str.make_big # this works
        str2 = "def" str2.make_big # this does not work, since make_big is defined for the str object only
      • Then it looks for any modules in the object’s singleton class
      • Then it looks at the object’s class
      • Then it will look at the object’s class’ included modules
      • Then it will move on up the tree (class, module, class, module) until you reach the Object class (or BasicObject in Ruby 1.9)
    • Lookup-paths are dynamic. Objects created before a module is added to the base class will have access to the added methods.
      class Person
      end
      trev = Person.new
      class Person def talk "rubyrubyruby" end end
      trev.talk # is "rubyrubyruby", since the lookup happens at RUNTIME, not object creation time.
    • There is an extend method that will add a module’s methods to one object, using the singleton object.
  • Point #4: Classes and Modules are objects
    • “The answer to 75% of all questions about Ruby is: classes are objects, too.”
    • You can send messages to them.
    • They can be put in arrays.
    • They can be assigned to local variables
    • They have their own instance variables, just like other objects. These are instance variables for the CLASS, not the objects instanced by the class.
    • Class variables are hierarchy-scoped globals. Don’t use them. They are evil, like eval evil. They are mini- globals!
    • Of course, classes are special beings, but they and still objects.
    • So, treat you classes as normal objects!
  • Point #5: There is always a self
    • self is the default receiver of messages
    • self owns instance variables, and, by definition, any instance variables you see belong to self.
    • The value of self changes:
      • in classes, it is the class
      • in methods, it is the object running the method
      • outside of objects, it is main (this is just a holder for objects)
      class C
        self # I am the class C
      
      def m self # I am an instance of the class C end end
      self # I am main
    • instance_eval swaps out self for the duration of the code block.
    • class_eval is a special flavor of instance_eval
  • Point #6: Variables contain references to objects
    • In Ruby, every variable contains a reference to an object. And every object can have access to any number of variables.
    • This means that every variable is passed by reference, not as a copy of the object.
      str = "abc"
      str2 = str
      str2 << "def"
      str # is "abcdef" since both str and str2 point to the same object
    • Ruby exposes references to instance variables in objects, so you can abuse this, unless you write some extra code
      class Person
        attr_reader :name # this should not be writable
      
      def initialize(name) @name = name end end
      trev = Person.new('trev') trev.name # this is 'trev' trev.name << " j00 have been hacked!" trev.name # this is 'trev j00 have been hacked!'
      # To stop this, don't use attr_reader: class Person def initialize(name) @name = name end
      def name @name.dup end end
      trev = Person.new('trev') trev.name # this is 'trev' trev.name << " j00 have been hacked!" trev.name # this is 'trev', since it is just a dup of the instance variable @name
  • Point #7: true and false are objects AND true and false are states
    • If a test evaluated to the object nil or the object false: it is false, otherwise it is true.

Thanks to David A. Black, and the entire Philly.rb crew for a great talk!