Ruby: Review

Variables

Variables are placeholders for data. They're useful for storing objects for use later.

Creating a variable is called defining and giving it a value is called assignment. In Ruby, defining happens when we assign a variable, so we usually only talk about assigning a variable.

# Assignment
>> x = 4
>> y = 5

You can find out what type of variable something is with the .class method.

>> 4.class
=> Fixnum

>> "hello".class
=> String

Nil

The nil keyword is a placeholder for "nothing". It is different from "undefined".

We can think of a variable as a box that we put data in.

# what's in the box? Nothing! aka `nil`
>> box = nil
>> box
=> nil

# what's inside of a non-existent box? That's impossible to know, so it's an error.
>> defined? box2
=> false
>> box2
NameError: undefined local variable or method `box2' for main:Object

Booleans

Booleans represent whether something is either true or false.

Boolean Expressions

A boolean expression is an expression that evaluates to true or false.

>> 1 < 3
=> true

>> 5 > 6
=> false

>> 5 == 5
=> true

>> "asdf" == 'asdf'
=> true

Comparison operators

Comparing two values using comparison operators in a boolean expression results in either true or false.

operator description
== equality
!= not equal
> greater than
< less than
>= greater than or equal to
<= less than or equal to
.eql? equal values AND same types
.equal? identical objects (by object id)

Though they exist, we almost never use .eql? or .equal?.

Logical Operators

Booleans can be combined using logical operators.

operator english example description
&& AND (5 > 3) && (6 > 4) true only if both sides are true
|| OR (5 > 3) || (3 > 5) true if any expression evaluates as true
! NOT !(5 > 3) negates (reverses) a boolean expression's result

Truthiness

Everything in Ruby evaluates to true except for nil and false. This is very useful for writing simple if statements.

>> "yes" if 0
=> "yes"

>> name = "Darth Vader"
>> "yes" if name
=> "yes"

>> "yes" if false
=> nil

>> "yes" if nil
=> nil

Strings

Strings are variables that store text.

Combining strings

Concatenation - combining two strings with addition operator + or the append operator <<.

"Hello " + "World"
=> "Hello World"

Interpolation - using #{} to interpolate variables into a string.

name = "Darth Vader"
"Hello #{name}!"
=> "Hello Darth Vader"

# Interpolation allows more complex statements
"Hello #{name.upcase.reverse}"
=> "Hello REDAV HTRAD"
# However, for clarity's sake, it is usually better to assign it to a variable first

Remember, interpolation is generally faster than concatenation and should be prefered if you need to programmatically create a string.

'Single Quotes' vs "Double Quotes"

Single quotes - Do not allow for string interpolation or special characters.

Double quotes - Support string interpolation and special characters.

# 'single quotes' does not interpolate
>> 'Hi #{name}!'
=> "Hi \#{name}!"

# "double quotes" does interpolate
>> "Hi #{name}!"
=> "Hi Darth Vader!"

Recent optimizations in the Ruby compiler has reduce the performance difference between single and double quotes to a negligible amount. You can use either one as long as you understand the difference.

Want to know a lot more about it? Read the blog post Rubyists: Just use double-quoted strings.

Common string methods

(each method links name to the Ruby source document)

Numerics

All numbers have a parent class called Numeric, which shares basic numeric methods. Many mathematical methods can be found on `Numeric.

There are two common subclasses of Numeric, the Fixnum class which represents whole number integers (1, 10, 85), and the Float class, representing real numbers (1.4, 10.75, 3.1415).

# Fixnum
>> 4.class
>> 1000.class
=> Fixnum

# Float
>> 10.4.class
>> 1984.189848.class
=> Float

Why Fixnum instead of Integer? The Fix in Fixnum stands for "fixed point" numbers, in contrast to "floating point" numbers. Fixnum is a subclass kind of Integer!

Numeric division

Watch out! Ruby treats integer division differently than most languages. It will not implicitly convert Integer types to Floats!

# Integer division always rounds and returns an Integer
>> 21 / 5
=> 4

>> (21 / 5).class
=> Fixnum

