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 used.
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_varialbe) - 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 variable 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_valuetototalthe first time the block is called, and afterwards assigns the result of the previous iteration tototal. If noinitial_valueis given, the first element of the array will be used instead, and iteration will start with the second element. The wayinjectis 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)
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 namedsendalready 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. Ifancestorsis 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
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, ….