Introduction to Programming in Ruby

← Use arrow keys →

You are here: Intro to Ruby

Today:
Basics of Ruby
Tomorrow:
RubyGems, working with data and scraping

Day 1 Schedule

1. Set up the room
2. Variables, types, conditionals, iteration
3. Methods and classes

Set up the room.

$ ruby yourfile.rb
  
$ irb
irb(main):001:0> puts "hello"
hello
=> nil
  
$ irb
irb(main):001:0> require './my_script'
MyLibrary
=> MyLibrary
  

Intro.

Programming is 3 things

Assignment, Conditional, Iteration

Assignment

name = "Al"

Conditional

if name == "Al"
  puts "Hello #{name}"
end

Iteration

names = ["Al", "Joe", "Sally"]
names.each do |name|
  puts "#{name} is here!"
end

Let's go!

Strings & Integers

address  = 155
street   = "6th Ave"
city     = "New York"
state    = "NY"
zip_code = "10013"
city + ", " + state
#=> "New York, NY"
"#{city}, #{state}"
#=> "New York, NY"
"#{address} #{street}"
#=> "155 6th Ave"
address + " " + street
#=> TypeError: String can't be coerced into Fixnum
  from (irb):10:in `+'
  from (irb):10
address.to_s + " " + street
#=> "155 6th Ave"

Type coercion

5.to_s    #=> "5"
"5".to_i  #=> 5
5.to_f    #=> 5.0
str = ""
str << city
str << ", "
str << state

#=> "New York, NY"
str = ""
str += city
str += ", "
str += state

#=> "New York, NY"
address += address
#=> 310
= vs. ==
a = 3
b = 3

a == b
#=> true
a = 3
b = 3

if a == b
  puts 'they match'
end
a = 1
b = 2

if a != b
  puts "They don't match!"
end
a = 3
b = 3

if a == b
  puts "they match"
else
  puts "they don't match"
end

Arrays & Hashes

[]
students = ["Amy", "Joe", "Sally", "Harry"]
students.pop
#=> "Harry"

students
#=> ["Amy", "Joe", "Sally"]
students.shift
#=> "Amy"

students
#=> ["Joe", "Sally"]

What else can I do?

(students.methods - Object.methods).sort
#=> [:&, :*, :+, :-, :<<, :[], :[]=, :all?, :any?, :assoc, ...]

Getting help

students << "Amy"
#=> ["Joe", "Sally", "Amy"]
students[0]
#=> "Joe"
students[1]
#=> ?
students[1]
#=> "Sally"
person = {}
person = {}
person[:first_name] = "Al"
person[:last_name]  = "Shaw"
person = {
  :first_name => "Al",
  :last_name  => "Shaw"
}
person[:first_name]
#=> "Al"
people = []
people.push(person)
#=> [{:first_name=>"Al", :last_name=>"Shaw"}]

Iteration

students.each do |student|
  puts "#{student} is in class today"
end

#=>
Joe is in class today
Sally is in class today
Amy is in class today
even_numbers = [2, 4, 6, 8, 10]
odd_numbers  = []
# how can we programmatically 
# fill up this array with odd numbers
even_numbers.each do |n|
  odd_numbers << n + 1
end

odd_numbers
#=> [3, 5, 7, 9, 11]
even_numbers.map do |n|
  n + 1
end
#=> [3, 5, 7, 9, 11]
even_numbers.map { |n| n + 1 }
#=> [3, 5, 7, 9, 11]
even_numbers
#=> [2, 4, 6, 8, 10]

even_numbers.map! { |n| n + 1 }
#=> [3, 5, 7, 9, 11]

Methods

def add(a, b)
  a + b
end
add(1, 2)
#=> 3
def full_address(address, street, state, zip)
  "#{address} #{street}, #{state}, {zip}"
end
# Who's in class today?
def is_in_class(student)
  attendance = {
    "Amy"   => true,
    "Joe"   => true,
    "Sally" => false,
    "Harry" => true
  }

  if attendance[student]
    "here"
  else
    "absent"
  end
end
# Try it
is_in_class("Sally")
#=> Absent
is_in_class("Joe")
#=> Here

Everything in Ruby is an object

1.+(2)
#=> 3
class Fixnum
  def +(num)
    self - num
  end
end
1 + 2
#=> -1

I just blew your mind

A useful example:

[1,2,3].sum
NoMethodError: undefined method `sum' for [1, 2, 3]:Array
  from (irb):1
class Array
  def sum
    self.reduce(0) {|memo, it| memo += it; memo}
  end
end
[1,2,3].sum
#=> 6

Classes are factories for objects

class Student
  attr_reader :name, :grade
  def initialize(name, grade)
    @name  = name
    @grade = grade
  end
end
s = Student.new("Sally", 8)
#=> #<Student:0x1052aca28 @name="Sally", @grade=8>

s.name
#=> "Sally"

Why should I care?

Let's build a system that assigns students to teachers!

