Arrays

An array is a way of storing a list of objects. Unlike in some other languages in ruby you don’t need to specify which sort of object the array will hold - it can even hold objects of different types.

Arrays are written with square brackets.

my_array = [1,2,3]

empty_array = []    # an array with no elements
Task:

Working with a partner, guess what each of the following expressions will give, then try them out in irb:

a = [3, 39, 1, 0, 5]

a[1]
a.first
a[0]
a[5]

a[-1]
a[-5]
a.last

a[1..3]
a[1..-1]

b = []

a.empty?
b.empty?
a.length
b.length

a.include?(39)
a.include?(49)

a.reverse
a   # has this changed a?
a.reverse!
a   # has this changed a?

a.sort
a   # has this changed a?
a.sort!
a   # has this changed a?

a = [1, 2, 3, 4, 5]

a[3] = 6
a   # What is a now?

a[7] = 7
a   # What else has been added?

a << 9  # This one's important - you'll see it a lot

Exercise summary

Here are some things you learnt in the last exercise:

Accessing elements

  • Arrays start from 0, so that the first elements is a[0]
  • Arrays also allow indexing from the back, giving the last element as a[-1]
  • If try to access beyond the end of the array it gives nil (instead of an error in other languages)
  • You can access multiple elements by specifying a range, e.g. a[1..-1] is everything but the first element

Array methods

Arrays have many useful (and largely self-explanatory) methods, including:

  • empty? - does the array have any elements, or is it the empty array []
  • length
  • include?(x) - is x in the array
  • reverse, reverse! - as with strings the ! permanenty changes the array

 Adding to an array

  • You can add to an array by setting a given element explicitly a[5] = 6

  • If the array isn’t long enough the gaps are filled with nil:

    a = [1, 2]
    a[6] = 5
    a #=> [1, 2, nil, nil, nil, nil, 5]
  • You can also add to the end of an array by doing a << 5. This is very ‘rubyish’ and you’ll see it a lot.

Iteration

Most programming language have some way of looping, or iterating, over a range of values.

Ruby’s approach to this is slightly unusual - instead of you taking elements from the array one-by-one, the ruby array gives the elements to you one-by-one. This might seem like a small distinction to make, but it has a significant effect on the feel of the ruby language and how you think about coding with it.

The way you get an array to give you its elements is by calling its each method:

array = [1, 2, 3, 4, 5]
sum   = 0

array.each do |n|
  sum = sum + n
end

sum #=> 15

The each method accepts a block - in the example above, this is everything between do ... end.

  • The each method sends the elements of the array one-by-one to the block.
  • When the element arrives it is assigned to the variable n.
  • The code on the inside of the block is then run.

In the example above, each element is added to the sum. The example shows one method that you can use to sum the values in an array.

Below is another example, which iterates over the elements of an array, separating them into one of two lists:

all_words   = ['hello', 'how', 'are', 'you', 'today']
long_words  = []
short_words = []

all_words.each do |word|
  if word.length >= 5
    long_words  << word
  else
    short_words << word
  end
end

long_words  #=> ['hello', 'today']
short_words #=> ['how', 'are', 'you']

Please note: the summing and categorising examples above can be made much neater with the use of the array methods inject and select. We'll leave that for another day though

Task:

Take another look through the code examples above. Check you really understand how they work. Try them out in irb, to check they work as advertised!

Lists of data

So far we have only inserted single values and words into an already existing erb template. We will now look at using ruby to generate more of the template dynamically.

Suppose you have a ruby array that you want to turn into an html list:

# in app.rb

get '/fruit' do
  @fruits = ["Bananas", "Apples", "Raspberries"]
  erb :fruits_view
end

We can do this by using the @fruits array’s each method to iterate over the elements and put them into the page one-by-one.

<!-- in views/fruits_view.erb -->
<ul>
  <% @fruits.each do |fruit| %>
    <li><%= fruit %></li>
  <% end %>
</ul>

You might notice that we’ve used a different type of erb tag for the do ... end block: <% %> instead of <%= %>. This is really important:

  • You need to use <%= %> if you want the result of executing that line of ruby to be added to the page.
  • You need to use <% %> if you don’t want the result of executing that line of ruby to be added to the page.

Here, the bits we want adding are the elements of the array, so only they have the <%= %> tags. The code will cause the following html to be generated:

<ul>
  <li>Bananas</li>
  <li>Apples</li>
  <li>Raspberries</li>
</ul>

You can also use the same technique to generate tables and other html (lists of divs etc.).

Task:
  1. Fork, then clone the code for this exercise https://github.com/code61/sinatra_c3s3.
  2. Add the code to the body of todo.erb, so that the @todos display as an ordered list.
  3. Add, commit and push your code to Github.
  4. Add the code to the body of schedule.erb, so that the items in the @schedule display as rows in a table. Note that @schedule is an array of arrays - you'll need to iterate over the first array, and within the loop use the normal ways of accessing elements of an array pull out the items.
  5. Add, commit and push your code to Github.
  6. Take a look at the get &#39;/rsvps&#39; block. Try the CSV.read bit in irb, to see what it does. Have a go at writing the code to categorise rsvps into acceptances/rejections and count them.
  7. Update the rsvps.erb view, so that the right information is displayed.
  8. Add, commit and push your code to Github.

