Ruby crash course

Posted 15 May 2017

I recently had the pleasure of doing a workshop for my non-ruby programmer colleagues. For unknown reasons I decided to write the crash-course from scratch in slide form, and now that the workshop is over I felt like persisting that work somewhere. So here goes: A ruby syntax crash course for programmers!

# This is a comment

# Variable assignment
foo = 1

# Strings can be expressed with double-quotes
foo = "I have 10 apples"

# Or single-quotes
bar = 'I eat 2 apples'

# Double-quoted strings can be interpolated
message = "I now have #{10-2} apples" # "I now have 8 apples"

# We use single-quoted strings when we don't need interpolation

# Basic arithmetics follow basic algebraic priority
2 + 8 / 2 + 2 # 8
(2 + 8) / 2 + 2 # 7

# Integers gets converted to floats only when we mix them
7 / 3 # 2
7 / 3.0 # 2.333..

# We can call methods on objects (parentheses optional)
'apple'.length # 5

# We can pass methods arguments (parantheses optional)
'apple'.sub('app', 'fidd') # "fiddle"
'apple'.sub 'app', 'fidd' # "fiddle"

# We have booleans expressions
(true || false) && true # true

# We have nil (similar to null and void)
'apple'.index('o') # nil (because the letter o is not in the string)

# Everything is truthy except `false` and `nil`
false || nil # false

# And I mean everything
!!('' && 0 && -1) # true

# We got basic control flow
if 'apple'.length == 5
  # Code here executes
else
  # Code here doesn't
end

# We can even do inline conditionals
x = 'I get assigned!' if x.nil?

# There's loops
5.times do
  x = x + 1
end

while(true) do
  # I run forever!
end

until(false) do
  # I also run forever!
end

# We can define methods
def yell_at_terminal(message)
  puts message.upcase + "!!!" # `puts` prints the message to the terminal
end

# and call them in the same scope
yell_at_terminal('I have a different opinion than you')

Objects objects objects

In ruby, everything is an object. Really. We can check for ourselves:

1.is_a?(Object) # true

false.is_a?(Object) # true

nil.is_a?(Object) # true

Object.is_a?(Object) # true

Class.is_a?(Object) # true

That means they share some methods

Object.new.methods
=> [
:instance_of?,
:public_send,
:instance_variable_get,
:instance_variable_set,
:instance_variable_defined?,
:remove_instance_variable,
:private_methods,
:kind_of?,
:instance_variables,
:tap,
:is_a?,
:extend,
:define_singleton_method,
:to_enum,
:enum_for,
:<=>,
:===,
:=~,
:!~,
:eql?,
:respond_to?,
:freeze,
:inspect,
:display,
:send,
:object_id,
:to_s,
:method,
(...)

Classes


class Animal # class names are PascalCase
  ORIGIN = 'Earth' # constants are upper case

  def breed(other_animal)
    return unless other_animal.is_a?(Animal)
    Animal.new # if nothing is explicitly returned, last expression is returned
  end

  def introduce_yourself
    "I'm a #{self.class.to_s.downcase}, I come from #{ORIGIN}"
  end
end

class Fish < Animal # Fish is a subclass of Animal
  def initialize(kind) # Initialize is a special method (constructor)
    @kind = kind # variables starting with @ are instance variables
  end

  def breed(other_fish) # fish has its own rules for breeding
    return unless other_fish.is_a?(Fish)
    Fish.new([self.kind, other_fish.kind].sample) # randomly pick a parent type
  end

  def kind
    @kind
  end
end

Instances


first_animal, second_animal = Animal.new, Animal.new # multi-assignment!
first_fish, second_fish = Fish.new('Goldfish'), Fish.new('Shark')

baby_animal = first_animal.breed(second_animal)
baby_animal.class # Animal

baby_fish = first_first.breed(second_fish)
baby_fish.class # Fish
baby_fish.kind # 'Goldfish' or 'Shark'

baby_animal.breed(baby_fish) # nil

baby_fish.introduce_yourself # "I'm a fish, I come from Earth"

Ruby method dispatch


baby_fish.introduce_yourself
  • will first check if the Fish class has this method, but it does not
  • will then check if the parent class has it, and it does.
baby_fish.this_method_is_not_there
  • will check fish, and all it’s superclasses with no luck
  • will then check fish for a special method called method_missing
  • will check all superclasses for method_missing
  • will find it on BasicObject, which tells ruby to raise a NoMethodError

method_missing is one of the most common (and most dangerous!) ways to meta-program ruby.


Basic data-structures


# We have arrays!
things = ['fizz','buzz']
things.length # 2
things.first # "fizz"
things.last # "buzz"

more_things = things + things # ["fizz", "buzz", "fizz", "buzz"]

# We can iterate over arrays
more_things.each do |thing|
  puts thing
end

# We can access elements with square brackets
more_things[1] # "buzz" (0-indexed)

# We have hashes (other languages calls them dicts, hashmaps, etc.)
pets = {'dog' => 'Fluffy', 'cat' => 'Princess Sprinkles'}
pets.keys # ['dog', 'cat']
pets.values # ["Fluffy", "Princess Sprinkles"]

# We can get values by keys
pets['dog'] # "Fluffy"

Easier hashes


Since we use hashes a LOT in ruby, there’s a few things to make them easier to work with

# Ruby has symbols
x = :herp # symbols start with :

# Any two identical symbols are actually the same instance
:herp.object_id == :herp.object_id # true

# If we use symbols as hash keys, we can avoid the hash-rockets (=>)
{:herp => 'derp'} == {herp: 'derp'} # true

herpderp = {herp: 'derp'}

herpderp[:herp] # "derp"

# WARNING: the {key: 'value'} syntax only works in ruby 1.9+

Blocks


# Blocks are chunks of code we give as method arguments
# We already saw some
5.times do
  # this is a block with do/end
end

5.times {
  # we can also use brackets
}

(1..5).each do |number|
  # this block takes an argument, and makes it available as `number`
end

[1, 2, 3, 4, 5].map {|element| element.even?} # [false, true, false, true, false]

# We can define a method that accepts a block like this
def powerful_method
  block_returned = yield # yield executes a given block
  "The block returned #{block_returned}"
end

powerful_method do
  "a meaningful answer!"
end

# "The block returned a meaningful answer!"

That was all!