Ruby – A Lightning Tutorial

This is a work in progress tutorial on Ruby I’m currently writing. You must already be a rather experienced programmer to understand it. By “Lightning” I mean that it is essentially a glorified syntax reference that attempts to teach all the features of the language with very little explanation or detail on actual usage.

The main usage of this tutorial would be to learn how Ruby works, and then learn how to use it through a book such as Agile Web Development with Rails

Please keep in mind that I am not a Ruby expert (I’m quite new at it in fact), so some of this information may very well be wrong. Corrections/suggestions are welcome, just use the comment box at the bottom.

Typing Rules

Ruby is a dynamic, strongly typed language. For instance this works:

t = 15
t = "Hello world"

But this doesn’t

x = 15 + "hello world"

Variables in Ruby do not need to be declared, they leap into existence the first time they are assigned to.

Object Oriented Features

Ruby uses class based OOP. Everything is an object, there are no primitive types. Every single object (except nil) has methods. Methods are accessed using . syntax, IE my_object.my_method

There are no functions in Ruby, everything is a method of some object or other, although the methods of the Kernel object are always available.

Ruby supports only single inheritance, although you can use mixins to add extra functionality from modules.

Methods of the current object can be called with self as the implicit receiver, as in my_method, or explicitly using “self” as the receiver: self.my_method.

Scopes and Variables

Every type of block structure (except for blocks, which I will explain later) creates a new local scope.

Valid characters in a variable name are upper and lowercase letters, digits, and underscores. The first letter in a variable name determines what type it is.

  • Local variables start with a lowercase letter or underscore (my_variable, _something)
  • Global variables begin with a $ ($my_global_variable)
  • Instance variables begin with a @ (@name, @my_instance_variable)
  • Class variables begin with @@ (@@number_created)
  • Constants begin with an uppercase letter (MyConstant, PI)

Instance variables are always private, but they can be accessed from outside an object using attribute readers and writers.

class Person
    attr_reader :name # creates a reader for attribute 'name'
    attr_writer :address # writer
    attr_accessor :zip # reader and writer

    def initialize(name)
        @name = name
    end
end

bob = Person.new('Bob')
puts bob.name
bob.address = 'Somewhere'
bob.zip = '38193'
t = bob.zip

Classes

Ruby uses two separate methods when creating a new instance of a class, new and initialize. new is the constructor method, and initialize accepts initial arguments and sets default values. To create a new instance of an object, use MyClass.new. You do not need to call initialize explicitly, it will be called automatically by new (although this behavior can be overridden).

class Person # classes don't have to inherit from anything
    def initialize(name)
        @name = name # setting an instance variable with a default value
    end
    def say_hello # defining an instance method
        puts "Hello #{@name}"
    end
end

class Student < Person # inherit from the Person class
    def initialize(name, grade) # over-riding the parent class's initialize method
        super(name) # super calls the current method in the parent class
        @grade = grade
    end
    def say_hello
        super
        puts "You are in grade #{@grade}"
    end
end

bob = Person.new('Bob')
bob.say_hello
=> "Hello Bob"

tom = Student.new('Tom', 10)
tom.say_hello
=> "Hello Tom"
=> "You are in grade 10"

Class Methods

class HelloGenerator
    def self.make_hello(name)
        class_eval <<-end_eval
        def hello_#{name}
            puts "Hello #{name}"
        end
        end_eval
    end
end

class HelloBobTom < HelloGenerator
    make_hello 'bob'
    make_hello 'tom'
end

t = HelloBobTom.new
t.hello_bob
t.hello_tom

Writing Your Own attr_accessor

class Object
    def self.my_attr_accessor(*symbols)
        my_attr_reader(*symbols)
        my_attr_writer(*symbols)
    end

    def self.my_attr_reader(*symbols)
        symbols.each do |symbol|
            class_eval <<-end_eval
            def #{symbol}
                @#{symbol}
            end
            end_eval
        end
    end

    def self.my_attr_writer(*symbols)
        symbols.each do |symbol|
            class_eval <<-end_eval
            def #{symbol}=value
                @#{symbol} = value
            end
            end_eval
        end
    end
end

class Person < MyAttrAccessors
    my_attr_accessor :name
    my_attr_reader :age
    my_attr_writer :grade

    def initialize(name, age)
        @name = name
        @age = age
    end
end

instance_eval will do the same thing, but it will define a singleton method.

Methods

The basic syntax for a method is:

def hello(name)
    puts "Hello #{name}"
end

They can be called like:

hello('Bob')

Blocks and Iterators

