Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
learn-to-code/source/lesson-3-writing-ruby-scripts.html.md.erb
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
556 lines (407 sloc)
16 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
title: "Lesson 3: Writing Ruby Scripts" | |
weight: 5 | |
--- | |
# Lesson 3: Writing Ruby Scripts | |
## Introduction | |
### Objectives | |
In this tutorial we are going to look at: | |
* getting user input | |
* doing things multiple times using loops | |
* building simple programs | |
* using `require` to access other Ruby modules | |
* using Ruby's `Date` type | |
* using `File` to read files in Ruby | |
### Goal | |
By the end of this tutorial you will have used what you learnt in lesson 2 to | |
build a couple of small Ruby programs, including one like [this times tables game](https://repl.it/@RichardTowers/TimesTablesGame). | |
### Running a file with Ruby | |
We've been having fun with `irb` in Lesson 2 - it's a great tool for exploring and | |
learning about Ruby. Ideally though we'd like to be able to save our programs | |
for later, and run them whenever we want. | |
To do this, we can write a Ruby script in a text editor (like you did with HTML in | |
lesson 1), then run the script from the command line with `ruby name-of-your-script.rb`. | |
Ruby files end with the extension `.rb`. | |
#### Task 1: My first Ruby script | |
* Exit `irb` by running the `exit` command | |
* Create a file in your text editor (we recommended Atom in lesson 1) called `hello.rb` | |
* Confirm you can see the file in the command line by running `ls` | |
* From the command line, use Ruby to run your file by typing `ruby hello.rb` and pressing enter | |
* You should see nothing returned (because `hello.rb` is empty at the moment) | |
### Interacting with the user | |
Now that we're running our Ruby from a file, we can't rely on `irb` to handle | |
all of our user input and output. When we want to show something to the user | |
we need to explicitly say so, and likewise, when we want some input from the | |
user we have to explicitly ask for it. | |
Ruby provides a `puts` method (put string) and a `gets` method (get | |
string). | |
* `puts` - prints a string to the command line | |
* `gets` - waits for the user to type one line of text into the command line, | |
when they press enter, returns whatever the user typed (so you can store it | |
in a variable) | |
#### Task 2: Hello world! | |
Add the following to your `hello.rb` file, save it, and run it with `ruby hello.rb`: | |
```ruby | |
puts("What is your name?") | |
name = gets | |
puts("Hello #{name}") | |
``` | |
We can use `puts` and `gets` to create a very simple text based user interface. | |
#### Getting things wrong in Ruby scripts | |
Just like `irb`, if you get something wrong in a Ruby script Ruby will tell | |
you by printing an error message. Nothing to worry about - programmers make | |
mistakes all the time. | |
Imagine a script called `oops.rb` containing: | |
```ruby | |
puts("time for a deliberate error") | |
oops("this won't work") | |
``` | |
When you run this with `ruby oops.rb` you'll get an error like this: | |
``` | |
time for a deliberate error | |
oops.rb:2:in `<main>': undefined method `oops' for main:Object (NoMethodError) | |
``` | |
The `oops.rb:2` bit at the start tells us what file the error was in, and | |
what line (2 in this case) the error was on. There's no method called `oops` | |
so Ruby doesn't know what to do. | |
#### Task 3: Create a script that reverses the input | |
Create a new script called `reverse.rb` in your text editor. | |
Use `gets` to get a string from the user, use `.reverse` to reverse that string, | |
and use `puts` to print it. | |
<details> | |
<summary>Answer</summary> | |
```ruby | |
puts("Enter a string to reverse: ") | |
input = gets | |
puts("Your string reversed is: ") | |
puts(input.reverse) | |
``` | |
</details> | |
#### Task 4: Create a script that multiplies two numbers | |
Create a new script called `multiply.rb` in your text editor. | |
Note that `gets` gets the user input as a `string`. In Ruby strings and | |
numbers are *not the same* (`"42"` is not equal to `42`). | |
When you need to ask the user for a number you must ask them for a string and | |
then convert it into a number using `.to_i` like so: | |
```ruby | |
user_input_as_string = gets | |
user_input_as_number = user_input_as_string.to_i | |
``` | |
The `i` in `.to_i` stands for "integer" which is fancy way of saying | |
"whole number". | |
Use `gets`, `.to_i` and `puts` to get two strings from the user, | |
converts them to numbers, multiplies them together and prints the result. | |
<details> | |
<summary>Answer</summary> | |
```ruby | |
puts("Enter the first number:") | |
x_string = gets | |
x_number = x_string.to_i | |
puts("Enter the second number:") | |
y_string = gets | |
y_number = y_string.to_i | |
puts("#{x_number} * #{y_number} = #{x_number * y_number}") | |
``` | |
</details> | |
### Defining methods | |
The Ruby authors didn't manage to write every method we'll ever need into | |
Ruby itself, so sometimes we need to define our own. | |
This means we can write abstractions that describe what we're doing and we | |
don't have to repeat ourselves. | |
Earlier, we had this line of code: | |
```ruby | |
age_dog_years = age_human_years * 7 | |
``` | |
This is okay, but it's a little surprising to see that `7` in there - what's | |
it doing? It would be nicer if we could write: | |
```ruby | |
age_dog_years = convert_to_dog_years(age_human_years) | |
``` | |
and hide away the grubby implementation details inside a method. | |
In Ruby a method definition looks like this: | |
```ruby | |
def method_name(argument) | |
result = "some value" | |
return result | |
end | |
``` | |
You can name a method anything you could name a variable (they're | |
`snake_cased` by convention too). They can have as many lines of code inside | |
them as you want. | |
#### Task 5: Create a script that converts human years to dog years | |
Create a script called `dog-years.rb`. Use `puts`, `gets` and `.to_i` | |
to ask the user how old their dog is. | |
Assuming that dogs age faster than humans, and that there are seven "dog | |
years" to one "human year", define a method which: | |
* takes an age in human years as a argument | |
* multiplies the age in human years by seven | |
* returns the result of the multiplication | |
Call the method in your script, and print the result. | |
<details> | |
<summary>Answer</summary> | |
```ruby | |
def convert_to_dog_years(age) | |
return age * 7 | |
end | |
puts("How old is your dog in human years?") | |
age_human_years_string = gets | |
age_human_years_number = age_human_years_string.to_i | |
age_dog_years = convert_to_dog_years(age_human_years_number) | |
puts("Your dog is #{age_dog_years} in dog years") | |
``` | |
</details> | |
### Repeating things | |
<img src="/images/lesson-2/brøther.jpg" alt="Meme where a cat looks at a bowl of fruit loops, and says 'brøether, may i have the lööps'" style="max-width: 300px;"> | |
So far, each line of code we've written has run exactly once (each time a | |
script runs). Ruby goes through each line in the script, runs it, then moves | |
on to the next line. When it reaches the end of the file, the script exits. | |
One of the things that computers are really good at is repeating themselves. | |
In Ruby we can use methods like `loop` and `.times` to make the computer | |
repeat things. | |
You might want to try some of the following out in `irb` to get a feel for | |
how they work. (If you get stuck in a loop in `irb` you can escape it by | |
pressing `ctrl + c`): | |
```ruby | |
5.times do | |
puts("Odelay!") | |
end | |
``` | |
```ruby | |
10.times do |i| | |
puts("#{10 - i} green bottles, hanging on the wall,") | |
puts("#{10 - i} green bottles, hanging on the wall,") | |
puts("And if one green bottle should accidentally fall,") | |
puts("There'll be #{9 - i} green bottles hanging on the wall.") | |
puts | |
end | |
``` | |
```ruby | |
loop do | |
puts("What's your name?") | |
name = gets | |
puts("Hello #{name} (to exit this infinite loop, press ctrl+c)") | |
end | |
``` | |
The bit between the `do` and the `end` is called a block, which is run by the | |
method repeatedly. Blocks can take parameters after the `do`, which are | |
wrapped in vertical pipes like `|i|`. | |
#### Task 6: Update one of your scripts to work in a loop | |
Earlier, you wrote a few scripts that ask the user for some input, do | |
something with the input then print the result and exit. Change one of these | |
scripts (e.g. `multiply.rb`) so they do this in an infinite loop (using `loop`). | |
If you get stuck in an infinite loop (a loop which never ends) in the command | |
line you can break out by pressing `ctrl + c`. | |
<details> | |
<summary>Answer</summary> | |
```ruby | |
loop do | |
puts("Enter the first number:") | |
x_string = gets | |
x_number = x_string.to_i | |
puts("Enter the second number:") | |
y_string = gets | |
y_number = y_string.to_i | |
puts("#{x_number} * #{y_number} = #{x_number * y_number}") | |
end | |
``` | |
</details> | |
### Conditionals | |
One last thing to learn is how to get the computer to do something based on | |
some condition. To do this, we can use the `if` statement, like so: | |
```ruby | |
puts("What is your name?") | |
name = gets | |
if name.include?("r") | |
puts("Hi #{name} - you sound like a pretty cool person") | |
else | |
puts("Hi nerd") | |
end | |
``` | |
An `if` statement takes a "condition" (something that evaluates | |
to `true` or `false`). You can use things like `==` (equals), `<` (less than), | |
or methods like `.include?` (like we did in the example). | |
Note that we use `==` instead of `=` to compare two values. This is because | |
`==` tests if two values are equal, whereas `=` assigns the value on the right | |
to the variable on the left. | |
If the condition is true Ruby will run the lines of code between | |
the `if` and the `else`, otherwise Ruby will run the lines of code between | |
the `else` and the `end`. | |
### Tying it all together | |
Phew! That was a lot to learn. To recap, we've learned: | |
* how to use `irb` | |
* how to use numbers | |
* what "strings" are and how to use them | |
* how to use variables to name things | |
* how to "interpolate" strings | |
* how to call methods | |
* how to run Ruby from a file | |
* how to interact with a user | |
* how to define methods | |
* how to repeat things | |
* how to do things conditionally | |
If you've understood all of that, well done! You now know the basics of | |
programming. There's a lot more to learn, but it all builds on these | |
fundamentals. | |
To tie everything you've learned together, here's one final "hard" task. | |
#### Task 7: Build a times tables game | |
Right at the beginning, we said you'd build [this times tables game](https://repl.it/@RichardTowers/TimesTablesGame). | |
To do this you'll need to use everything you've just learned. It's a pretty | |
difficult problem for a beginner, so if you get stuck on something don't be | |
afraid to ask for help. | |
Here's a rough plan of how to implement this: | |
* create a `score` variable to keep track of the users score | |
* in a loop that happens 4 times | |
* generate two numbers between 1 and 12 using `rand` and assign them to variables | |
* print a message to the user asking them to multiply the numbers together | |
* get the user's answer and covert it to a number (using `.to_i`) | |
* compare the user's answer to the answer Ruby thinks is correct | |
* show the user a message saying whether they're correct (and add one to their score) | |
* if the user scored 4 then show them a message saying they got them all right | |
* otherwise, show the user a message commiserating them for their failure | |
For bonus points, consider whether you could define a method to do some of | |
the work to make the loop easier to read. | |
<details> | |
<summary>Answer</summary> | |
```ruby | |
def multiplication_challenge | |
x = rand(12) + 1 | |
y = rand(12) + 1 | |
print("What is #{x} multiplied by #{y} ? ") | |
user_input = gets | |
user_input_as_a_number = user_input.to_i | |
correct_answer = x * y | |
if user_input_as_a_number == correct_answer | |
puts(":) correct!") | |
return true | |
else | |
puts(":( oops!") | |
puts("The answer was #{correct_answer}") | |
return false | |
end | |
end | |
number_of_rounds = 4 | |
score = 0 | |
puts("Times tables challenge") | |
puts("----------------------") | |
puts | |
puts("You will be asked #{number_of_rounds} questions.") | |
puts("Press enter to start...") | |
gets | |
number_of_rounds.times do |i| | |
if multiplication_challenge | |
score = score + 1 | |
end | |
end | |
if score == number_of_rounds | |
puts("You scored #{score}/#{number_of_rounds}. Well done!") | |
else | |
puts("You scored #{score}/#{number_of_rounds}. Better luck next time!") | |
end | |
``` | |
</details> | |
### Using `require` to access other Ruby modules | |
In lesson 2 we used a number of built-in Ruby methods, like `rand` (to get | |
a random number), `.reverse` (to reverse a string), `gets` and | |
`puts` (to read and write strings from the command line). | |
These methods are so useful that they live in the core of Ruby, so you can | |
always call them. | |
Some bits of Ruby are used less often, and these are not included by default. | |
To use these other bits you need to `require` them. For example, the `"date"` | |
module lets you work with dates and times, but it isn't included by default. | |
Running `require("date")` includes the date code so you can use it in your | |
script. For example: | |
``` | |
irb(main):001:0> require("date") | |
=> true | |
irb(main):002:0> Date.today | |
=> #<Date: 2019-05-30 ((2458633j,0s,0n),+0s,2299161j)> | |
``` | |
### Working with Dates in Ruby | |
Ruby's date type makes doing things like adding days to a date much easier | |
(if you didn't have a date type you'd need to worry about how many days there | |
are in a month and so on). | |
``` | |
irb(main):003:0> one_thousand_days_in_the_future = Date.today + 1000 | |
=> #<Date: 2022-02-23 ((2459633j,0s,0n),+0s,2299161j)> | |
``` | |
The `.today` method gets today's date, but you can also create dates for | |
other days using `.new`: | |
``` | |
irb(main):004:0> Date.new(2022, 2, 23) | |
=> #<Date: 2022-02-23 ((2459633j,0s,0n),+0s,2299161j)> | |
``` | |
Because different cultures represent dates in different ways (e.g. Japan use | |
2019-05-30, the USA use 05/30/2019 and the UK use 30/05/2019), there's a | |
special method for formatting dates as strings called `.strftime`. It takes | |
a "format" string as an argument specifying how to show the date: | |
``` | |
irb(main):005:0> japan_format = Date.today.strftime("%Y-%m-%d") | |
=> "2019-05-30" | |
irb(main):006:0> usa_format = Date.today.strftime("%m/%d/%Y") | |
=> "05/30/2019" | |
irb(main):007:0> uk_format = Date.today.strftime("%d/%m/%Y") | |
=> "30/05/2019" | |
``` | |
There are lots of placeholders you can use in your date string, including: | |
* `%Y` - the year as a 4 digit number | |
* `%m` - the month as a 2 digit number | |
* `%d` - the day as a 2 digit number | |
* `%A` - the day of the week as a word (e.g. Thursday) | |
#### Task 1: What day were you born on? | |
Using `irb` and the `Date` type, work out what day of the week you were born on. | |
<details> | |
<summary>Answer</summary> | |
``` | |
irb(main):001:0> require("date") | |
=> true | |
irb(main):002:0> birthday = Date.new(1988, 1, 20) | |
=> #<Date: 1988-01-20 ((2447181j,0s,0n),+0s,2299161j)> | |
irb(main):003:0> birthday.strftime("%A") | |
=> "Wednesday" | |
``` | |
</details> | |
### Using `File` to read files in Ruby | |
Sometimes it's nice to be able to store things in files, and read these files | |
using Ruby so we can do something with the contents. Ruby has a `File.read` | |
method that lets us do this. | |
`File.read` takes the path to the file as an argument, and returns the | |
contents of the file as a string. | |
``` | |
irb(main):004:0> File.read("/Users/your-username/Desktop/lesson-1-html-and-css/index.html") | |
=> "<!doctype html>\n<html>... | |
``` | |
We can work with the contents using all the string methods (like | |
`.reverse`) we've already seen. | |
There are lots of other useful methods like `.size` (which returns how many | |
characters there are in the string), and `.sub` (which takes two arguments, | |
and substitutes the first occurence of the first string with the second). | |
``` | |
irb(main):005:0> "Cats are the best".sub("Cat", "Dog") | |
=> "Dogs are the best" | |
irb(main):006:0> "Dogs are the best".size | |
=> 17 | |
``` | |
#### Task 2: count the characters in a file | |
We created a file in `lesson-1-html-and-css/index.html`. Use `File.read` | |
and `.size` to work out how many characters there are in the file. | |
> In Finder you can copy the path to a directory by right clicking, holding | |
down the option key (`⌥`), and choosing *Copy "Directory" as Pathname*. | |
Alternatively, you can drag the folder onto the terminal and it will type | |
the path for you. | |
<details> | |
<summary>Answer</summary> | |
``` | |
irb(main):006:0> file = File.read("/Users/your-username/Desktop/lesson-1-html-and-css/index.html") | |
=> "<!doctype html>\n<html>... | |
irb(main):007:0> file.size | |
1260 | |
``` | |
</details> | |
## Further reading | |
If you learn best from _weird_ books then [_why's (poignant) guide](https://poignant.guide/) | |
is excellent (see cartoon below). If you'd prefer something less silly, have a look at: | |
* [Learn to Program](https://pine.fm/LearnToProgram/) | |
* [Learn Ruby The Hard Way](https://learnrubythehardway.org/book/) | |
* [Ruby Koans](http://rubykoans.com/) | |
Which are all great. | |
 |