Introduction
Python and SQL, are my primary languages and the core of my daily DataOps duties.
Time goes by, but after many years in IT, I still consider Ruby as a magical unicorn that always makes me happy every time I see its syntax.
Although there are many languages in the market, Ruby has something magical, that it's hard to find elsewhere.
Why Ruby?
Ruby syntax reminds native language more than any other programming language.
It is a full-blown object-oriented language, even more than Python (in my opinion).
It has an elegant and clean syntax, with a great battery included frameworks such as Rails or Sinatra, Sequel, Thor etc.
In this article, you will find my hand notes, written years ago and rewritten to the more digital version. I didn't edit them that much, so they may look trivial (kind of).
But they were meant to be like that and the moment of writing them.
Contents
Head back to contents.
Variables
In Ruby
, variables are declared using its name and content. Since Ruby is a dynamic interpreted language, an interpreter sets types automatically depending on a data set in a variable.
Ruby has data structures such as:
booleans
strings
integers
floats
arrays (list)
hashes (dictionary for e.g. in Python)
arrays of hashes
etc.
For printing (console log) we use the keyword puts 'Some text'
or print('Some text')
.
There is also a shorthand for the letter p
:
def start
p 'Engine is on'
end
Various variable types examples:
# String
fruit = 'orange'
full_name 'Mikey Logan'
# Integer
magic_number = 89
# Float
tax_value = 0.23
# Booleans
does_exists = false
has = true
# Array
actors = [
'Bruce Willis',
'Pierce Brosnan',
'Sean Connery',
'Cristian Bale',
'Sean Bean',
'Will Smith'
]
# Hash
magic_numbers = {:one => 1, :two => 2, :three => 3}
settings = {
:environment => environment,
:host_name => hostname,
:app => 'Freya'
}
country = {
:name => 'Poland',
:capital => 'Warsaw',
:population => '38000000',
:language => 'polish',
:continent => 'Europe'
}
# Array of hashes
array_of_hashes = [
{:user_id => 501, :role => 'Administrator'},
{:user_id => 666, :role => 'CEO'},
{:user_id => 333, :role => 'IT Architect'}
]
We can also convert variables into different types e.g.:
123.to_s
'903'.to_i
'22.33'.to_f
Head back to contents.
Comments
In Ruby, comments start with #
or =begin
that ends with... =end
- which we usually use for multiline comments.
# Single line comment
=begin
This
is
a
multiline
comment
=end
Go back to the contents.
Object methods
Since everything in Ruby is an object, we can easily use built-in methods on all types of data structures.
name = 'Alice'
concat = "I love #{name} <3"
# String methods
name.empty? # => false
'How long am I?'.length #=>
name.upcase
name.downcase
' Trim me please'.strip
'SERVERX001'.start_with?('SERVER')
'XOR'.gsub('X', 'S')
'I love Ruby'.include? 'Ruby'
# Number methods
magic_number = 89.02
magic_number.round
magic_number.floor
magic_number.ceil
# Array methods
manufacturers = ['Audi', 'Opel', 'Skoda', 'Fiat']
manufacturers = %w[Audi Opel Skoda Fiat]
manufacturers.length
manufacturers.last
manufacturers.first
manufacturers[1]
manufacturers[0..2]
manufacturers.include? 'Ferrari'
manufacturers.pop # remove last element
manufacturers.shift # remove first element
manufacturers.push 'Ferrari' # append at the and
manufacturers << 'Pagani' # append at the end
manufacturers.unshift # append at the start
manufacturers.delete_at(2) # delete third element
# Hash methods
product = {}
product[:name] = 'Intel i7-6700K CPU'
product[:price] = 1599.00
product.keys
product.values
product.each do |key, value|
puts key
puts value
end
Everything is well documented and behaves in the same way as in any other high-level language.
The same goes for arrays or hashes, they also have their own built-in methods and probably there is no reason to show them at all.
Any Ruby IDE will allow you automatically to explore all methods by using a variable name and .
with a company of CTRL+SPACE
.
Go back to the contents.
Dates
Time.now.utc
Time.now.to_i
now = Time.now
now.year
now.month
now.day
now.hour
now.min
now.sec
now.sunday?
Time formatting:
now.strftime("%Y-%m-%d") # => '2020-01-01'
Snippet:
%d Day of the month (01..31) %m Month of the year (01..12) Use %-m for (1..12) %k Hour (0..23) %M Minutes %S Seconds (00..60) %I Hour (1..12) %p AM/PM %Y Year %A Day of the week (name) %B Month (name)
Go back to the contents.
Loops
Ruby loops are not that scary at all and resemble loops known in any other language.
However, there are two extremely human-like ones, which resemble the native language:
unless
until
Example:
# Until loop
cars = 0
until cars == 10
cars += 1
end
# Unless loops
x = 1
y = 666
unless x >= y
puts "Warning! X: #{x} is less than Y: #{y}."
end
There are also more common
loops:
# While loop
foo = 666
while foo < 999
puts foo
foo += 1
end
Or:
# Simple unconventional loop
for i in 1..100
next if i % 2 == 0
puts i
end
Which should be rewritten into more Rubyish
style:
# Simple Ruby loop
(1..100).each { |i|
next if i % 2 == 0
puts i
}
Another example of a loop in Ruby is:
9.times { puts "Hello World" }
Break loop:
# Break loop
numbers = [1,2,4,9,12]
numbers.each do |n|
break if n > 10
puts n
end
Go back to the contents.
Conditionals
Simple if-else
statement:
a = 10
if a == 10
puts 'Number equals to .'
elsif a < 10
puts 'Number is less than 10.'
else
puts 'Number is higher than 10.'
end
Simple case
statement:
# In Ruby, you can use case statement instead of if-elsif-else loop
secret_number = 444
case
when secret_number < 333
puts "You are not even close..."
when secret_number > 666 && secret_number < 668
puts "You've almost touched it..."
when
secret_number == 666
puts "You found the devil."
else
puts "Oh my gosh... guess that god damn evil number..."
end
Ternary operator:
# If oneliner aka ternary operator
puts 40 > 100 ? "Greater than" : "Less than"
Negated if (unless):
puts 'Database connection is not set.' unless database.isactive? == true
Ruby short if:
num = 2
puts "This number is even!" if num % 2 == 0
Go back to the contents.
Methods
In Ruby, the combined comparison operator, <=>, also known as the spaceship operator is used to compare two objects. It returns 0 if the first operand equals the second, 1 if the first operand is greater than the second, and -1 if the first operand is less than the second.
puts "Freya" <=> "Alice"
puts 4 <=> 6
puts 666 <=> 6
In Ruby, we don't write returns or puts at the end of the function.
The interpreter automatically evaluates the last statement in the block as a method return (implicit return):
require 'socket'
def hostname
Socket.gethostname
end
# Interpreter automatically returns the last evaluated expression.
def tax_value(product_price, tax_value)
product_price * tax_value
end
puts tax_value(1000, 0.17)
For methods without parameters, we even do not need to use ()
:
foo = hostname
Another example:
def greeting
puts "Hello world!"
end
greeting
Another one, with an input:
def count_tax(income, tax_value)
# Tax value must be double/float
result = income + (income * tax_value)
end
shop_item_value = 1000
eligible_tax = 0.19
puts count_tax(shop_item_value, eligible_tax)
Lambdas:
# Ruby Lambda Methods
add = lambda { |x,y| x + y }
sub = lambda { |x,y| x - y }
def math(method, x, y)
method.call(x,y) * 2
end
In Ruby, the yield
keyword is used to transfer control from a method to a block and then back to the method once executed.
def yield_test
puts "I'm inside the method."
yield
puts "I'm also inside the method."
end
yield_test { puts ">>> I'm butting into the method!" }
#Output
# I'm inside the method.
# >>> I'm butting into the method.
# I'm also inside the method.
Go back to the contents.
Object-oriented programming
Ruby is a pure object-oriented language and everything appears to Ruby as an object. Every value in Ruby is an object, even the most primitive things: strings, numbers and even true and false. Even a class itself is an object that is an instance of the Class class.
In Ruby, we declare classes with:
class Warehouse
end
wa01 = Warehouse.new
Simple class:
class Car
def initialize(brand, model, type)
@brand = brand
@model = model
@type = type
end
end
audi_s6 = Car.new('Audi', 'S6', 'sedan')
Private methods:
class Locomotive
def initialize(brand, model, type, axes)
@brand = brand
@model = model
@type = type
@axes = axes
end
private
def is_freight?
true if @axes > 6 and @axes < 10
end
end
eu07 = Locomotive.new('Pafag', 'EU07', 'electic', 6)
Inheritance in Ruby is quite simple.
And you need to note that when using inheritance:
The initialize method is also inherited when creating a child class. Any method can be subscribed in a child class, by defining a method with the same name.
Inheritance example:
class MultipleUnit < Locomotive
def initialize(brand, model, type, axes, allowed_passengers)
@brand = brand
@model = model
@type = type
@axes = axes
@allowed_passengers = allowed_passengers
end
private
def is_freight?
false
end
end
Simple class with class variable:
# In Ruby, class variables are attached to the class in which they are declared. A class variable should be declared with two @ symbols preceding it.
class Store
@@stores = 0
def initialize(store, city, voivodeship, county, population)
@store = store
@city = city
@voivodeship = voivodeship
@county = county
@population = population
@@stores += 1
end
def self.stores_count
return @@stores
end
def is_town?
puts @population <= 20000 ? true : false
end
end
store_a = Store.new("F001", "Zlotow", "Wielkopolskie", "zlotowski", 18000)
store_b = Store.new("F004", "Pila", "Wielkopolskie", "pilski", 80000)
store_a.is_town?
store_b.is_town?
puts Store.stores_count
Instead of writing getters or setters manually:
class Product
# set
def price=(value)
@price = value
end
# get
def price
@price
end
end
We can use attr_reader
or attr_writer
or combined attr_accessor
keywords, that point variable allowed to edition/reading:
class Product
def initialize(name, price)
@name = name
@price = price
end
attr_reader :name # Read only (getter)
attr_writer :price # Write only (setter)
attr_accessor :name, :price # Read and write
end
Or:
class Product
def initialize(product, name, group, net_price, minimal_price, tax)
@product = product
@name = name
@group = group
@net_price = net_price
@minimal_price = minimal_price
@tax = tax
@gross_price = net_price + (net_price * tax)
end
attr_reader :product
attr_accessor :net_price, :minimal_price, :tax, :gross_price
def description
puts "The product with code: #{@product}, has initial net price set to: #{@net_price} with tax: #{@tax}, which gives: #{@gross_price} gross price in total. "
end
end
test_product = Product.new("123456", "LG34NW", "TVAXGLED", 1300.00, 1150.00, 0.23)
test_product.description
Ruby can be very flexible and elegant with its syntax:
class Attendee
def initialize(height)
@height = height
end
def issue_pass!(pass_id)
@pass_id = pass_id
end
def revoke_pass!
@pass_id = nil
end
def has_pass?
true if @pass_id != nil
end
def fits_ride?(ride_minimum_height)
@height >= ride_minimum_height ? true : false
end
def allowed_to_ride?(ride_minimum_height)
true if @height >= ride_minimum_height && @pass_id != nil
end
end
Interface inheritance
Ruby allows two main types of inheritance class inheritance and interface inheritance.
Interface inheritance is used to isolate specific behaviors and we define it with modules instead of classes:
module Service
def change_oil
puts 'Oil has been changed.'
end
def renew_engine
puts 'Engine has been renewed.'
end
end
class DieselUnit < ElectricUnit
include Service
def initialize(brand, model, type, axes, allowed_passengers)
@brand = brand
@model = model
@type = 'electric'
@axes = axes
@allowed_passengers = allowed_passengers
end
private
def is_freight?
false
end
end
Go back to the contents.
Modules
Modules allow us to separate certain parts of logic into tiny little pieces:
module Driveable
def message
'The object is drivable'
end
end
class Car
include Driveable
end
class Tank
include Driveable
end
Car.new.message
Tank.new.message
Mixins (multiple inheritance)
Ruby does not support multiple inheritance directly but Ruby Modules have another feature that eliminates the need for multiple inheritance, providing a facility called a mixin.
Mixins give you a way of adding functionality to classes:
module Garage
def g1
end
def g2
end
end
module Warehouse
def w1
end
def w2
end
end
class Dock
include Garage
include Warehouse
def d1
end
end
example = Dock.new
example.d1
example.g1
example.g2
example.w1
example.w2
Go back to the contents.
Exceptions and errors
Like in any other language, raising errors is kinda simple:
# RuntimeError is implicit here
raise 'This is an exception'
# Consistent with `raise SomeException, 'message', backtrace`.
raise SomeException, 'message'
begin
read_file
rescue Errno:ENOENT => ex
handle_error(ex)
end
# Exception handling
begin
# E.g. ZeroDivision => 0 / 1
0 / 1
rescue
# Will make this code to run
puts "Exception"
do_sth()
end
Go back to the contents.
Notes
For further reading, about e.g. style guide, please consider these three:
Books
The Ruby Programming Language (David Flanagan, Yukihiro Matsumoto)
The Well-Grounded Rubyist 3rd Edition (David A. Black, Joseph Leo III)