Ruby Intro

SENG2021

Deploying With Heroku

Heroku is a pioneer in what we now call Platform-As-A-Service. It’s a little on the expensive side to scale on it, but its great for developing with as there is a freebie tier and its service makes deploying to the internet a breeze. Be aware however that if you need a database, PostgreSQL is your only option.

The newcomer in town is Appfog. They’re a lot cheaper than Heroku and supports MySQL in addition to PostgreSQL, but I haven’t had the chance to check them out yet.

Step 1

Sign up to Heroku

Step 2

Install the Heroku Toolbelt

Step 3

In the root folder of your application, type in the following commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Initialize a git repository
git init

# Login to heroku (enter your username and password as prompted, generate ssh key if required)
heroku login

# Create a new app on heroku
# Make note of the output of this command
# The http url is where your application will be available - http://<app name>.heroku.com
# The git url is where you will be deploying the application to - git://heroku.com:<app name>.git
# The last line should say 'Git remote heroku added'
# If it doesn't, do 'heroku git:remote -a <app name>'
heroku create

# Add everything to git staging
git add .

# Commit the repository
git commit -m "initial commit"

# Push to Heroku for the first time
git push heroku master

# Ensure that only 1 web dyno will be running
heroku ps:scale web=1

Step 4

Your app should now be available in the http url mentioned in step 3. If there are any problems, you can watch your application’s live logging by using heroku logs -t. As usual, google your issues :) There are some relevant links to documentation below too.

  1. Heroku Signup
  2. Heroku Toolbelt
  3. Heroku Quickstart
  4. Heroku Rails4
  5. Heroku Rails3
  6. Heroku Billing
  7. Appfog Pricing
  8. Demo on Heroku

Prettifying With Bootstrap

Since Twitter open sourced their CSS framework several years ago, Bootstrap has become the benchmark for CSS. As a terrible designer, I’ve very much relied on Bootstrap to make my web pages decent looking, not great – just OK.

I’ve opted for a slightly older version (2.3.2) here since the new version 3 is not yet stable at the time of writing. The documentation for 2.3.2 can be found here. Regardless, there’s only 3 files to change.

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
  <title>TwitterApp</title>
  <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css" rel="stylesheet">
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>
  <div class="container">
    <div class="navbar">
      <div class="navbar-inner">
        <a class="brand" href="/">Twitter App</a>
        <ul class="nav">
          <li><a href="/proposals">JB Proposals</a></li>
          <li><a href="/ausvotes">#ausvotes</a></li>
        </ul>
      </div>
    </div>
<%= yield %>
  </div>
</body>
</html>

And add class="table" to the 2 table tags

app/views/twitter/ausvotes.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>Last 200 Tweets with #ausvotes</h1>

<table class="table">
  <tr>
    <th>User</th>
    <th>Text</th>
  </tr>
<% @tweets.each do |tweet| %>
  <tr>
    <td><%= tweet.user.name %></td>
    <td><%= tweet.text %></td>
  </tr>
<% end %>
</table>
app/views/twitter/proposals.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>Last 200 Justin Bieber Proposals</h1>

<table class="table">
  <tr>
    <th>User</th>
    <th>Text</th>
  </tr>
<% @tweets.each do |tweet| %>
  <tr>
    <td><%= tweet.user.name %></td>
    <td><%= tweet.text %></td>
  </tr>
<% end %>
</table>
  1. Twitter Bootstrap

Ruby on Rails

As I mentioned in the very first post, Rails has been credited with Ruby’s sudden, meteoric rise in popularity. It’s not the only web application framework for Ruby, but it remains by far the most popular to this day. If you’re interested in a career in web development and want to stay away from corporate enterprises, this is a great way to go about it.

Rails emphasises the Model-View-Controller principle, and if you’re not familiar with the concept, it’s essentially the separation of the data, to the logic of processing the data, and the display of the processed results (links at the bottom for more details). The application I’ll be demoing will ignore the Model part however. This is a very simple application will retrieve some data from Twitter, same as the previous post, and display it on a webpage.