Blocks are Ruby’s way of allowing simple yet extremely powerful functional programming. A block can be associated with a method by simply placing it on the same line after the method call. The method can call the block using yield, optionally passing it parameters. The value produced by the block will be returned by the yield statement.

def my_method(name, action)
    yield(name, action)
end

my_method('Bob') { |name, action| puts "#{name} is doing #{action}" }

The block accepts any parameters sent to it through yield inside a pair of pipes.

A method can test to see if it was passed a block using the Kernel method block_given?

Procedures

A block can be converted to a procedure (or lambda function) using the lambda statement.

hello = lambda { |name| puts "Hello #{name}"}

It can then be passed into a method as an argument:

def call_proc (name, proc)
    proc.call(name)
end

call_proc("Bob", hello)

A method can also convert a block passed to it into a procedure by preceding the last argument name with a &

def block_to_proc (&block) # it doesn't have to be &block, it could be &anything
    block.call
end

Block Scope

Blocks can affect variables in the local scope that are already defined when the block is created, but any new variables a block creates will not exist past the end of the block. For example:

name = "Tom"
p = lambda do
    name = "Bob"
    age = 15
end
p.call # run the block
name == "Bob" # name was modified by the block because it was defined before the block was created
defined? age == nil # age ceased to exist at the end of the block

Furthermore, it does not matter if a variable is initialized after the block was created but before it is run.

p = lambda { name = "Bob" }
name = "Tom"
p.call
name == "Tom" # name was not modified because it did not exist when the block was created

However, if a block assigns to an instance variable, that instance variable will be set on the object which created the block, even if it did not exist before the block was created. IE:

class Blocky
    def run
        yield
    end
end

t = Blocky.new
t.run { @name = "Bob" }
@name == "Bob"

@name was set in the local scope, rather than as an instance variable in the t object. Also note that @name continued to exist after the end of the block, even though it didn’t exist before the block was created. This same principle applies to class and global variables as well.

Variable Numbers of Arguments

A method can accept variable numbers of arguments by preceding the last argument (or second to last if using the &block rule) with a * which will fill that argument with an array of any extra arguments.

def many_args (*args)
    args.each { |arg| puts arg }
end

Ranges

1..10 inclusive 1…10 non-inclusive

Regular Expressions (regex)

Ruby uses the Oniguruma library to implement regular expressions. Regexes in Ruby can be expressed either as a literal like /pattern/flags or in a special %r{pattern} syntax.

To match a regular expression against a string, use the =~ operator, which returns the position of the beginning of the match, or nil if it was not found.

match_position = /p(erl|ython)/ =~ "python"

Matching a regular expression also sets several global variables, which are summarized below:

