Russ Olsen
in his book Design Patterns in Ruby
makes it easy for beginners in the language to view an excellent intro about Ruby to help build better software programs.
In this article, I’m distilling the notes I wrote about chapter 2.
Check out chapter 1 here
Getting Started with Ruby
The easiest way to run Ruby language is to use the interactive Ruby shell,
irb
.
- You can run interactively on the shell:
$ irb
irb(main):001:0> 2 + 2
=> 4
irb(main):002:0>
- You can run a ruby program with the
ruby
command (the Ruby interpreter):
$ ruby hello.rb
hello world
where hello.rb
is:
puts 'hello world'
As you can see, puts
prints out values, and the function in Ruby can
accept the parameter(s) without a parenthesis.
Also, in Ruby calculations can continue on the second line like in calc.rb
file:
x = 1 +
2 + 3
puts x
running the file:
$ ruby calc.rb
6
Take care if you continue the line before the desired statement ends like this:
x = 1
+ 2 + 3
puts x
it will give you 1. If you want to include adding 2 & 3, you should
extend the first line with /
:
x = 1 \
+ 2 + 3
puts x
-
For variable names, The well-bred Ruby programmer uses words_separated_by_underscores instead of camelCase.
-
In Ruby, constants start with a capital letter. You shouldn’t change the constant’s value although you can change it, but you’ll get a warning for your trouble.
Pounds = 2.2
FACTS = 'Life and Death'
/
does integer division in Ruby:
6/3 # is 2
7/3 # is still 2
- Everything in Ruby is an object. With the class method you can know its type:
irb(main):012:0> 7777777777777777777.class
=> Integer
irb(main):013:0> 77.77777777777777777.class
=> Float
irb(main):014:0> 'str'.class
=> String
You can check the type of the object with instance_of
method. You
can check if it’s a nil or not by nil
method. You can also convert
the object to string with to_s
method.
irb(main):023:0> 'hello'.instance_of? String
=> true
irb(main):021:0> 'hello'.nil?
=> false
irb(main):022:0> 44.to_s
=> "44"
Because everything in Ruby is an object, it is not correct to say that the expression x = 44 assigns the value 44 to the variable x. Instead, what is really happening is that x receives a reference to an object that happens to represent the number after 43.
nil
andfalse
both evaluate to false:
nil || false # false
nil or false # false
as you might guess, ||
and or
are the OR boolean operator while
&&
and and
are the AND operator. Also not
and !
are the
same for negation.
If you come from the world of C or C++, you will be shocked to learn that in Ruby, zero, being neither
false
nornil
, evaluates totrue
in a Boolean expression. Surprisingly, this expression:
if 0
puts 'Zero is true'
else
puts 'Zero is false'
end
will print out
Zero is true
- The else if in Ruby is
elsif
- There is an
unless
statement in Ruby which reverses theif
statement:
unless weight < 100
puts 'way too heavy'
end
A short form is also available:
puts 'way too heavy' unless weight < 100
- Ruby programmers tend to write loops like this:
array = ['first', 'second', 'third']
array.each do |element|
puts element
end
instead of a for
loop:
for element in array
puts element
end
until
is the evil twin of thewhile
loop; reverses the condition.- Some methods for strings in Ruby:
irb(main):001:0> name = 'Ezz'
=> "Ezz"
irb(main):002:0> name.length
=> 3
irb(main):003:0> name.upcase
=> "EZZ"
irb(main):004:0> name.downcase
=> "ezz"
- In many ways, Ruby strings act like arrays; mutable:
irb(main):005:0> name[0] = 'Z'
=> "Z"
irb(main):006:0> name
=> "Zzz"
- Substitution inside a string is done by
#{expression}
n = 42
puts "The value of n is #{n}."
which prints out:
The value of n is 42
Take care, you should the string, which contains a substitution, should be enclosed by double-quotes.
Symbols are more or less immutable strings and Ruby programmers use them as identifiers:
:a_symbol
:an_other_symbol
:first_name
For example, you can’t edit str2 here:
str1 = 'Ezz'
str2 = :Ezz
str1[0] = 'Z'
# str2[0] = 'Z'
puts str1
puts str2
- Appending arrays in Ruby can be done with
<<
operator:
a = ['banana', 'apple', 'orange']
a << 'mango'
puts a # ['banana', 'apple', 'orange', 'mango']
You can sort or reverse the array. If you want to change the array in
place (changes happen to the original array), add !
to the method.
a = ['banana', 'apple', 'orange']
a << 'mango'
a.sort
puts "original array not sorted: #{a}"
a.reverse
puts "original array not reversed: #{a}"
a.sort!
puts "original array sorted: #{a}"
a.reverse!
puts "original array reversed: #{a}"
- You can create hashes in Ruby with a pair of braces:
h = {}
h['first_name'] = 'Abu Bakr'
h['last_name'] = 'El Seddiq'
puts h
h_new = {'first_name' => 'Abu Bakr', 'last_name' => 'El Seddiq'}
puts h_new
Symbols make good hash keys:
h_improved = {:first_name => 'Abu Bakr', :last_name => 'El Seddiq'}
puts h_improved
- Regular expressions in Ruby, sit between a pair of forward slashes.
The
=~
operator when you match an RE with a string and!~
for testing whether an RE does not match:
/old/ =~ 'this old house' # 5 - the index of 'old'
/new/ !~ 'this old house' # true - 'new' is not matching anything
/Russ|Russel/ =~ 'Fred' # nil - Fred is not Russ nor Russel
/.x*/ =~ 'any old string' # 0 - the RE will match anything
Classes
class BankAccount
def initialize account_owner
@owner = account_owner
@balance = 0
end
def deposit amount
@balance = @balance + amount
end
def withdraw amount
@balance = @balance - amount
end
end
- Classes in Ruby start with capital letters and use camel case
- The
initialize
method is special and it’s the Ruby version of a constructor - To create a new instance of our class BankAccount
my_account = BankAccount.new('Ezz')
- Let’s say I’d like to access the balance instance variable:
my_account = BankAccount.new('Ezz')
puts my_account.balance
it seems this code is not working and produces the following error:
BankAccount.rb:17:in `<main>': undefined method `balance' @owner="Ezz", @balance=0> (NoMethodError)
This is because the instance variable on a Ruby object can not be accessed outside the object. That’s why we define an accessor method:
def balance
@balance
end
If we add this to the BankAccount class and tried to get the balance, we will receive 0.
We might want to set a new balance, so we add a setter method:
def set_balance new_balance
@balance = new_balance
end
If you tried to take it easy and ignore just setter method and wrote this:
my_account.balance = 100
it will output a NoMethodError
. What you need to do instead of that
and instead of the ugly setter method is:
def balance=(new_balance)
@balance = new_balance
end
which we can use to set balance using:
my_account.balance=(100)
and as always in Ruby, you can omit the parenthesis.
What we did above is a trick to make Ruby know that balance=
is a
method and it takes one argument. Yes, as you might realize, this equal
sign is part of the method’s name that Ruby translates it into a plain
old method call.
So now, the class looks good from the outside world. We have balance
; a method to get and balance=
; a method to set.
Boring, isn’t it?
Unsurprisingly, Ruby has a solution. As Ruby programmers use getter and setter methods a lot. Ruby supplies us with a great shortcut:
attr_accessor :balance
Now, this statement creates the value of the instance variable @balance
to be able to get . It also creates the balance=(new_value)
setter
method.
What if you want to just have access to getting an instance variable not
setting it? In this case, you can use attr_reader
like so:
attr_reader :name
in this case, the name variable is read-only.
Similarly, for the setter method only, use attr_writer
.
- Sometimes a method needs a reference to the current object, we can
use
self
:
class SelfCentered
def talk_about_me
puts "Hello I am #{self}"
end
end
conceited = SelfCentered.new
conceited.talk_about_me
but when you run that code, you’ll get something like this:
Hello I am #<SelfCentered:0x00005600a5ccfa78>
pointing to a hex address of the instance SelfCentered
.
- Ruby supports single inheritance – all the classes that you create
have exactly one parent of the superclass. If the superclass is not
specified, your class is automatically a subclass of
Object
.
In this example, we will create a subclass of BankAccount
:
class InterestingBearingAccount < BankAccount
def initialize owner, rate
@owner = owner
@balance = 0
@rate = rate
end
def deposit_interest
@balance += @rate * @balance
end
end
If you compare this with the superclass BankAccount
, you’ll see
duplicate information in the initialize
method. We have the same owner
and balance
as in the BankAccount
’s initialize
method:
def initialize account_owner
@owner = account_owner
@balance = 0
end
In this case, it’s better to avoid that messy code duplication and we
should use super
method like so:
def initialize owner, rate
super(owner)
@rate = rate
end
When a method calls
super
, it’s saying, “Find the method with the same name as me in my superclass, and call that.”
So basically, what super
is doing is that it calls the initialize
method in the superclass BankAccount
. If this method doesn’t
exist in the first superclass, Ruby will continue in the root of
inheritance until it finds that particular method.
Arguments
- You can set a default value in Ruby function like:
def add_args name, car="BMW"
puts "#{name} has #{car}"
end
add_args("Ezz")
add_args("Ezz", "Rolls Royce")
For the first return, it will say that I have BMW and the second return states that I got richer and have Rolls Royce.
- For a list of arguments, you can use asterisks:
def describe_langs name, *languages
puts "#{name}"
for lang in languages
puts "learns: #{lang}"
end
end
describe_langs("Ezz", "Python", "MATLAB", "Javascript", "Rust")
Modules
- In Ruby, you can include a module inside a class but you can create an instance from the module.
module Chatty
def say_hi
puts "Hello, my name is #{name}"
puts "My job title is #{title}"
end
end
class Employee
include Chatty
def name
'Ezz'
end
def title
'Data Engineer'
end
end
employee = Employee.new
puts employee.say_hi
so you can’t do something like this:
employee = Chatty.new
puts employee.say_hi
Exception
- Ruby will catch any exception with the keyword
rescue
. You can also specify the type of exception:
begin
quotient = 1 / 0
rescue ZeroDivisionError
puts "Division by zero"
end
If you want to raise the exception, you can use raise
.
Importing source files
- In Ruby, you can import source files with
require
which loads the classes inside the Ruby the file name followed. Import classes like this:
require 'account.rb'
or
require 'account'
This also applies to the standard files included with Ruby. For example, you can parse some URLs with URI class that comes with Ruby:
require 'uri'
ezz = URI.parse 'https://wwww.ezzeddinabdullah.com'
- Let’s say you want to require a package called runt that exists in RubyGems; a software packaging system. You need then to require RubyGems before the the package itself:
require 'rubygems'
require 'runt'
Final Thoughts
In this chapter, we’ve been on a quick tour of the Ruby language.
We’ve seen that everything in Ruby, from a string to a number to arrays, is an object. Here, the author wraps up Ruby for beginners so that you can use it to build better software programs implementing design patterns. Stay tuned because we’re about to start.
See you in chapter 3 notes!
Get Design Patterns in Ruby from Amazon!!
Image by the Author
Credit
- Photo by Michael Dziedzic on Unsplash