Ruby Intro

SENG2021

Arrays

Arrays in Ruby are very similar to those in other dynamic programming languages. It’s all very standard and the documentation has loads of examples anyway, so I’ll go through only the most basic and obvious to get started.

Construction

1
2
3
4
5
6
7
8
9
10
11
12
[1, "foo", nil, "bar"]
# [1, "foo", nil, "bar"]

Array.new
# []
Array.new(3, true)
# [true, true, true]

(1..10).to_a
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
("a7".."b5").to_a
# ["a7", "a8", "a9", "b0", "b1", "b2", "b3", "b4", "b5"]

Accessing elements and array info

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
arr = [1, 2, 3, 4, 5, 6]
arr[2]
# 3
arr[100]
# nil
arr[-3]
# 4
arr[2, 3]
# [3, 4, 5]
arr[1..4]
# [2, 3, 4, 5]
arr.at(0)
# 1

arr.length
# 6
arr.empty?
# false
arr.include?(7)
# false

Adding to the array

1
2
3
4
5
6
7
8
9
10
arr.push(7)
# [1, 2, 3, 4, 5, 6, 7]
arr << 8
# [1, 2, 3, 4, 5, 6, 7, 8]

arr.unshift(0)
# [0, 1, 2, 3, 4, 5, 6, 7]

arr.insert(3, 2.5)
# [0, 1, 2, 2.5, 3, 4, 5, 6, 7, 8]

Removing from Array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
arr
# [0, 1, 2, 2.5, 3, 4, 5, 6, 7, 8]

arr.pop
# 8
# arr = [0, 1, 2, 2.5, 3, 4, 5, 6, 7]

arr.shift
# 0
# arr = [1, 2, 2.5, 3, 4, 5, 6, 7]

arr.delete(3)
# 2.5
# arr = [1, 2, 2.5, 4, 5, 6, 7]

Print vs Puts

Both these functions can be used to output text. To put it simply, puts adds a newline character to the end of each argument and outputs nil as an invisible character rather than a String. Refer to the link at the bottom for more discussion.

1
2
3
4
5
6
7
print [nil, 1, 2]
# [nil, 1, 2]=> nil

puts [nil, 1, 2]
# 
# 1
# 2
  1. Ruby-Docs Array
  2. Stack Overflow – What is the difference between print and puts

Truthiness & Control Flow

Ruby uses keywords true and false to represent truthiness and nil to represent a reference that points to nothing (usually some variation of null in other languages).

Like Fixnum from the previous post, these 3 keywords are immediate values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
true.object_id
# 20
true.object_id
# 20

false.object_id
# 0
false.object_id
# 0

nil.object_id
# 8
nil.object_id
# 8

Equality Testing

There are 4 ways of testing for equality in Ruby. You’ll be using one of them a majority of the time… ==. Generally == represents equality of the values within the object while .equal? ensures that the 2 objects are one and the same (reference pointer comparison). However, as always, make sure you test your code thoroughly since these are just methods that are easy to override (more on this later). The other two are more obscure and depends much more on the implementation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"abc" == "abc"
# true
"abc".equal? "abc"
# false

1 == 1
# true
1.equal? 1
# true
1.eql? 1
# true

1.0 == 1
# true
1.0.equal? 1
# false
1.0.eql? 1
# false

Control Flow

As mentioned in the first post, Ruby was designed to be readable and flexible. I think control flow statements are the highlight. Further, it’s important to always remember that false and nil are the only objects that can be ‘untruthy’ (including 0).

Firstly, we have the very standard if-then-else statements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
t = true

if t
  puts "t is true"
else
  puts "t is false"
end

# t is true

num = 0

if num < 0
  puts "num is negative"
elsif num == 0
  puts "num is zero"
else
  puts "num is positive"
end

# num is zero

Then we have unless, which literally means if not. However, please avoid this if you need to use else (there is no secondary guard like elsif or elsunless) – it’s only useful in causing confusion.

1
2
3
4
5
6
7
8
9
t = true

unless t
  puts "t is false"
else
  puts "t is true"
end

# t is true

A popular way of writing terse simple control flow without sacrificing on readability is to suffix the control to the line itself

1
2
3
4
5
6
puts "true" if true
# true
# => nil