require './1_registrar'
# 1_registrar.rb
class Student
  attr_reader :name, :grade, :teacher
  def initialize(name, grade)
    @name  = name
    @grade = grade
  end

  @@teachers = {
    "Shanahan"  => 5,
    "Kovacek"   => 5,
    "Streich"   => 5,
    "Ankunding" => 5,
    "Marks"     => 5
  }

  def assign_teacher
    @@teachers.each do |t_name, slots|
      if slots > 0
        @teacher = t_name
        @@teachers[t_name] -= 1
        puts "#{@name} is assigned to #{t_name}"
        puts "#{t_name} now has #{slots - 1} slots left"
        return
      end
    end
  end
end
s = Student.new("Sally", 8)
#=> #<Student:0x1052aca28 @name="Sally", @grade=8>

s.assign_teacher
Sally is assigned to Streich
Streich now has 4 slots left

s.teacher
#=> "Streich"

How could we change this to randomly assign teachers?

# 2_registrar_random.rb
class Student
  attr_reader :name, :grade, :teacher
  def initialize(name, grade)
    @name  = name
    @grade = grade
  end

  @@teachers = {
    "Shanahan"  => 5,
    "Kovacek"   => 5,
    "Streich"   => 5,
    "Ankunding" => 5,
    "Marks"     => 5
  }

  def random_list
    @@teachers.keys.shuffle
  end

  def assign_teacher
    random_list.each do |t_name|
      slots = @@teachers[t_name]
      if slots > 0
        @teacher = t_name
        @@teachers[t_name] -= 1
        puts "#{@name} is assigned to #{t_name}"
        puts "#{t_name} now has #{slots - 1} slots left"
        return
      end
    end
  end
end
s = Student.new("Sally", 8)
#=> #<Student:0x007facfb97e870 @name="Sally", @grade=8>

s.assign_teacher
Sally is assigned to Ankunding
Ankunding now has 4 slots left

s.teacher
#=> "Ankunding"

There are still two things wrong with this code. What are they?

1. What happens when all the slots run out?
2. You can assign the same student to another teacher

Let's handle running out of slots

# 3_registrar_random_bugfix.rb
class Student
  attr_reader :name, :grade, :teacher
  def initialize(name, grade)
    @name  = name
    @grade = grade
  end

  @@teachers = {
    "Shanahan"  => 5,
    "Kovacek"   => 5,
    "Streich"   => 5,
    "Ankunding" => 5,
    "Marks"     => 5
  }

  def random_list
    @@teachers.keys.shuffle
  end

  def assign_teacher
    random_list.each_with_index do |t_name, idx|
      slots = @@teachers[t_name]
      if slots > 0
        @teacher = t_name
        @@teachers[t_name] -= 1
        puts "#{@name} is assigned to #{t_name}"
        puts "#{t_name} now has #{slots - 1} slots left"
        return
      end
      if idx == random_list.length - 1
        puts "There's no room for #{@name}"
      end
    end
  end
end

To test this, we'll use a hint from stuff we'll learn about tomorrow

RubyGems are bits of code that add functionality to Ruby

$ gem install faker
Successfully installed faker-1.6.3
1 gem installed
require 'faker'
require './3_registrar_random_bugfix'
students = []
(0.upto(25)).each do |s| 
  students << Student.new(Faker::Name.name, 8)
end
students
=> [#<Student:0x007fc7cbfca018 @name="Ms. Magdalena Lowe", @grade=8>, 
#<Student:0x007fc7cbfc96e0 @name="Alivia Wintheiser", 
@grade=8>, #<Student:0x007fc7cbfc8da8 @name="Jeanie Marks", @grade=8>, 
...]
students.each do |s|
  s.assign_teacher
end

Ms. Magdalena Lowe is assigned to Marks
Marks now has 4 slots left
Alivia Wintheiser is assigned to Shanahan
Shanahan now has 4 slots left
Jeanie Marks is assigned to Kovacek
Kovacek now has 4 slots left
Conrad Durgan is assigned to Kovacek
Kovacek now has 3 slots left
Kenneth Schaden is assigned to Marks
Marks now has 3 slots left
Bennett Olson I is assigned to Ankunding
Ankunding now has 4 slots left
Bud Mante V is assigned to Streich
Streich now has 4 slots left
Manuela Cole is assigned to Shanahan
Shanahan now has 3 slots left
Arch Schaden is assigned to Kovacek
Kovacek now has 2 slots left
Ophelia Carter is assigned to Shanahan
Shanahan now has 2 slots left
Lori Kovacek is assigned to Streich
Streich now has 3 slots left
Emmie Nienow DDS is assigned to Ankunding
Ankunding now has 3 slots left
Cecil Harvey is assigned to Kovacek
Kovacek now has 1 slots left
Clay Weber is assigned to Ankunding
Ankunding now has 2 slots left
Martin Wisoky is assigned to Marks
Marks now has 2 slots left
Orion Wehner is assigned to Kovacek
Kovacek now has 0 slots left
Miss Jo Welch is assigned to Ankunding
Ankunding now has 1 slots left
Karolann Grant is assigned to Ankunding
Ankunding now has 0 slots left
Jerry Franecki is assigned to Shanahan
Shanahan now has 1 slots left
Mrs. Taya Harris is assigned to Marks
Marks now has 1 slots left
Dusty Simonis V is assigned to Streich
Streich now has 2 slots left
Alexanne Ryan is assigned to Shanahan
Shanahan now has 0 slots left
Alfreda Effertz is assigned to Marks
Marks now has 0 slots left
Kaylie Buckridge IV is assigned to Streich
Streich now has 1 slots left
Kenton Rogahn I is assigned to Streich
Streich now has 0 slots left
There's no room for Rodger Wilkinson II
There's no room for Rodger Wilkinson II

