Naming
var_names # snake_case
CONSTANT_VAR # UPPERCASE
class ClassNames # PascalCase
@instance_var # single underscore
@@class_var # double underscore
endNamespacing
Namespacing prevents naming collisions by organizing constants
Constant lookups
GREETING: Find nearestX::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
endScope
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 herePrinting
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 } # hashType checking
x = 5
x.class # Integer
x.kind_of?(Integer) # true
x.is_a?(Numeric) # trueArrays
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
enda = [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}"
endStrings 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 changedFrozen Objects & Immutability
We can make mutable objects immutable by freezing
str = "hello"
str.freeze
str.upcase! # FrozenError
arr = [1, 2, 3].freeze
arr << 4 # FrozenErrorCopies, 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 copyshallow 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 AlexString Interpolation
name = "Bob"
puts "Thanks #{name}" # Thanks BobInput
All input is returned as a string
puts "Name: "
name = gets.chomp
puts "Height: "
height = gets.chomp.to_iLoops
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
endWhile Loops
while condition
puts "condition is true"
endUntil Loops
until condition
puts "condition is false"
endLoop 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 1Ranges
(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, ..., zIterators
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 } # falseBoolean 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"
endunless age < 18
puts "You are an adult"
else
puts "You are a minor"
endLogical 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 = !falseCase (Switch) Statements
name = case first_letter
when 'A' then "Arthur"
when 'B' then "Bobby"
else "Sam"
endFunctions 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 # 8Arguments
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
"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) # roundError 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}"
endresult = risky_method rescue "default value"
def divide(a, b)
# we don't actually need a begin
a / b
rescue ZeroDivisionError
Float::INFINITY
endbegin
# 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:)"
endException 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