As always, start by installing it…gem install rails

Scaffolding

One of the big features of rails is scaffolding – automatically generating common parts of an application. This is the first step of development. Here, we’re asking rails to scaffold the project without support for a database.

1
2
3
cd ~
rails new twitter_app -O
cd twitter_app

The rails generator has created a lot of new files – about 20 in fact – but there aren’t that many we need to worry about.

Before you begin editing the files, copy the twitter configuration file from the previous pages to the config/initializers directory. Files in this directory are run when the server is started.

Gemfile

This file basically tells the Ruby environment what gems are required to run the the application. Our app only needs the twitter gem so just add that.

Gemfile
1
2
3
source 'https://rubygems.org'
gem 'twitter'
gem 'rails', '4.0.0'

Source specifies where it should look for the gems if they were to be installed. We can also specify the version of a gem we want in order to ensure that things work as we expect; don’t want updates to break the application. Everything else in the file is optional.

The Twitter controller

Finally on to the logic… rails generate controller twitter.

The code here is almost identical to that from the previous Twitter post. Now, we’re just keeping all the tweets in an array instead of just printing it. At the end of the processing, Rails will automatically look for the template views/<controller name>/<function name>.* unless you specify which HTML template to use with the render. This is displayed within views/layouts/application.html.erb.

app/controllers/twitter_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class TwitterController < ApplicationController

  def proposals
    @tweets = []
    max_id = -1
    for i in (0..1)
      t = Twitter.search("to:justinbieber marry me", :count => 100, :result_type => "recent")
      t.statuses.each do | tweet |
        @tweets.push tweet
      end
      max_id = t.next_results[:max_id]
    end
  end

  def ausvotes
    @tweets = []
    max_id = -1
    for i in (0..1)
      t = Twitter.search("#ausvotes -rt", :count => 100, :result_type => "recent", :max_id => max_id)
      t.statuses.each do | tweet |
        @tweets.push tweet
      end
      max_id = t.next_results[:max_id]
    end
  end
end

The Rails Router

It’s advisable that you leave the commented out lines here until you get the hang of routing, just for reference.

config/routes.rb
1
2
3
4
5
TwitterApp::Application.routes.draw do
  root 'twitter#proposals'
  get 'proposals' => 'twitter#proposals'
  get 'ausvotes' => 'twitter#ausvotes'
end

The first line in the block points the root path, / to the proposals function in the twitter controller. The second and third points /proposals and /ausvotes paths to their respective functions in the same controller.

Justin Bieber Proposals page

If you’re familiar with JSP, this is very much the same – HTML mixed in with some code. Here, we’re making a simple table but we’re creating a row for each tweet.

app/views/twitter/proposals.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>Last 200 Justin Bieber Proposals</h1>

<table>
  <tr>
    <th>User</th>
    <th>Text</th>
  </tr>
<% @tweets.each do |tweet| %>
  <tr>
    <td><%= tweet.user.name %></td>
    <td><%= tweet.text %></td>
  </tr>
<% end %>
</table>

#ausvotes page

Identical to the above. Do try to refactor this :)

app/views/twitter/ausvotes.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<h1>Last 200 Tweets with #ausvotes</h1>

<table>
  <tr>
    <th>User</th>
    <th>Text</th>
  </tr>
<% @tweets.each do |tweet| %>
  <tr>
    <td><%= tweet.user.name %></td>
    <td><%= tweet.text %></td>
  </tr>
<% end %>
</table>

Deploying with WEBrick

WEBrick is a very simple Rails web server, perfect for dev testing. Just go to the root directory of the application and type in rails server. WEBrick listens to port 3000 by default, but look carefully at its output if you come across any problems. The JB Proposals page will be available in @ http://0.0.0.0:3000 and http://0.0.0.0:3000/proposals while the #ausvotes page will be available at http://0.0.0.0:3000/ausvotes.