Sinatra static assets

Last week we linked Twitter Bootstrap into your view templates, by linking to the online hosted version <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css">. What if we want to link to our own files?

When we were building static sites, this was simple: we just created a main.css file in our site folder, and linked to it. The problem is that a sinatra site folder is not like a static site folder. We’ve already seen this: app.rb controls the url structure, not the folders in the app folder. (Just try going to ‘/app.rb’ or ‘/views/index.erb’). Because of this, just dropping a main.css into the sinatra folder and trying to link to it won’t work.

Thankfully sinatra has a solution to this problem: if you create a folder called public, any files you put in there will be available from the root url. For example, if I have a file public/main.css, I can then link to this from my template files using <link rel="stylesheet" href="/main.css">. The two important things to note here are:

  1. I’m using a root-relative link (starting with a /). If you don’t do this, you’ll have problems if you ever call that template from a different url route.
  2. I’m linking directly to main.css, not to public/main.css.

Sinatra conventions

  1. Files inside the public folder will be served at the root of the site. This is where you should put your own stylesheets/images etc.
  2. You call a template with the line erb :template_name.
  3. For this to work, you will need a template called template_name.erb in the views folder.
  4. Instance variables (ones that start with @) will be shared with the template.
  5. You use these shared instance variables in the template by putting them inside an erb tag: <%= @my_variable >
Task:
  1. Link the bootstrap stylesheet you'll find in public/dist/css/bootstrap.css into the head of todo.erb and schedule.erb.

Sinatra layouts

One of the most important principles in software engineering is to stay DRY: don’t repeat yourself.

The basic idea is that you don’t want to be copying and pasting the same code into multiple places. If you’re doing this, you’re making a lot of work for yourself should you ever decide to change that code.

One way of staying DRY that we’ll meet soon is writing functions: if there’s a task that you do repeatedly, write a function to put the logic for that task in one place and then call the function when you need to do the task.

Another way, that we’ll be looking at today, is in the form of layouts.

The Problem

In the exercise today, you’ve been using a lot of erb view templates. You have 4 different templates, which all share the same basic outline:

<!-- in views/index.erb -->
<!DOCTYPE html>
<html>
<head>
  <title>Event Manager 2.0</title>
</head>
<body>

  <h1>Picture Unveiling Evening - Event Managment</h1>

  <ul>
    <li><a href='/todos'>Todo list</a></li>
    <li><a href='/schedule'>Event schedule</a></li>
    <li><a href='/rsvps'>RSVPs</a></li>
  </ul>

</body>
</html>
<!-- in views/todo.erb -->
<!DOCTYPE html>
<html>
<head>
  <title>Event Manager 2.0</title>
</head>
<body>
  <h1>Todo list</h1>

  <ul>
    <% @todos.each do |todo| %>
      <li><%= todo %></li>
    <% end %>
  </ul>

</body>
</html>

These files are almost the same - a lot of copy and paste has gone on.

 The solution

Sinatra gets round this problem by allowing you to have a layout.erb file in your views folder:

<!-- views/layout.erb -->
<!DOCTYPE html>
<html>
<head>
  <title>Event Manager 2.0</title>
</head>
<body>

  <%= yield %>

</body>
</html>

You can then write only the bits that change in the other two views:

<!-- in views/index.erb -->
<h1>Picture Unveiling Evening - Event Managment</h1>

<ul>
  <li><a href='/todos'>Todo list</a></li>
  <li><a href='/schedule'>Event schedule</a></li>
  <li><a href='/rsvps'>RSVPs</a></li>
</ul>
<!-- in views/todo.erb -->
<h1>Todo list</h1>

<ul>
  <% @todos.each do |todo| %>
    <li><%= todo %></li>
  <% end %>
</ul>

Sinatra knows that if you have a file called layout.erb it should use that as a layout. If you call erb :index:

  • It takes the layout.erb file.
  • It finds the bit where it says <%= yield %>.
  • It inserts index.erb at that point.

You can find more about Sinatra layouts on the internet.

Sinatra conventions

  1. If you have a file views/layout.erb Sinatra will treat it as a layout file: whenever it renders another template, it will try and insert the output into the <%= yield %> part of the layout file.
  2. Files inside the public folder will be served at the root of the site. This is where you should put your own stylesheets/images etc.
  3. You call a template with the line erb :template_name.
  4. For this to work, you will need a template called template_name.erb in the views folder.
  5. Instance variables (ones that start with @) will be shared with the template.
  6. You use these shared instance variables in the template by putting them inside an erb tag: <%= @my_variable %>
Task:

Refactor your views:

  1. Create a layout file, containing the shared material from each view.
  2. Remove the shared material from each view.
  3. Check that the screens still look the same!

Homework

Task:
  1. Finish the final exercise from class. If you have time, try and make the app look a bit nicer and practice deploying to heroku.
  2. Do the Codecademy Ruby track Sections 5 and 6.