puts "false" unless true
# => nil

Finally, we have the C-style single line statements

1
2
3
4
5
true ? true : false
# true

false ? true : false
# false

And many more complex variations of them. They’re just another way of formatting the first option since they Ruby treats ; as a new line. Please never write code like this :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if true then true else false end
# true

if true; true else false end
# true

num = 0
if num < 0; puts "negative"; puts "lalala" elsif num > 0; puts "positive" else puts "zero" end
# zero

num = -1
if num < 0; puts "negative"; puts "lalala" elsif num > 0; puts "positive" else puts "zero" end
# negative
# lalala

num = 1
if num < 0; puts "negative"; puts "lalala" elsif num > 0; puts "positive" else puts "zero" end
# positive
  1. Stack Overflow – What’s the difference between equal?, eql?, ===, and ==?

Numbers

Like most (if not all) object oriented programming languages out there, Ruby has an Integer class and a Float class. Less common in standard libraries however, are the classes Rational and Complex which are used to represent fractions and complex numbers respectively.

Integers

In terms of functionality, there’s little interesting with Integers. There’s obviously adding, subtracting, multiplication, division, modulo and exponentials.

1
2
3
4
5
6
7
8
9
10
11
12
1 + 2
# 3
1 - 2
# -1 
1 * 2
# 2
2 / 1
# 2
5 % 2
# 1
2 ** 3
# 8

There are also several methods for common operations

1
2
3
4
5
6
7
8
1.next
# 2
1.even?
# false
10.gcd(2)
# 2
"10".to_i
# 10

Fixnum vs Bignum

To make an analogy to Java and C, this is the comparison between int and long. In Ruby however, integers are not actually of the class Integer.

What we think of Integer is the Fixnum. It’s used for all integers that would fit in a machine’s ‘word’, otherwise it’s a Bignum. Both these types inherit from the Integer class.

On this 64bit Mac, a word is 8 bytes. The class uses 1 bit mark the number as positive or negative, and another to mark the integer as value as opposed to a pointer (this is why object_id for Fixnum are always odd). As such, the maximum value for a Fixnum is 4611686018427387903 (262-1) and the minimum is -4611686018427387904 (-262). If an Fixnum leaves this range, Ruby automatically converts it to a Bignum and vice versa.

1
2
3
4
5
6
7
8
9
10
11
FIXNUM_MAX = (2**(0.size * 8 -2) -1)
# 4611686018427387903
FIXNUM_MIN = -(2**(0.size * 8 -2))
# -4611686018427387904
FIXNUM_MAX.class
# Fixnum
bignum = FIXNUM_MAX + 1
bignum.class
# Bignum
(bignum-1).class
# Fixnum

The biggest difference between these classes is that, like Java ints, Fixnum objects are immediate value. They are not references to another object. Remember in the last post when repeating "xyzxyz".object_id would return a different number everytime? This won’t be the case for Fixnum. In fact, object_id of Fixnum are predictable. For positive numbers, the id is simply 2 * value + 1. Similarly, 2 * value – 1 for negative numbers.

1
2
3
4
5
6
7
4611686018427387903.object_id
# 9223372036854775807
4611686018427387903.object_id
# 9223372036854775807

-4611686018427387904.object_id
# -9223372036854775807

Rationals

Rationals are useful for calculations because they come with the accuracy that’s missing from floats. There are 3 ways to create them

1
2
3
4
5
6
7
8
9
10
11
Rational(1, 2)
# (1/2)
"1/2".to_r
# (1/2)
0.5.rationalize
# (1/2)

Rational(1,2) + Rational(2,3)
# (7/6)
Rational(7,6).to_f
# 1.1666666666666667

Float

Like integers, Float’s methods are quite standard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.2345.to_i
# 1
1.2345.floor
# 1
9.87654321.round(3)
# 9.877
(0.0).infinite?
# nil
(-1.0/0.0).infinite?
# -1
(+1.0/0.0).infinite?
# 1
0.3.rationalize
# (3/10)

However, it’s important to note that floating point calculations are inaccurate, like almost every programming language (can’t name any that are accurate).