1
2
3
4
5
6
7
8
$ rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2013-08-05 22:13:15] INFO  WEBrick 1.3.1
[2013-08-05 22:13:15] INFO  ruby 2.0.0 (2013-06-27) [x86_64-darwin12.4.0]
[2013-08-05 22:13:15] INFO  WEBrick::HTTPServer#start: pid=25428 port=3000

Adding links between the 2 pages

We want the user to be able to easily manuever between the 2 pages so let’s put hyperlinks to the 2 pages at the top of every page (just add the 2 anchor bits in the html below).

app/views/layouts/application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
  <title>TwitterApp</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>
  <a href="/proposals">JB Proposals</a>
  <a href="/ausvotes">#ausvotes</a>
<%= yield %>

</body>
</html>

Just refresh the page to take affect. Notice that this change did not require you to restart WEBrick. Some changes, eg. config changes, will require you to do so.

  1. Wikipedia on MVC
  2. Jeff Atwood on MVC

Accessing Twitter’s API

Since mid June this year, Twitter has forced users to use OAuth to authenticate and access its API. You can no longer access its data in a trivial way like the GitHub example before. You must get 4 keys from Twitter’s developer page: Consumer key, Consumer secret, Access token and Access secret – don’t need to know what they mean yet, but be sure that the 2 secret keys are not shared. OAuth is a real pain. These 4 keys won’t give you access. They’ll let you get 3 more one use keys which you can then use to access the API.

Fortunately for you as a Ruby user, there are two libraries that will do all the menial work for you. Twitter is a conveniently named library to access the standard Twitter API (it is not developed by Twitter Inc) while the other, TweetStream is designed to use Twitter’s streaming API (kind of like email pushing on your phone). It’s unlikely that you’ll need to use the streaming API for this assignment so I won’t be showing you TweetStream.

Twitter Gem

Install the twitter gem with gem install twitter and make the following config file, replacing the upper case strings with the relevant keys.

twitter_config.rb
1
2
3
4
5
6
Twitter.configure do |config|
  config.consumer_key = YOUR_CONSUMER_KEY
  config.consumer_secret = YOUR_CONSUMER_SECRET
  config.oauth_token = YOUR_OAUTH_TOKEN
  config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
end

There are some usage examples here, but there’s plenty more functionality so do refer to the documentation.

1
2
require 'twitter'
load 'twitter_config.rb'

Lets find the last person to have proposed to Justin Bieber

1
2
3
4
5
6
7
result = Twitter.search("to:justinbieber marry me", :count => 1, :result_type => "recent")

result.class
# Twitter::SearchResults

result.statuses[0].user.screen_name
# 4ever_beliebing

Just to give another example, I’m going to get the last tweet from each of the users I’m stalking.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
f = Twitter.friends
f.class
# Twitter::Cursor
# -- http://rdoc.info/gems/twitter/Twitter/Cursor

f.collection.class
# Array

f.collection[0].class
# Twitter::User
# -- http://rdoc.info/gems/twitter/Twitter/User

f.collection[0].name
# "John Oliver"

f.collection[0].status
f.collection[0].status.class
# Twitter::Tweet
# -- http://rdoc.info/gems/twitter/Twitter/Tweet

f.collection[0].status.text
# "Mugabe is the Harlem Globetrotters of democracy. His winning record is undeniably impressive, but he doesn't really play by the rules."

for user in f.collection.each
  puts "#{user.name} said '#{user.status.text}'"
end

Because we don’t care about the return value, let’s not do this in irb

last_twitter_status.rb
1
2
3
4
5
6
7
8
#!/usr/bin/env ruby
require 'twitter'
load 'twitter_config.rb'

f = Twitter.friends
for user in f.collection.each
  puts "#{user.name} said '#{user.status.text}'"
end

But there’s still a problem, I’m following more users than this.

This is because Twitter paginates the results to 20 by default. So if there is more than 20 records, you’ll have to iterate through each page to get all the results.

