Learning Ruby Basics in < 9 minutes

Created on Jun 28, 2021

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 .

$ irb
irb(main):001:0> 2 + 2
=> 4
irb(main):002:0> 
$ 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
Pounds = 2.2
FACTS = 'Life and Death'
6/3 # is 2
7/3 # is still 2
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 || 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 nor nil , evaluates to true 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
unless weight < 100
    puts 'way too heavy'
end

A short form is also available:

puts 'way too heavy' unless weight < 100
array = ['first', 'second', 'third']
array.each do |element|
    puts element
end

instead of a for loop:

for element in array
    puts element
end
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"
irb(main):005:0> name[0] = 'Z'
=> "Z"
irb(main):006:0> name
=> "Zzz"
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
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}"
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
/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
my_account = BankAccount.new('Ezz')
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 .

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 .

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

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.

def describe_langs name, *languages
    puts "#{name}"
    for lang in languages
        puts "learns: #{lang}"
    end
end

describe_langs("Ezz", "Python", "MATLAB", "Javascript", "Rust")

Modules

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

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

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'
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!!

Design Patterns in Ruby by RussOlsen

Image by the Author

Credit

Get the next FREE ebook ‘Disciplined’ once released and more exclusive offers in your inbox

Published on medium