$& = matched part of the string
$` = part of the string before the match
$' = string after the match
$~ = MatchData object (more on that later)
$1-9 = subpattern matches

Replacements and substitutions can be done with either the sub or gsub string methods. sub only replaces the first match in the string, while gsub (global substitution) replaces every match.

"hello world".sub(/l+/, 'y') == "heyo world" # only replaces the first match
"hello world".gsub(/l+/, 'y') == "heyo woryd"
"hello world".sub(/(\w+)/, '\1, bye') == "hello, bye world"
"hello world".gsub(/[^l]+/, " \\& ") == " he ll o wor l d "

# this won't work
"random thing".gsub(/\w+/, "-#{$&}-") == "-d- -d-"

# $& was still set to "d" by the last call to gsub,
# so the replacement string that was used for each substitution was " d "

# If '-\&-' had been used as the replacement instead,
# it would have produced "-random- -thing-"

As you can see in the last example, instead of using the global variables mentioned above inside the replacement string (because they will not have been initialized when it is evaluated), you must use sequences like \&, \', and \1. Also be sure that you escape the backslash if using double quotes (IE: " \\& " rather than " \& ")

sub and gsub can also receive a block in place of their last argument, the return value of which will be substituted into the original string, like so:

"hello world".sub(/\w+/) { "bye" } == "bye world"
"hello world".sub(/\w+/) { $&.upcase } == "HELLO world" # the global variables mentioned above are available inside the block

The block can optionally receive the matched string as a parameter:

"hello world".gsub(/\w+/) { |match| match.upcase } == "HELLO WORLD"

For further information about Ruby’s regular expression syntax, see the Oniguruma reference page.

Array Methods

  • length
  • zip – rotates an array (sort of, needs more investigation)
  • transpose – actually rotates an array

Iterators

  • each { |element| puts element } – calls associated block with each element of the list as an argument
  • inject([initial_value]) { |total, element| total + element } – assigns initial_value to total the first time the block is called, and afterwards assigns the result of the previous iteration to total. If no initial_value is given, the first element of the array will be used instead, and iteration will start with the second element. The way inject is used here would return the sum of all the elements in the array.
  • map { |element| element * 2 } – also called collect, returns an array consisting of the the block operation applied to every element of the list (like Python list comprehensions)
  • find_all { |element| element % 2 == 0 } – filters an array, returning only those elements for which the block returned true

Kernel Methods

These can be called anywhere, without using a receiver.

  • rand([max]) – returns a random number from 0 to max (not inclusive). If called without arguments, returns a random float from 0 to 1 (not inclusive)
  • puts(string) – print a string plus a newline to the console (STDOUT)
  • gets – return a string of input to the console (STDIN) plus a trailing newline (calling chomp on the result will remove the newline)
  • print(string) – print a string without a newline to the console (STDOUT)

Basic Object Methods

These are present in every object. For more information see the documentation for Object, which is the base class in Ruby which all other objects inherit from.

  • send(:message[, *args]) – (or __send__ if a method named send already exists) sends the object :message (a symbol), with any number of args.
  • methods – returns an array of all the methods in the object
  • instance_methods([include_ancestors]) – returns an array of instance methods (meaning no class methods). Ironically, this only works on the class, IE String.instance_methods, not 't'.instance_methods. If ancestors is false, no inherited methods will be returned.
  • instance_variable_get("@name"), instance_variable_set("@name", "Bob"), and instance_variable_defined?("@name") – get, set, or check for the existence of an instance variable. Note: the @ before the name of the instance variable is required

The difference between or and ||

or has a lower precedence than =, meaning that something like the following will not do what you expect:

@open = event.open? or @admin # the value of the statement is true, but false is actually assigned to @open
@open == false

What happens is that @open will be assigned a value of false, and then the value of false will be ored with @admin. || on the other hand has a higher precedence than = so this will do what you would expect:

@open = ovent.open? || @admin
@open == true

Always use || when the result of the expression will be assigned to a variable.

Syntax

# this is a comment

# class declaration
class MyClass
    # method declaration
    def my_method
        my_other_method # calling another method of the same object
        $my_global_variable # global variables start with $
    end

    def my_other_method
        puts "Hello" # calling a method of the Kernal object
        @name = "Bob" # instance variables start with @
    end
end

# case statement (somewhat similar to the C switch statement), uses the === operator for comparison
name = "bob"
case name
    when "tom" then puts "Get out of here!"
    when "john", "bob" then # you can compare it to several possibilities by separating them with commas
        puts "Hello John or Bob"
        status = "Cool" # you can also use several statements
    else puts "You're lame" # the equivalent of default: in C
end

7 Responses »

  1. Fingal - April 25th, 2008 at 3:01 pm

    Attribute readers, writers, and accessors may not have meaning for those who first encounter them in the “Scopes and Variables” section above.

    What to do about this, if anything, depends, of course, on the intended audience, but if it’s ruby newbies, ….

  2. QBass - May 24th, 2008 at 5:36 pm

    Variables in Ruby do not need to be declared, they leap into existence the first time they are used. This should probably read : Variables in Ruby do not need to be declared, they leap into existence the first time they are assigned a value. A variable that is first ‘used’ on the RHS doesn’t leap into existence. 😛

  3. Connor - May 25th, 2008 at 2:49 pm

    Thanks for the corrections Fingal and QBass!

    @Fingal, I hadn’t thought much about that, I suppose most languages deal more with public/private attributes rather than accessors. Objective-C however is an exception in that it works almost exactly like Ruby. I should add some explanation about them.

  4. Pietro - April 5th, 2009 at 2:53 am

    The code

    def my_method(name, action) yield(name, action) end

    my_method(‘Bob’) { |name, action| puts “#{name} is doing #{action}” }

    is not correct since mymethod needs 2 arguments like mymethod(‘Bob’, ‘smoking’).

  5. Arun - February 23rd, 2010 at 2:47 am

    Good Tutorial :). Add more notes

  6. sanjeevakumar - October 26th, 2014 at 8:24 am

    Hello, I am automation engineer and basically from Java background.. Was looking for some good tutorials on ruby for experience level..this is extremely good… Superb…

  7. Tim Vergenz - September 22nd, 2015 at 5:34 pm

    Thanks for this! I avoided learning Ruby for years until today, and this tutorial was super helpful. It demonstrates the basic models and expectations of the language in a way that makes it easy to quickly grok for an experienced programmer.

    Thanks again!

Leave a Reply

Comments will be styled using Markdown.