1
2
3
4
0.3.to_r
# (5404319552844595/18014398509481984)
printf("%.55f\n", 1.9)
1.8999999999999999111821580299874767661094665527343750000
  1. Ruby-Docs Integer
  2. Ruby-Docs Fixnum
  3. Ruby-Dics Bignum
  4. Ruby-Docs Float
  5. Ruby-Docs Rationals
  6. Ruby-Docs Complex
  7. Stack Overflow Ruby max integer

Strings

There isn’t much special about Ruby strings. Try the code snippets below in irb.

Construction

Strings can be created either using literals, or the new function.

1
2
3
4
5
6
7
8
9
a = "abcde"
puts a
# abcde
# => nil

b = String.new "fghij"
puts b
# fghij
# => nil

Instance Functions

Like java.lang.String, there are loads of predefined methods for the class. Remeber to refer to the Class documentation; there are code samples in there too. I’m just going to give you a taste of what’s available.

1
2
3
4
5
6
7
8
9
10
a.length
# 1
a.next
# abcdf
a.reverse
# edcba
a.upcase
# ABCDE
a.slice(3, 5)
# de

You’ll notice that the methods above only returns the result of the function. Some methods makes changes to the string in place. In general, the in place version will end with an exclamation mark, but note that this is not a standard.

1
2
3
4
5
6
7
8
c = "ababab"
c.reverse!
puts c
# bababa

c.upcase!
puts c
# BABABA

Like Perl, Strings in Ruby are mutable. The object_id method I’m using below returns the id of the object, similar to the & operator in C. Strings inherit this from the Object class. Note that String objects are created every time a String literal is used.

1
2
3
4
5
6
7
8
9
10
11
12
"xyzxyz".object_id
# 70243133245180
"xyzxyz".object_id
# 70243133228560

d = "xyzxyz"
d.object_id
# 70243133212180
d.reverse!
# "zyxzyx"
d.object_id
# 70243133212180

Building a String

Strings can be built from variables in 4 different ways. The first two is probably the most commonly used

1
2
3
4
5
6
7
8
e = "abc"
f = "def"
g = 10
h = e + " + " + f + " is not equal to " + g.to_s
i = "#{e} + #{f} is not equal to #{g}"
puts h
puts i
# abc + def is not equal to 10

The third way is appending to strings. It’s more efficient in that it simply adds to a string instead of creating a new one, but it does change the string that is being appended to.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
j = e << " + " << f << " is not equal to " << g.to_s
# abc + def is not equal to 10

j.object_id
# 70243124981460
e.object_id
# 70243124981460
f.object_id
# 70243124965700

puts e
# abc + def is not equal to 10
puts f
# def

The first line basically means, append “ + ” to e, then append f to e, then append “ is not equal to ” to e, and finally append g.to_s to e. I will demonstrate the final way when we get to the Arrays section.

Escaping characters

If the String you’re after is literally #{e} + #{f} is not equal to #{g}, ie. you don’t want Ruby to evaluate e, f, and g, you can escape the #{} blocks by prepending the # with a backslash \. Alternatively, you can use single quotes instead of double quotes, and Ruby will escape those characters for you.

1
2
3
4
5
6
puts "\#{e} + \#{f} is not equal to \#{g}"
# #{e} + #{f} is not equal to #{g}
puts '#{e} + #{f} is not equal to #{g}'
# #{e} + #{f} is not equal to #{g}
a = '#{e} + #{f} is not equal to #{g}'
# "\#{e} + \#{f} is not equal to \#{g}"

Ruby Documentation

There’s two way to access the API documentation, online through http://ruby-doc.org/, or offline in your command line with ri.

1
2
ri String
ri String#object_id

Unofficially, (if you don’t already) you’ll learn to love Google and Stack Overflow too :)

  1. Ruby Docs – String

Hello World

It’s finally time to start coding.

hello_world.rb
1
puts "Hello World"

That’s it! Save it as hello_world.rb, exit, and now we can run the file. Note that # is the comment delimiter in Ruby and Shell so I will be using that to give you an idea of the expected result of the line preceding it.

1
2
ruby hello_world.rb
# Hello World

Like shell scripts, you can also run them as executables. Create another file:

hello_world_exe.rb
1
2
#!/usr/bin/env ruby
puts "Hello World"

And give it executable permission, and run it.

1
2
chmod +x hello_world_exe.rb
./hello_world_exe.rb

