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 argumentinject([initial_value]) { |total, element| total + element }
– assignsinitial_value
tototal
the first time the block is called, and afterwards assigns the result of the previous iteration tototal
. If noinitial_value
is given, the first element of the array will be used instead, and iteration will start with the second element. The wayinject
is used here would return the sum of all the elements in the array.map { |element| element * 2 }
– also calledcollect
, 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 namedsend
already exists) sends the object:message
(a symbol), with any number ofargs
.methods
– returns an array of all the methods in the objectinstance_methods([include_ancestors])
– returns an array of instance methods (meaning no class methods). Ironically, this only works on the class, IEString.instance_methods
, not't'.instance_methods
. Ifancestors
is false, no inherited methods will be returned.instance_variable_get("@name")
,instance_variable_set("@name", "Bob")
, andinstance_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 or
ed 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
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, ….
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.
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’).
Good Tutorial :). Add more notes
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…
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!