A Hash
is ruby’s basic key-value store: a place where you can store values, and look them up by a key. The Hash
and the Array
are ruby’s two common ways of storing a collection of objects. The key differences between ‘Hash’ and ‘Array’ are:
Array
objects are stored in order. You can think of an Array
as a list.Array
we reference objects by a numerical index.Hash
there is no order on the objects. You can think of a Hash
as a general collection.Hash
objects are referenced by a key
. The key can be a number or a word (or several other things).Here is an example of a simple hash:
character = {'name'=>'Bart', 'surname'=>'Simpson', 'age'=> 10, 'catchphrase' => 'Eat my shorts'}
character['name'] #=> 'Bart'
character['catchphrase'] #=> 'Eat my shorts'
In particular:
Hash
using the curly brackets { }
.'name'
and a value e.g. 'Bart'
.=>
symbol: key => value
.[ ]
.h = {'one' => 1, 'two' => 2, 'three' => 3}
# Accessing elements
# ------------------
h['one']
h['two']
h['four']
# Setting elements
# ----------------
h['five'] = 5
h['one'] = 1.0
h.delete('one') # What does this return? Does it change the hash?
# Some methods
# ------------
h.length
h.empty?
h.keys
h.values
h.has_key?('one')
h.has_value?(7)
# Iterating
# ---------
h.each do |key, value|
puts "#{key}: #{value}"
end
# Combining
# ---------
h1 = {'a'=>1, 'b'=>2}
h2 = {'b'=>3, 'c'=>4}
h1.merge(h2)
h1 # what has this done to h1?
h1.merge!(h2)
h1 # what is h1 now?
# Using a hash for counting (extension)
# -------------------------
# start with an empty hash
h = {}
['a', 'b', 'a', 'a'].each do |letter|
if h.has_key?(letter)
# if the letter is already in the hash, increase
# its count by 1
h[letter] += 1
else # letter isn't in the hash
# so put it in and set the count to 1
h[letter] = 1
end
end
h # what is h now?
# A slicker way to count (extension)
# ----------------------------------
# A slightly slicker way of doing this is
# using a hash with a default value:
h = Hash.new(0) # 0 is the default value that is
# returned when the key is missing
['a', 'b', 'a', 'a'].each do |letter|
h[letter] += 1
end
In the last exercise you will have learnt a number of things about hashes. Here is a summary of the important points:
h = {'one' => 1, 'two' => 2, 'three' => 3}
h['one'] #=> 1
h['four'] #=> nil
h['five'] = 5
h['one'] = 1.0
h #=> {'one' => 1.0, 'two' => 2, 'three' => 3, 'five' => 5}
h['one']
nil
Hashes have a number of methods, which behave as you would expect e.g.
length
empty?
keys
, values
has_key?
, has_value?
Just like an Array
, you can iterate over a Hash
using the each
method. Unlike array iteration, in hash iteration the block accepts two parameters: the key and the value:
h = {'one' => 1, 'two' => 2, 'three' => 3}
h.each do |key, value|
puts "#{key} = #{value}"
end
Combining hashes is done by using the merge
method. The values from the second hash are added to the first, replacing them if they already exist. You will often see this used in rails for specifying default options to a function:
# note how the first version of b gets overwritten
{'a' => 1, 'b' => 2}.merge({'b'=>3, 'c'=>4}) #=> {'a'=>1, 'b'=>3, 'c'=>4}
# real life example
def print_names(opts)
default_opts = {'fancy_format' => true, 'max_length' => 20}
# replace the defaults with options supplied
my_opts = default_opts.merge(opts)
if my_opts['fancy_format']
# ....
end
#...
end
The options example above is a common use of a hash in real life code.
One special use of a hash is for counting things. The following code is an example of how to do this.
h = {}
['a', 'b', 'a', 'a'].each do |letter|
if h.has_key?(letter)
# if the letter is already in the hash, increase
# its count by 1
h[letter] += 1
else # letter isn't in the hash
# so put it in and set the count to 1
h[letter] = 1
end
end
h #=> {'a' => 3, 'b' => 1}
You can make this example a bit shorter by using a hash with a default value: normally a hash will return nil
if the key isn’t there, but we can set it up to return something else. In particular we’ll set it up to return 0:
# set up a hash with default value 0
h = Hash.new(0)
['a', 'b', 'a', 'a'].each do |letter|
h[letter] = h[letter] + 1
end
h #=> {'a' => 3, 'b' => 1}
If the letter isn’t in the hash yet, the default value of 0 will be returned. Otherwise the current count will be returned. Either way, we just need to increase the value by 1.
Symbols are a bit like strings that can’t be changed. They’re primarily used to save space and time:
So far we’ve used strings for our hash keys. As we write and compare hash keys a lot, it makes sense to use symbols instead.
"hello"
:hello
:hello.to_s
"hello".to_sym
"hello" << " world"
:hello << :" world"
"hello".object_id
:hello.object_id
Today we’ll be looking at sending email from a ruby program. To do this we’ll be using a library called Pony. Pony is a light-weight, simple library (in comparison more fully fledged options like ActionMailer). After a small amount of configuration, it allows you to send emails with a single command.
We’ll be using a google account to send the email - you will need to input your username and password, and it will look like the email came from your gmail account. Using gmail is a good solution for a small test app like this. When you get bigger you will start hitting gmails sending limits, and you will want to investigate other solutions e.g. sendgrid or mandrill. For further options see the heroku addons page.
Before sending an email we need to set up some configuration. We do this by setting the Pony.options
hash:
Pony.options = {
:via => 'smtp',
:via_options => {
:address => 'smtp.gmail.com',
:port => '587',
:enable_starttls_auto => true,
:user_name => 'yourusername',
:password => 'yourpassword',
:authentication => :plain, # :plain, :login, :cram_md5, no auth by default
:domain => "localhost.localdomain" # the HELO domain provided by the client to the server
}
}
To send the email is then very simple:
Pony.mail(:to => 'example@example.com', :subject => "Wow - an email", :body=>"Hi. This is your program speaking. Bye.")
You can find out more about the different options you can use on the pony github page.
Fork and clone the code for this session: https://github.com/code61/sinatra_c3s4
Install the gems (including pony
):
bundle install
Copy and paste the contents of development_pony_options.rb.sample
into a new file. Save this file as development_pony_options.rb
. Fill in your gmail (or university) account details.
In irb type require 'pony'
, then copy and paste in your updated options.
Send an email to yourself e.g.:
Pony.mail(:to => 'example@example.com', :subject => "Wow - an email", :body=>"Hi. This is your program speaking. Bye.")
ruby app.rb
.So far the body of our email has only been a single line. What if we want a proper multi-line email? You can use erb
templates for this!
Pony.mail( :to => @email,
:subject => "Congratulations, you added a fruit!",
:body => erb(:email, :layout => false) )
The important bit is erb(:email, :layout => false)
, which tells sinatra to find views/email.erb
and run it through erb (to replace any <%= %>
tags). The :layout => false
tells sintra to skip the normal layout file: we want to send the user the raw text, not an html page! The result is then used as the email’s body.
The template will look something like this:
Hello there!
<%= @name %> is a great fruit!
Best,
The FruitApp team
post '/'
block, to send a welcome email to the new user.views/email.erb
so that it (at least) contains the name of the person who just signed up.This page is more advanced. This stuff is sometimes useful, but don’t worry if you want to skip over it the first time through.
You can convert a hash to an array using the to_a
method and use the Hash[ ]
syntax to convert it back:
h = {'a' => 1, 'b' => 2}
h.to_a #=> [['a', 1], ['b', 2]]
h2 = Hash[['a', 1], ['b', 2]]
The Hash[ ]
is also useful for creating a hash from arrays of keys and values (which is surprisingly useful).
keys = [1, 2, 3]
values = ['one', 'two', 'three']
# use Array#zip to get the arrays in the right format
zipped = keys.zip(values) #=> [[1, 'one'], [2, 'two'], [3, 'three']]
h = Hash[zipped] #=> {1 => 'one', 2 => 'two', 3 => 'three'}
(Optional)
# A few new ways to create a hash
# -------------------------------
h1 = Hash['a', 1, 'b', 2, 'c', 3]
h2 = Hash[[['a',1],['b',2],['c',3]]
# Converting to arrays (and back?)
# -------------------------------
h = {'a'=>1, 'b'=>2, 'c'=>3}
a = h.to_a
# How could you convert a back into h?
h = {'one'=>1, 'two'=>2, 'three'=>3}
keys = h.keys
values = h.values
# How would you combine keys and values back into h?