DEV Community

Cover image for Rubyme: My minimalist Ruby Handbook
mikelogaciuk
mikelogaciuk

Posted on • Updated on

Rubyme: My minimalist Ruby Handbook

rubyme

Introduction

Although Python and SQL still remain as my core DataOps languages, I consider Ruby as my primary backend language. Because of that I decided to write down few things just as a reminder.

The question is why I choosen Ruby as a backend language instead of TS (Express, AdonisJs) or Python (Django)?

Well:

  • Ruby syntax reminds me native language than any other programming language.
  • It is full blown object oriented languge, even more than Python (in my opinion).
  • It has an elegant and clean syntax, with a great batteries included frameworks such us Rails or Sinatra, Sequel, Thor etc.

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 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 keyword puts 'Some text' or print('Some text').

There is also a shorthand of letter p:

def start
  p 'Engine is on'
end
Enter fullscreen mode Exit fullscreen mode

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'}
]
Enter fullscreen mode Exit fullscreen mode

We can also convert variables into different types e.g.:

123.to_s
'903'.to_i
'22.33'.to_f
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Go back to 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 i5-12600K CPU'
product[:price] = 1599.00
product.keys
product.values

product.each do |key, value|
  puts key
  puts value
end
Enter fullscreen mode Exit fullscreen mode

Everything is well documented and behaves in a same way as in any other high-level languages.

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 an acompany of CTRL+SPACE.

Go back to 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?
Enter fullscreen mode Exit fullscreen mode

Time formatting:

now.strftime("%Y-%m-%d") # => '2020-01-01' 
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Go back to 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
Enter fullscreen mode Exit fullscreen mode

There are also more common loops:

# While loop
foo = 666
while foo < 999
  puts foo
  foo += 1
end
Enter fullscreen mode Exit fullscreen mode

Or:

# Simple unconventional loop
for i in 1..100
  next if i % 2 == 0
  puts i
end
Enter fullscreen mode Exit fullscreen mode

Which should be rewritten into more Rubyish style:

# Simple Ruby loop
(1..100).each { |i|
  next if i % 2 == 0
  puts i
}
Enter fullscreen mode Exit fullscreen mode

Another example of loop in Ruby is:

9.times { puts "Hello World" }
Enter fullscreen mode Exit fullscreen mode

Break loop:

# Break loop
numbers = [1,2,4,9,12]
numbers.each do |n|
  break if n > 10
  puts n
end
Enter fullscreen mode Exit fullscreen mode

Go back to 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Ternary operator:

# If oneliner aka ternary operator
puts 40 > 100 ? "Greater than" : "Less than"
Enter fullscreen mode Exit fullscreen mode

Negated if (unless):

puts 'Database connection is not set.' unless database.isactive? == true
Enter fullscreen mode Exit fullscreen mode

Ruby short if:

num = 2
puts "This number is even!" if num % 2 == 0
Enter fullscreen mode Exit fullscreen mode

Go back to 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
Enter fullscreen mode Exit fullscreen mode

In Ruby, we don't to write return or puts at the end of function.

Interpreter automatically evaluates last statement in 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)
Enter fullscreen mode Exit fullscreen mode

For methods without parameters, we even do not need to use ():

foo = hostname 
Enter fullscreen mode Exit fullscreen mode

Another example:

def greeting
  puts "Hello world!"
end

greeting
Enter fullscreen mode Exit fullscreen mode

Method with 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Go back to 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
Enter fullscreen mode Exit fullscreen mode

Simple class:

class Car
  def initialize(brand, model, type)
    @brand = brand
    @model = model
    @type = type
  end
end

audi_s6 = Car.new('Audi', 'S6', 'sedan')
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 in order to custom it by defining a method with the same name.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Instead of writing getters or setters manually:

class Product
  # set
  def price=(value)
    @price = value
  end
  # get
  def price
    @price
  end
end
Enter fullscreen mode Exit fullscreen mode

We can use attr_reader or attr_writer or combined attr_accessor keywords, that point variables 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Interface inheritance

Ruby allows two main types of inheritance class inheritance and interface inheritance.

Interface inheritance is used to isolate specific behaviours 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
Enter fullscreen mode Exit fullscreen mode

Go back to 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
Enter fullscreen mode Exit fullscreen mode

Mixins (multiple inheritance)

Ruby does not support multiple inheritance directly but Ruby Modules have another feature that eliminate 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
Enter fullscreen mode Exit fullscreen mode

Go back to contents.

Exceptions and errors

Like in any other language, raising errors is kinda simple:

# RuntimeError is implicit here
raise 'This is an exception'
Enter fullscreen mode Exit fullscreen mode
# Consistent with `raise SomeException, 'message', backtrace`.
raise SomeException, 'message'
Enter fullscreen mode Exit fullscreen mode
begin
  read_file
rescue Errno:ENOENT => ex
  handle_error(ex)
end
Enter fullscreen mode Exit fullscreen mode
# Exception handling
begin
  # E.g. ZeroDivision => 0 / 1
  0 / 1 
rescue
  # Will make this code to run
  puts "Exception"
  do_sth()
end
Enter fullscreen mode Exit fullscreen mode

Go back to contents.

Notes

For further reading, e.g. style guide, please consider those two:

Top comments (0)