🍓 LanguagesRubyBasics

Naming

var_names # snake_case
CONSTANT_VAR # UPPERCASE
 
class ClassNames # PascalCase
  @instance_var # single underscore
  @@class_var # double underscore
end

Namespacing

Namespacing prevents naming collisions by organizing constants

Constant lookups

  • GREETING: Find nearest
  • X::Y: Look inside X
  • ::Y: Look farthest
GREETING = "top-level"
 
module Outer
  GREETING = "outer"
 
  class Parent
    GREETING = "parent"
 
    def lookup
      puts GREETING           # lexical "parent"
      puts Outer::GREETING    # "outer"
      puts Parent::GREETING   # "parent"
      puts NotFound::Greeting # NameError
      puts ::GREETING         # absolute "top-level"
    end
  end
 
  class Child < Parent
    GREETING = "child"
 
    def lookup
      puts GREETING          # "child"
      puts ::GREETING        # "top-level"
      puts Child::GREETING   # "child"
      puts Outer::GREETING   # "outer"
      puts Parent::GREETING  # "parent"
    end
  end
end

Scope

Determines the accessibility of variables, includes: local, instance, class, and global

$x = 10  # global var
what_scope = "global"
 
class ScopeClass
  @@class_var = "class"
 
  def initialize
    @instance_var = "instance"
  end
 
  def outer
    enclosing_var = "enclosing"
 
    inner = lambda do
      local_var = "local"
      puts $x, @@class_var, @instance_var, enclosing_var, local_var  # can access higher scopes
    end
 
    inner.call
    puts $x, @@class_var, @instance_var, enclosing_var  # local_var doesn't exist here
  end
end
 
example = ScopeClass.new
example.outer
puts $x  # enclosing_var and local_var don't exist here

Printing

print "hi there" # hi there
puts "hi there" # hi there\n (newline)
p "hi there" # "hi there" (inspect)

Data types

name = "Alice"  # string (mutable text)
food = :potato # symbol (unique immutable text)
age = 25         # integer (whole nums)
height = 5.5     # float (decimal nums)
is_true = true   # boolean (true/false)
fruits = ["apple", "banana"]  # array
person = { name: "Alice", age: 25 }  # hash

Type checking

x = 5
x.class # Integer
x.kind_of?(Integer) # true
x.is_a?(Numeric) # true

Arrays

fruits = ["apple", "banana"]
Array.new  # []
Array.new(2, "orange") # ["orange", "orange"]
 
fruits[0] # get
fruits[4] # error
fruits.dig(4) # safe get, nil
fruits.last(2) # select
 
fruits[-1] = "strawberry"  # set
fruits.push("cherry")  # add
fruits.delete("cherry")  # remove
 
fruits.each do |fruit|  # loop
  puts fruit
end
a = [1, 2, 3]
b = [3, 4, 5]
 
c = a + b # [1, 2, 3, 3, 4, 5]
c - [1, 3] # [2, 4, 5]

Hashes

Symbols are used as keys in hashes as they are more performant than strings

person2 = { "name" => "Alice", "age" => 25 } # => as keys are non-symbols
person = { name: "Alice", age: 25 } # : as keys are symbols
person[:name]  # get
person.fetch(:city, "Toronto") # get with default
person[:height] ||= 5.5  # if nil, assign
person[:city] = "New York"  # add
person.delete(:age)  # remove
 
person.keys # access keys
person.values # access values
 
hash1.merge(hash2) # combine
 
person.each do |key, value|  # loop
  puts "#{key}: #{value}"
end

Strings are mutable objects: each literal is a separate object in memory Symbols are immutable objects: there is only ever one object, and it only holds its name

"hello".object_id == "hello".object_id  # false
:hello.object_id == :hello.object_id # true
 
a = "hello"
b = :hello
# a and b are variables (refs to objects in memory)
a << "!" # s points to the same string literal, now mutated
b << "!" # cant do this! symbols are immutable and can't be changed

Frozen Objects & Immutability