last_twitter_status_iterated.rb
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env ruby
require 'twitter'
load 'twitter_config.rb'

cursor = -1
while cursor != 0 do
  f = Twitter.friends :cursor => cursor
  for user in f.collection.each
    puts "#{user.name} said '#{user.status.text}'"
  end
  cursor = f.next_cursor
end

Just to a more relevant example, this is to get the last 200 tweets with #ausvotes excluding retweets. Search results pagination work slightly different to friends – refer to documentation!!

Note that the count parameter refers to the number you want per page, although the maximum is 100.

recent_ausvotes.rb
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env ruby
require 'twitter'
load 'twitter_config.rb'

max_id = -1
for i in (0..1)
  t = Twitter.search("#ausvotes -rt", :count => 100, :result_type => "recent", :max_id => max_id)
  t.statuses.each do | tweet |
    puts "#{tweet.user.name} said #{tweet.text}"
  end
  max_id = t.next_results[:max_id]
end
  1. RubyGem – Twitter
  2. RubyGem – Tweetstream
  3. Twitter API Authentication Documentation
  4. Twitter API
  5. Twitter Developer Apps
  6. RDoc Twitter

Consuming JSON REST Resource Through HTTP

As you saw in the previous post, XML is an overly verbose format so in the last few years, what we call JSON (JavaScript Object Notation) has come to dominate web communication. Ideally I’d be demonstrating this with Twitter’s API since you’re likely to use it for this assignment, but since mid June this year, the Twitter API requires OAuth to access. I will be address this in the next post.

If you’ve been writing Javascript, you love JSON. If you haven’t, you soon will. The following is a sample of a JSON formatted file. Note that spaces, tabs and line breaks are all optional. I’ve just added those to make it easier to read.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "id": 32,
  "name": "Mother Duck",
  "children":[
    {
      "id": 12345,
      "name": "Claire"
    },
    {
      "id": 12372,
      "name": "Daniel"
    }
  ],
  "isAlive": true
}

That’s all there is to JSON, very simple, very clear. {} encapsulates object/map/hash, while [] encapsulates an array, both of which can be nested infinitely. Otherwise only strings, numbers, booleans and null are supported values.

Github API

GitHub is an online source repository, extremely popular with open source projects. You won’t be using any data from it for your projects, but it’s the first public JSON API that came across my mind. I’m sure you’ll be using it somewhere in your career though. For this demonstration, I’ll just be using the most simple of calls, retrieving the details of the GitHub user octocat. The documentation for GitHub’s API is available here.

The easiest way to access this data, is to simply open a web browser and go to https://api.github.com/users/octocat. Next step further is accessing it through cURL.. curl https://api.github.com/users/octocat. Let’s do it in Ruby.

Retrieving with Net/HTTP and parsing with json

Net/HTTP is a standard Ruby library, and it’ll be your interface to the internet. JSON is a simple parser and it’s also a standard library.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'net/http'
require 'json'

uri = URI('https://api.github.com/users/octocat')
response = Net::HTTP.get uri
octocat = JSON.parse response

octocat.keys
# ["login", "id", "avatar_url", "gravatar_id", "url", "html_url", "followers_url", "following_url", "gists_url", "starred_url", "subscriptions_url", "organizations_url", "repos_url", "events_url", "received_events_url", "type", "name", "company", "blog", "location", "email", "hireable", "bio", "public_repos", "followers", "following", "created_at", "updated_at", "public_gists"]

octocat["followers"]
# 398
octocat["public_repos"]
# 3
octocat["hireable"]
# false

I had some issues here with openssl on the Mac, but the update instructions with Homebrew found here worked perfectly.

  1. JSON Examples
  2. Ruby Net/HTTP
  3. Rubygem json

Parsing XML With XMLSimple

For the purpose of this exercise, I’ve downloaded and extracted a zip file from the AEC’s site wget wreckbea.ch/aec-mediafeed-results-standard-light-15508.xml Ruby has an XML parser called REXML in its standard library, but it’s known to be very slow – Some 50 times slower than Nokogiri. I would love to demonstrate Nokogiri, but unfortunately it’s more complex to use than XmlSimple. XmlSimple parses the data into a native Ruby hash whereas Nokogiri has its own set of classes.