# Using floats return Floats
>> 21 / 5.0    # => 4.2
>> 21.0 / 5    # => 4.2
>> 21.0 / 5.0  # => 4.2

>> (21 / 5.0).class
=> Float

# Explicitly convert integer to float
>> age = 21
>> age.to_f / 5
=> 4.2

Common Numeric methods

Other than the basic mathematical operators addition +, subtraction -, multiplication *, division /, modulo %, and power **, there are a few useful numeric methods.

(each method links name to the Ruby source document)

Methods

Methods are tools for grouping lines of code together with a single common purpose.

Defining methods

Ruby uses the def keyword to define methods.

Methods can take arguments, which are variables in the scope of the method.

def scream(string)
  "#{string.upcase}!!!"
end

scream('howzit')
=> "HOWZIT!!!"

Calling (Invoking) methods

Ruby has syntactic sugar that allows you to drop the () on a method call if it's not required.

>> 10.to_s()
=> "10"

# No () needed
>> 10.to_s
=> "10"

Sometimes you do need them, most often when you're passing in the return value of one method into another method call. Ruby doesn't know which argument belongs to which method!

# Does the 3 belong to "puts" or "include?", no way to know!
puts "hello", [1, 2, 3].include? 3
SyntaxError: unexpected tINTEGER, expecting end-of-input

# Now we have to make it clear and it will work
puts "hello", [1, 2, 3].include?(3)
hello
true
=> nil

Return Values

Every method returns a value. The return value of a method is the last evaluated statement.

def give_me_something_good
  "this won't be returned"
  "this WILL BE returned!"
end

give_me_something_good
=> "this WILL BE returned!"

Other statements, like if or blocks, will return a value too!

# If statements work too
def check_truthiness(item)
  if item
    true
  else
    false
  end
end

# Notice that the return value of the if statement is the last
# evaluated statement!
>> check_truthiness(0)
=> true

>> check_truthiness(nil)
=> false

Method scope

Arrays

Arrays are collections of things!

# It can contain anything in Ruby
>> array = ['zero', 1, 'two', :three, nil, {four: 5.0}, 6]

# Each slot of an array is accessed with an "index"
# Indeces of an array start at 0
>> array[0]
=> "zero"

# The items in an array are called "elements"
>> array[0]   # index
=> "zero"     # element

# Array elements can be accessed with methods also
>> array.first
=> "zero"

# And negative indeces, which count back from the end
>> array[-1]
=> 6

Adding and removing elements

>> chores = ["laundry", "pay rent"]

# Adding an element with the append operator
>> chores << "dishes"
=> ["laundry", "pay rent", "dishes"]

# Removing an element by it's value (probably slow)
>> chores.delete("laundry")
=> ["pay rent", "dishes"]

# Removing by an index (much faster)
>> chores.delete_at(1)
=> ["pay rent"]

# Combining arrays
>> ian_chores = ["grade quizzes", "create slides"]
>> kevin_chores = ["take photos", "say lekker"]
>> ix_chores = ian_chores + kevin_chores
=> ["grade quizzes", "create slides", "take photos", "say lekker"]

# What about the append operator?
>> ix_chores << ["grade quizzes", "make popcorn"]
=> ["grade quizzes", "create slides", "take photos", "say lekker", ["buy staples", "make popcorn"]]
# Oops! It inserts it, not appends it!

# What if we only want unique elements?
>> ix_chores = ["grade quizzes", "grade quizzes", "create slides", "create slides"]
>> ix_chores.uniq
=> ["grade quizzes", "create slides"]

Common Array Methods

Symbols

Symbols are just a simple way of using strings as keys. Strings are human readable, but they're slow and using a lot of them is not memory efficient.

Symbols act as human-readable ways of using strings effectively.

:this_is_a_symbol

Rails uses symbols a lot!

Hashes

Hashes are a list like arrays, except that they don't use indeces for their elements. They use keys, usually symbols.

A hash maps a set of unique keys to values. The elements of a hash are also referred to as a key/value pair.