Hashbangs will be explained at the end of this post for those curious

Interactive Ruby Shell

irb is a tool that provides an interface to Ruby. It’s a great feature, especially for developers new to Ruby because it allows one to interact with Ruby for instant feedback, and build up to a complex script line by line. Code behaves exactly the same way in irb as it would in the method described above. So open up irb and write Hello World again….puts "Hello World"

You’ll notice that after printing Hello World, it also says => nil; this is the ‘null’ value in Ruby. It’s the value that is returned by the function, and in this case, the value returned by puts.

If you want to import files that you’ve written, there are two ways to do so. A file that is imported in irb is executed just as it would if it had been imported through normal code, and it is the same as if it had been copy and pasted into irb. The following demonstrates the two major difference between the two options so try this out in irb. Whereas require is your standard ‘import this library’, load is more like ‘run this bit of code’.

The first is subtle, but important. The argument to load is a path to the file. A file you load can be anywhere on the system, it accepts both relative and absolute paths. The argument to require is just the name of the file, and it will look for that file only in Ruby’s PATH. Since 1.9.2 however, your working directory is no longer included in it.

1
2
3
4
5
6
7
8
9
puts $:
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/site_ruby/2.0.0
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/site_ruby/2.0.0/x86_64-linux
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/site_ruby
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/vendor_ruby/2.0.0
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/vendor_ruby/2.0.0/x86_64-linux
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/vendor_ruby
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/x86_64-linux

To fix this, we start irb with irb -I .

1
2
3
4
5
6
7
8
9
10
puts $:
# /root/ruby-intro
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/site_ruby/2.0.0
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/site_ruby/2.0.0/x86_64-linux
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/site_ruby
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/vendor_ruby/2.0.0
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/vendor_ruby/2.0.0/x86_64-linux
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/vendor_ruby
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0
# /root/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/x86_64-linux

Let’s try again.

1
2
3
4
5
6
load 'hello_world.rb'
# Hello World
# => true
load 'hello_world.rb'
# Hello World
# => true
1
2
3
4
5
require 'hello_world'
# Hello World
# => true
require 'hello_world'
# => false

The other difference is that require will not import a file more than once.

Hashbangs

You might have come across this in COMP2041. This is the line at the top of scripts that tells your shell what to execute it with – your shell will simply interpret it as a shell script without it.

1
2
3
chmod +x hello_world.rb
./hello_world.rb
# ./hello_world.rb: line 1: puts: command not found

/usr/bin/env

The characters following the exclamation mark is the path of the executable that you want to run the script with.

Since you now have several different versions of Ruby installed, it’s best to make the script run with the version that you intend to. rbenv controls the Ruby version in your environment, so that’s the best one to use! By /usr/bin/env ruby, the script will be run with the same ruby as typing ruby in the command line. To demonstrate, create the 2 files below, and then enter the following commands (make note of the versions):

version_system.rb
1
2
#!/usr/bin/ruby
puts RUBY_VERSION
version_env.rb
1
2
#!/usr/bin/env ruby
puts RUBY_VERSION
1
2
3
4
5
6
7
8
9
10
ruby -v
# ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.4.0]
/usr/bin/ruby -v
# ruby 1.8.7 (2012-02-08 patchlevel 358) [universal-darwin12.0]
chmod +x version_env.rb
./version_env.rb
# 2.0.0
chmod +x version_system.rb
./version_system.rb
# 1.8.7
  1. Wikipedia on Hashbangs
  2. Wikipedia on irb
  3. Alan Skorkin on Require vs Load

Installing Ruby

When I was first starting out in Ruby, this is what gave me the biggest problems. One can install from their operating system’s package manager easily enough (likely be installed already with the system), but because of how quickly the Ruby community moves, using system Ruby is generally not a good idea. Every now and then, you’ll come across version incompatibility issues and if you need to work on different apps with different versions of Ruby, then you’re in a bit of a mess. Not to mention the permission issues.

To get around this, we use a Ruby version manager – one named simply rvm, and another, rbenv.

I will be going through installing Ruby with rbenv on Bash with you simply because I’ve personally had fewer issues with it integrating with our Bamboo continuous integration environment, but that’s a story for another day. You’ll no doubt come across problems that I didn’t foresee. If it tells you of a missing dependency, just install it. Setting up Ruby was a great exercise in google-fu for me; don’t be discouraged if you end up having to reinstall several times.