Third Party libraries

Third party libraries in Ruby are referred to as gems. Gem is an executable that comes with Ruby. Tell it to install, along with the name of the gem and it will download and install the gem that you want as well as all its dependencies…gem install xml-simple

Let’s Parse!

1
2
3
require 'xmlsimple'
xml = File.read 'aec-mediafeed-results-standard-light-15508.xml';0
data = XmlSimple.xml_in xml

Parsing XML takes a little while, and XmlSimple isn’t the most efficient of parsers. If speed is a concern at all, you should definitely look into Nokogiri.

Once it’s done we can see what’s in there one step at a time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data.keys
# ["Id", "Created", "SchemaVersion", "EmlVersion", "xmlns", "xmlns:eml", "xmlns:ds", "xmlns:xal", "xmlns:xnl", "xmlns:ts", "xmlns:xs", "xs:schemaLocation", "ManagingAuthority", "MessageLanguage", "MessageGenerator", "Cycle", "Results"]

data["Results"].keys
# NoMethodError

data["Results"].class
# Array

data["Results"][0].class
# Hash

data["Results"][0]["Election"][0]["House"][0]["Contests"][0]["Contest"][0].keys
# ["Projected", "ContestIdentifier", "Enrolment", "FirstPreferences", "TwoCandidatePreferred", "TwoPartyPreferred", "PollingPlaces"]

data["Results"][0]["Election"][0]["House"][0]["Contests"][0]["Contest"][0]["TwoPartyPreferred"][0]["Coalition"][0]["Votes"][0]
# 0

data["Results"][0]["Election"][0]["House"][0]["Contests"][0]["Contest"][0]["Enrolment"][0]
# 124215

Classes

Ruby is a Object Oriented language so it’s no less than intuitive to have classes. The following code block shows the basic syntax of a Ruby class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Counter
  @@classCount = 0
  @instanceCount

  def initialize(startInstanceCount)
    @instanceCount = startInstanceCount
  end

  def icIncrement
    @instanceCount += 1
  end

  def self.ccIncrement
    @@classCount += 1
  end

  def to_s
    "classCount = #{@@classCount} and instanceCount = #{@instanceCount}"
  end
end

We have a Class variable, @@classCount. This is equivalent to the keyword static in Java. There’s an Instance variable @instanceCount.

Then there are the methods. initialize is the constructor used when you run Counter.new, icIncrement is an instance method, while self.ccIncrement is a class method – called with Counter.ccIncrement.

Finally to_s is the conversion of the class to a String, just like toString().

1
2
3
4
5
6
7
8
9
10
11
12
a = Counter.new 0
b = Counter.new 0

Counter.ccIncrement
a.icIncrement

puts a
# classCount = 1 and instanceCount = 0

b = Counter.new 5
puts b
# classCount = 1 and instanceCount = 5

Variable Scope

Scope-wise, there are 5 different types of variables in Ruby and they are simply differentiated by the first character of their name.

  1. $: Global
  2. @@: Class
  3. @: Instance
  4. [A-Z]: Constant
  5. [a-z_]: Local

Note that Constants can still be changed. The interpreter will simply issue a warning that that is the case, but the value assigning will proceed.

1
2
3
4
5
Aconstant = 1
Aconstant = 2
# warning: already initialized constant Aconstant
# warning: previous definition of Aconstant was here
# 2

Dynamic Typing

Ruby doesn’t care what class an object is, as long as it does what you want it to do. If it quacks like a duck, it is a duck.

I’ve prepared the 3 files below already. To see them, checkout the branch git checkout dynamic_typing. This code is based on the code snippets in the Ruby Cookbook which was based on Ruby 1.8

duck.rb
1
2
3
4
5
class Duck
  def quack
    'Quack!'
  end