We can make mutable objects immutable by freezing

str = "hello"
str.freeze
str.upcase!  # FrozenError
 
arr = [1, 2, 3].freeze
arr << 4  # FrozenError

Copies, duplicates, and clones

# frozen_string_literal: true
 
frozen_str = "french"
 
copied_str = +frozen_str  # always thawed, new string (only works on strings)
copied_str << " fries"  # french fries
 
frozen_str.dup.frozen?  # false, always thawed, shallow copy
frozen_str.clone.frozen?  # true, same state as frozen_str, shallow copy

shallow copy means we are creating a new obj but the references it contains will still point to the same elements outer: unique, inner: shared

Formatting

Concatenation

username = "Alex"
puts "Welcome " + username # Welcome Alex

String Interpolation

name = "Bob"
puts "Thanks #{name}" # Thanks Bob

Input

All input is returned as a string

puts "Name: "
name = gets.chomp
puts "Height: "
height = gets.chomp.to_i

Loops

For Loops

fruits = ["apple", "banana", "cherry"]
fruits.each do |fruit| # |fruit| block variable
  puts fruit # apple, banana, cherry
end
# or
fruits.each { |fruit| puts fruit }
 
5.times do |i|
  puts i  # 0, 1, 2, 3, 4
end
 
fruits.each_with_index do |fruit, index|
  puts "#{index}: #{fruit}" # 0: apple, 1: banana, 2: cherry
end

While Loops

while condition
  puts "condition is true"
end

Until Loops

until condition
  puts "condition is false"
end

Loop Control

10.times do |i|
  break if i == 5 # exits out of loop
  puts i  # 0, 1, 2, 3, 4
end
 
for i in 0..4
  next if i == 2 # skips to next iteration
  puts i  # 0, 1, 3, 4
end
 
5.times do |i|
  redo if i == 2 # restarts the current iteration
  puts i  # 0, 1, 2, 2, 3, 4
end
 
1.upto(2) { |i| print "#{i} " }     # 1 2
2.downto(1) { |i| print "#{i} " }   # 2 1

Ranges

(0..2) # 0, 1, 2 (inclusive)
(0...2) # 0, 1 (exclusive)
(0..10).step(2) # 0, 2, 4, 6, 8, 10
("a".."z") # a, b, c, ..., z

Iterators

names = ["Charles", "Bob"]
 
names.each { |name| puts name } # "Charles", "Bob"
names.each do |name|
  puts name
end
 
names.each_with_index { |name, index| puts "#{index}: #{name}" } # 0: Charles, 1: Bob
names.map { |name| name.upcase } # ["CHARLES", "BOB"]
names.select { |name| name.length > 5 } # keep all matching condition "Charles"
names.reject { |name| name.length > 5 } # remove all matching condition "Bob"
names.reduce { |acc, name| acc + name } # "CharlesBob"
names.any? { |name| name.length > 5 } # true
names.all? { |name| name.length > 5 } # false

Boolean Logic

Comparison Operators

ℹ️

Only false and nil are falsey in Ruby. Everything else is truthy.

==  # equal to
!=  # not equal to
>   # greater than
<   # less than
>=  # greater or equal
<=  # less or equal
<=> # -1, 0, 1 (less, equal, greater)

Conditionals

Conditional statements include if and unless + elsif and else

if temperature > 75
  puts "Hot!"
elsif temperature < 60
  puts "Cold!"
else
  puts "Mild"
end
unless age < 18
  puts "You are an adult"
else
  puts "You are a minor"
end

Logical Operators

Logical operators include and/&&, or/||, and !

if x > 0 && x < 10 # same as x.between?(0, 10)
  puts "x is between 0 and 10"
end
 
if x < 0 || x > 10 # same as !x.between?(0, 10)
  puts "Outside range"
end
 
is_valid = !false

Case (Switch) Statements

name = case first_letter
  when 'A' then "Arthur"
  when 'B' then "Bobby"
  else "Sam"
end