Please read the OSX comments even if you don’t have OSX!

OSX

If you have a Mac, you’re in luck – you have Homebrew. If you don’t, just install it with ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

1
2
brew update
brew install rbenv

Unfortunately, a plugin is required to install Ruby with rbenv

1
brew install ruby-build

‘Turn on’ rbenv

1
if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi

Set up bash to always enable rbenv if available

1
echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.profile

Add rbenv’s executables to your PATH

1
2
3
# My rbenv is installed in /usr/loca/opt/rbenv, but yours may be in ~/.rbenv
# Change the line below appropriately
echo 'export PATH=/usr/local/opt/rbenv/shims:$PATH'

Finally install Ruby 2.0.0-p247

1
2
3
4
rbenv install 2.0.0-p247
# Command below refreshes rbenv's executables.
# Make sure you run this every time you (un)install a ruby or a gem - more on this later
rbenv rehash

Debian/Ubuntu/Linux Mint

For fun times, install these dependencies

1
apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config nodejs

Install rbenv + ruby-build

1
2
3
4
5
6
7
8
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
exec $SHELL -l
mkdir -p ~/.rbenv/plugins
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 2.0.0-p247
rbenv rehash

Windows

Ruby does work on Windows, but I don’t have any experience with it and I’m not familiar with the toolset for developing on Windows so these posts don’t really work on Windows.

I haven’t tried any of this but apparently…

Download and install the relevant 2.0.0-p247 installer from http://rubyinstaller.org/downloads/, but make note of where you install it. Press Windows+R, and type in cmd. Then cd to the directory where you installed Ruby and everything else should be the same.

If you’re using Windows, open fxri from the Ruby section of your Start Menu.

Other than that, you’re on your own :(

Keeping legacy code consistent

You may have Ruby scripts before installing rbenv that ran with system Ruby, so it’s best to keep it that way. To ensure that this happens, we can make system ruby the default for rbenv.

1
2
3
rbenv global system
rbenv version
# system (set by /usr/local/opt/rbenv/version)

You can obviously set the global default to 2.0.0-p247 if you wish too.

Make rbenv use a specific version

Whenever you interact with rbenv, it will look in your current directory for a file named .rbenv-version before falling back to the global default. Try the following:

1
2
3
4
5
6
cd /tmp
rbenv version
# system (set by /usr/local/opt/rbenv/version)
echo '2.0.0-p247' > .rbenv-version
rbenv version
# 2.0.0-p247 (set by /tmp/.rbenv-version)

s

The best thing about setting versions with the file is that you can commit this file to your code repository, and it will ensure that every developer on every computer using rbenv will be using the version of Ruby that is intended (unless they really don’t want to).

  1. rvm
  2. rbenv
  3. Wikipedia on Continuous Integration
  4. Atlassian Bamboo
  5. Homebrew
  6. Installing rbenv
  7. ruby-build rbenv plugin

Intro to Intro

Ruby is a dynamically typed, object oriented programming language that first appeared toward the end of 1995, so it’s a bit younger than the other 2 similar languages in the mainstream, Python and Perl. In my opinion, it’s more hipster and better looking than its cousins, and intuitively, it’s as close to English as general purpose programming languages go. As such Ruby is one of the easiest languages to understand.

It really only appeared out of obscurity in 2005 with the rise in popularity of the Ruby-based web application framework, Rails. It has however, greatly matured since then.

There are many different implementations around, including one (JRuby) which compiles your Ruby code to run on the JVM. It allows you to use native Java classes in your Ruby code, and Ruby classes in your Java code. I’m not familiar with the obvious benefits of using these features, but if you ever feel like writing a Swing app with Ruby, you have that option.

The main C-based implementation, also known as MRI, is now up to version 2.0.0-p247. The first version of 2.0.0 was released less than 6 months ago, but the release was designed to be backward compatible with 1.9.3 (with 5 minor exceptions) so one should be able to use any Ruby documentation written in the last 6 or so years with little issue.

  1. Official About Ruby
  2. Wikipedia on Ruby
  3. Wikipedia on JRuby
  4. Ruby 2.0.0 Release Notes