end
humans.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Man
  def quack
    'Moo!'
  end

  def scream
    'AHHHHHH'
  end
end

class Woman
  def scream
     'AHHHH'
  end
end
make_it_quack.rb
1
2
3
def make_it_quack(duck)
  puts duck.quack
end

And back to irb. This time, run it with irb -I .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'duck'
require 'humans'
require 'make_it_quack'

d = Duck.new
m = Man.new
w = Woman.new

make_it_quack(d)
# Quack!
make_it_quack(m)
# Moo!
make_it_quack(w)
# NoMethodError: undefined method `quack' for #<Woman:0x007fb2d91ab3d0>

Unfortunately it seems like function parameters can no longer be given a type so the example isn’t the most clear. This is what it looks like in the book.

1
2
3
4
5
6
7
def make_it_quack(Duck duck)
  puts duck.quack
end

w = Woman.new
make_it_quack(w)
# TypeException: object not of type Duck

Duck Punching

So what if our ducks (Man) don’t quack? Then we punch them until it does.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
make_it_quack(m)
# Moo!

class Man
  def quack
    "Quack!"
  end
end

make_it_quack(m)
# Quack!

m.scream
# AHHHHHH

Just to be clear, we can also do the same to the Woman class.

1
2
3
4
5
6
7
8
9
10
11
make_it_quack(w)
# NoMethodError: undefined method `quack' for #<Woman:0x007fbe7b99e800>

class Woman
  def quack
    "Quack!"
  end
end

make_it_quack(w)
# Quack!

Functions

Firstly the syntax. Let’s first define a very basic function we can use for this post.

1
2
3
4
5
6
7
8
def print_stuff(str1, str2, reverse = false)
  if reverse
    puts "#{str2} and #{str1} received!"
  else
    puts "#{str1} and #{str2} received!"
  end
  str = str1 + str2
end

Calling functions

Brackets around function parameters is optional in Ruby. However, sometimes it’s useful to include them regardless for clarity’s sake.

1
2
3
4
5
print_stuff("First argument", "Second argument")
# First argument and Second argument received!

print_stuff "First argument", "Second argument"
# First argument and Second argument received!

Arguments can be made optional by giving them a default value.

1
2
3
4
5
print_stuff("First argument", "Second argument", false)
# First argument and Second argument received!

print_stuff("First argument", "Second argument", true)
# Second argument and First argument received!

Return value

As you may have already noticed, print_stuff does use return even though it does exist in Ruby and it does exactly what you’d expect. If the return value is not specified, Ruby will return the value returned in the last executed line of the block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def add(a, b)
  a + b
end

puts add(1, 2)
# 3

def add_return(a, b)
  return a + b
end

puts add_return(1,2)
# 3

def add_print(a, b)
  a + b
  print "add successful"
end

puts add_print(1,2)
# add successful

Because print "add successful" returns nil, add_print returns nil.

Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def do_something
  raise "Failed to do something"
end

do_something
# RuntimeError: Failed to do something

begin
  do_something
  puts "Done something"
rescue
  puts "Rescuing from exception"
end
# Rescuing from exception

retry will return the cursor to start of the begin block it belongs to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
i = 0
begin
  puts "#{i}"
  i += 1
  if i < 2
    do_something
  end
  puts "All done!"
rescue
  retry
end
# 0
# 1
# All done!

Also relevant later on, Closures and Metaprogramming

Hash & Symbols

Maps in the data structure sense is referred to in Ruby as Hashes. Map in Ruby is used in its functional programming definition.

Construction

1
2
3
4
5
6
7
8
9
a = { "a" => "b", 3 => "d" }
# {"a"=>"b", 3=>"d"}
b = Hash("a" => "b", 3 => "d")
# {"a"=>"b", 3=>"d"}

c = {}
d = Hash.new

e = Hash.new(0)

Testing for Equality

1
2
3
4
5
6
7
8
9
a==b
# true
a.equal? b
# false

c==d
# true
c.equal? d
# false