# Hashes usually use symbols
>> bill = {:name=>"Bill Door", :height=>150, :eyes=>"blue", :hair=>"red"}

# Ruby programmers prefer shorthand symbols because it's easier to type and read
>> bill = {name: "Bill Door", height: 150, eyes: "blue", hair: "red"}

# Accessing elements works like Arrays, except with keys
# Keys are ALWAYS unique! Anything you do to a key changes only that key.
# Hashes don't allow duplicate keys.
>> bill[:name]
=> "Bill Door"

# a String and a Symbol are different!
>> bill[:name]
=> "Bill Door"
>> bill["name"]
=> nil

# In RAILS though, they are the same!
# begin Rails only
>> bill[:name]
=> "Bill Door"
>> bill["name"]
=> "Bill Door"
# end Rails only

# Changing elements is the same as an Array
>> bill[:eyes] = "green"
>> bill[:eyes]
=> "green"

# Adding new elements is the same
>> bill[:shoes] = "Air Jordans"
>> bill[:shoes]
=> "Air Jordans"

# Deleting elements *always* searches by key
>> bill.delete :shoes
=> {:name=>"Bill Door", :height=>150, :eyes=>"blue", :hair=>"red"}

# Accessing list of keys
>> bill.keys
=> [:name, :height, :eyes, :hair, :shoes]

# Check if a hash has a key
>> bill.key? :eyes
=> true

# You SHOULD NEVER search a hash by value, that's EXTREMELY SLOW
# Hashes are designed when you need very fast access to data, using keys.
# But you can get a list of values of a hash! This is rarely useful.
>> bill.values
=> ["Bill Door", 150, "green", "red", "air jordans"]

Passing hashes as arguments

Ruby allows you to ignore the curly brackets if you are passing a hash as the last argument to a method.

def feed_me_hashes(hash)
  "YUM! I ate all these keys: #{hash.keys}"
end

# This is technically correct
feed_me_hashes({cheese: 'gruyere', meat: 'ostrich'})
=> "YUM! I ate all these keys: [:cheese, :meat]"

# But you can skip the curly brackets because it's the last argument.
# Rails uses this a lot!
feed_me_hashes(salad: 'Caesar', soda: 'Pepsi')
=> "YUM! I ate all these keys: [:salad, :soda]"

When Ruby allows you to be lazy, this is called syntactic sugar.

Useful Hash methods

Blocks

A block is code that acts like an unnamed method, as if you were passing it to a method as an argument.

Let's see an example in JavaScript to understand why blocks are so useful in Ruby.

// This is JavaScript
function console_print(element) {
  console.log(element)
}

var cars = ["Saab", "Volvo", "BMW"];
cars.forEach(console_print);

In JavaScript, you have to create a separate function (method) away from the context of your code. That means someone reading your code has to understand what the function is, away from where they're reading it. This can be error prone, as someone might come along and change the function, or various other challenges.

Ruby allows you to create a block, which acts like you are passing in a function. Your code is in the same place as where you're using it, instead of somewhere else. This allows you to write code more easily, and read it more easily later.

cars = ["Saab", "Volvo", "BMW"];

# Instead of this (not valid Ruby)
def console_print(car)
  puts car
end
cars.each(console_print)

# Use a block to print all the cars, all in one place
# |car| is the arguments
cars.each do |car|
  puts car
end

This is most commonly used with enumeration.

Enumeration

Enumeration is when you loop through all of the elements of a list and do something with them.

each

each loops through a list and does something to each element, then returns the original list.

cars = ["Saab", "Volvo", "BMW"];

cars.each do |car|
  puts car
end

each_with_index

each_with_index works just like each, except that it also passes in the index of the element.

cars = ["Saab", "Volvo", "BMW"];

cars.each_with_index do |car, index|
  puts "Car #{index}: #{car}"
end

map

map loops through a list and returns a new list based on the return value of the block. This allows you to transform lists into something different.

cars = ["Saab", "Volvo", "BMW"];

reversed_cars = cars.map do |car|
  car.upcase.reverse
end
=> ["BAAS", "OVLOV", "WMB"]