Functions Methods

ℹ️

Ruby is a “pure OOP” language. Everything is an object, so every function is a method

def add(a, b)
  a + b
end
 
result = add(5, 3)
puts result # 8

Arguments

Different types of arguments include: positional, default, keyword, and splat arguments

def area(width, height = 6) # positional args
  width * height # implicit return
end
 
puts area(5) # default height is used
puts area(5, 6)  # must be passed in order
 
def area(width:, height: 6) # keyword args with defaults
  width * height
end
 
puts area(width: 5)  # default height is used
puts area(height: 6, width: 5)  # keyword args can be passed in any order
 
def area(*args)  # splat args
  # args [5, 6]
  args[0] * args[1]
end
 
puts area(5, 6)  # can pass any number of args
puts area(5, 6, 7, 8) # args[2] and args[3] are unused
 
def area(**kwargs)  # keyword splat args
  # kwargs { width: 5, height: 6 }
  kwargs[:width] * kwargs[:height] # accessed with symbols
end
 
puts area(height: 6, width: 5)  # can pass any number of keyword args in any order
puts area(width: 5, potatos: 'round', height: 6) # potatoes is unused
 
# order: positional + defaults, keywords + defaults, positional splats, keyword splats
def area(pos_arg, pos_arg_def = 20, key_arg:, key_arg_def: 10, *pos_splat, **key_splat)
  pos_arg + pos_arg_def + key_arg + key_arg_def + pos_splat[0] + key_splat[:extra]
end
 
puts area(1, 2, key_arg: 3, key_arg_def: 4, 5, extra: 6)

Predicate Methods

Predicates are methods (ending in ?) that return a boolean value

puts 6.even?
puts 17.odd?

Bang Methods

Bang methods (ending in !) modify the object in place

greeting = "HI"
 
puts greeting.downcase # "hi"
puts greeting # "HI"
 
puts greeting.downcase! # "hi"
puts greeting # "hi"

Built-in Methods

View More

"Hello".length      # length
123.class           # type check
"42".to_i           # convert to int
"3.14".to_f         # convert to float
123.to_s            # convert to string
3.14159.round(2)    # round

Error Handling

Raising Exceptions

raise "Something went wrong"  # RuntimeError
raise ArgumentError, "Invalid argument"
raise ArgumentError.new("Invalid argument")

Exception Classes

class ValidationError < StandardError
  attr_reader :field
 
  def initialize(message, field: nil)
    super(message)
    @field = field
  end
end
 
raise ValidationError.new("Email is invalid", field: :email)

Rescue

begin
  # risky stuff
rescue ArgumentError => e # will catch first
  puts "Argument error: #{e.message}"
rescue StandardError => e
  puts "Standard error: #{e.message}"
rescue => e
  puts "Unknown error: #{e.message}"
end
result = risky_method rescue "default value"
 
def divide(a, b)
# we don't actually need a begin
  a / b
rescue ZeroDivisionError
  Float::INFINITY
end
begin
  # risky stuff
  whatever = false
rescue IOError => e
  retry if whatever
  puts "Whelp we tried"
else
  puts "No errors!"
ensure # finally
  puts "Win or lose, we had a nice time:)"
end
Exception Hierarchy
  • NoMemoryError
  • ScriptError
    • LoadError
    • NotImplementedError
    • SyntaxError
  • SignalException
    • Interrupt
  • StandardError
    • ArgumentError
    • IOError
      • EOFError
    • IndexError
      • StopIteration
    • LocalJumpError
    • NameError
      • NoMethodError
    • RangeError
      • FloatDomainError
    • RegexpError
    • RuntimeError
    • SecurityError
    • SystemCallError
    • SystemStackError
    • ThreadError
    • TypeError
    • ZeroDivisionError
  • SystemExit

File I/O

w: write, r: read, a: append, w+: read/write, a+: read/append

File.open("file.txt", "w") do |file|
  file.write("Hello, world!")
  file.append "Another line"
  puts file.read
end