Retrieving from Hash

1
2
3
4
5
6
7
8
9
a["a"]
# "b"
a[3]
# "d"

a[4]
# nil
e[4]
# 0

Adding values to the Hash

1
2
3
a[5] = 6
puts a
# {"a"=>"b", 3=>"d", 5=>6}

Other useful functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
b.clear
puts b
# {}

a.empty?
# false
b.empty?
# true

a.length
# 3

a.keys
# ["a", 3, 5]
a.values
# ["b", "d", 6]

Iterating through the hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
a.each { |k,v| puts "The value for #{k} is #{v}" }
# The value for a is b
# The value for 3 is d
# The value for 5 is 

a.each_key { |k| puts "The value for #{k} is #{a[k]}" }
# The value for a is b
# The value for 3 is d
# The value for 5 is 6

a.each_value { |v| puts v }
# b
# d
# 6

a.each do |k,v|
  puts "The value for #{k} is #{v}"
end
# The value for a is b
# The value for 3 is d
# The value for 5 is 6

Symbols

Fixnum always have the same object_id, and we can tell Ruby to do that to Strings too – it’s what we call a Symbol. While a new string will be created every time the same String literal is used, the same symbol will always point to the same object in memory. This also means that Ruby’s automatic garbage collection will not recycle symbols.

1
2
3
4
5
6
7
8
9
"abc".object_id
# 70139181776180
"abc".object_id
# 70139181819060

:abc.object_id
# 1024808
:abc.object_id
# 1024808

Because of this, Symbols are the preferred way to set and get Hash elements.

Ruby Cookbook attributes this quote to Jim Weirich:

If the contents (the sequence of characters) of the object are important, use a string. If the identity of the object is important, use a symbol.

Do this..

1
2
3
4
h = Hash.new
h[:abc] = "xyz"
puts h[:abc]
# xyz

but don’t do this..

1
2
3
4
h = Hash.new
h["abc"] = :xyz
puts h["abc"]
# xyz

Loops

Like the if statements you saw 2 posts prior, there are many ways to construct loops. Not that there is no increment/decrement, ++/--, operation in Ruby.

There is the while loop you’re used to

1
2
3
4
5
6
7
8
i = 0
while i < 3 do
  puts i
  i += 1
end
# 0
# 1
# 2

While loops with the test at the end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
i = 0
begin
  puts i
  i += 1
end while i < 3
# 0
# 1
# 2

i = 0
begin
  puts i
  i += 1
end while i < 0
# 0

There’s the until loops which run while the condition is false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
i = 0
until i > 2
  puts i
  i += 1
end
# 0
# 1
# 2

i = 0
begin
  puts i
  i += 1
end until i >= 0
# 0

And finally, my favourite – the for loops. If you already have a list to iterate through, great.

1
2
3
4
5
6
7
a = [0, 1, 2]
for i in a
  puts i
end
# 0
# 1
# 2

If not, it’s great too

1
2
3
4
5
6
7
8
9
10
11
12
13
for i in 0..2
  puts i
end
# 0
# 1
# 2

(0..2).each do |i|
  puts i
end
# 0
# 1
# 2

There’s also 4 keywords to help you manage the control of flow break, next, redo and finally retry which I’ll bring up in the Error Handling section. break exits the inner most loop

1
2
3
4
5
6
7
8
for i in 0..2
  puts i
  if i == 1
    break
  end
end
# 0
# 1

While next jumps to the next iteration of the loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for i in 0..2
  if i == 1
    next
  end
  puts i
end
# 0
# 2

for i in 0..2
  if i == 2
    next
  end
  puts i
end
# 0
# 1

And redo jumps back to the beginning of the current iteration in this loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
j = 0
for i in 0..2
  puts "i is #{i}"
  puts "j is #{j}"
  j += 1
  if j > 2
    break
  end
  if i == 1
    redo
  end
end
# i is 0
# j is 0
# i is 1
# j is 1
# i is 1
# j is 2