If we really want Ruby to blow up when there's no more slots

if idx == random_list.length - 1
  raise RuntimeError, "There's no room for #{@name}"
end
Streich now has 0 slots left
RuntimeError: There's no room for Guiseppe Leffler
  from /Users/ashaw/rails/data-institute/code/3_registrar_random_bugfix.rb:31:in `block in assign_teacher'
  from /Users/ashaw/rails/data-institute/code/3_registrar_random_bugfix.rb:21:in `each'
  from /Users/ashaw/rails/data-institute/code/3_registrar_random_bugfix.rb:21:in `each_with_index'
  from /Users/ashaw/rails/data-institute/code/3_registrar_random_bugfix.rb:21:in `assign_teacher'
  from (irb):6:in `block in irb_binding'
  from (irb):6:in `each'
  from (irb):6
  from /usr/local/var/rbenv/versions/2.2.2/bin/irb:11:in `<main>'

Now that we know how to use exceptions, how do we make sure the same student doesn't get assigned to two teachers?

First, let's do some refactoring

Let's keep track of which teachers have which students

#4_registrar_validated_step1.rb
class Student
  MAX_SLOTS = 5

  attr_reader :name, :grade, :teacher
  def initialize(name, grade)
    @name  = name
    @grade = grade
  end

  @@teachers = {
    "Shanahan"  => [],
    "Kovacek"   => [],
    "Streich"   => [],
    "Ankunding" => [],
    "Marks"     => []
  }

  def random_list
    @@teachers.keys.shuffle
  end

  def teachers
    @@teachers
  end

  def assign_teacher
    random_list.each_with_index do |t_name, idx|
      slots = @@teachers[t_name]
      if slots.length < MAX_SLOTS
        @teacher = t_name
        slots << @name
        puts "#{@name} is assigned to #{t_name}"
        puts "#{t_name} now has #{MAX_SLOTS - slots.length} slots left"
        return
      end
      if idx == random_list.length - 1
        raise RuntimeError, "There's no room for #{@name}"
      end
    end
  end
end
require './4_registrar_validated_step1.rb'

s = Student.new("Al", 8)
s.assign_teacher
Al is assigned to Shanahan
Shanahan now has 4 slots left
=> nil
s.teachers
=> {"Shanahan"=>["Al"], "Kovacek"=>[], "Streich"=>[], "Ankunding"=>[], "Marks"=>[]}

Right now, we can keep assigning `s` over and over again until all teachers are full

(0.upto(25)).each {|i| s.assign_teacher }
...
s.teachers
=> {"Shanahan"=>["Al", "Al", "Al", "Al", "Al"], 
"Kovacek"=>["Al", "Al", "Al", "Al", "Al"], 
"Streich"=>["Al", "Al", "Al", "Al", "Al"], 
"Ankunding"=>["Al", "Al", "Al", "Al", "Al"], 
"Marks"=>["Al", "Al", "Al", "Al", "Al"]}

How can we prevent this?

#5_registrar_validated_step2.rb
class Student
  MAX_SLOTS = 5

  attr_reader :name, :grade, :teacher
  def initialize(name, grade)
    @name  = name
    @grade = grade
    @assigned = false
  end

  @@teachers = {
    "Shanahan"  => [],
    "Kovacek"   => [],
    "Streich"   => [],
    "Ankunding" => [],
    "Marks"     => []
  }

  def random_list
    @@teachers.keys.shuffle
  end

  def teachers
    @@teachers
  end

  def assign_teacher
    raise RuntimeError, "#{@name} is already assigned" if @assigned
    random_list.each_with_index do |t_name, idx|
      slots = @@teachers[t_name]
      if slots.length < MAX_SLOTS
        @teacher = t_name
        slots << @name
        @assigned = true
        puts "#{@name} is assigned to #{t_name}"
        puts "#{t_name} now has #{MAX_SLOTS - slots.length} slots left"
        return
      end
      if idx == random_list.length - 1
        raise RuntimeError, "There's no room for #{@name}"
      end
    end
  end
end
require './5_registrar_validated_step2'

s = Student.new("Al", 8)
=> #<Student:0x007fd66abe0698 @name="Al", @grade=8, @assigned=false>
s.assign_teacher
Al is assigned to Ankunding
Ankunding now has 4 slots left

s.assign_teacher
RuntimeError: Al is already assigned
  from /Users/ashaw/rails/data-institute/code/5_registrar_validated_step2.rb:28:in `assign_teacher'
  from (irb):4
  from /usr/local/var/rbenv/versions/2.2.2/bin/irb:11:in `<main>'

Homework + Questions

Al Shaw    ProPublica Data Institute • 2016    @A_L