├── README.md
├── advanced
└── README.md
├── intermediate
├── 18_graphics
│ ├── Keyboard.ttf
│ ├── adding_text_to_image.md
│ ├── mac_coffee.jpeg
│ ├── mac_coffee_copyrighted.jpeg
│ └── text_on_image.rb
└── README.md
└── intro
├── 01_basics
├── README.md
└── family.rb
├── 02_control_flow
├── README.md
└── names.rb
├── 03_loops
├── README.md
└── fibonacci.rb
├── 04_data_structures
├── README.md
└── queen.rb
├── 05_functions
├── README.md
└── card_deck.rb
├── 06_blocks
├── README.md
└── game_rankings.rb
├── 07_symbols
├── README.md
└── sites.rb
├── 08_procs_lambdas
├── README.md
└── towers.rb
├── 09_classes
├── README.md
└── phones.rb
├── 10_scope
├── README.md
└── basic_game.rb
├── 11_encapsulation
├── README.md
└── netflix.rb
├── 12_inheritance
├── README.md
└── robots.rb
├── 13_modules
├── README.md
├── fighter.rb
└── mixins.rb
├── 14_objects
├── README.md
├── enemies.csv
├── heroes.csv
└── rpg.rb
├── 15_exceptions
├── README.md
├── auth.rb
└── users.csv
├── 16_yaml
├── README.md
├── order.rb
└── order.yml
├── 17_xml
└── README.md
└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # ruby
2 |
3 | ## Purpose
4 |
5 | The purpose of this project is to provide information and educational exercises for anyone that wants to explore the Ruby language. Rather than overwhelm you with text, my goal is to provide lots of code snippets and a minimum working knowledge for a given skill level. If you need to dive deeper into the details of Ruby, there are plenty of books and other resources freely available online.
6 |
7 | ## How to Use This
8 |
9 | If you're already an experienced programmer in at least one language, I recommend that you just scan over the materials in the repository and tinker around in your own development environment. On the other hand, if this is your first rodeo and you want my help, I recommend doing the following:
10 |
11 | 1. **Fork** the repository.
12 | 2. **Clone** your forked repository to your computer.
13 | 3. Modify the assignment files included in each module and **commit** the changees when finished.
14 | 4. **Push** the changes to your forked repo.
15 | 5. Issue a **pull request** so that I can quickly see your solution.
16 |
17 | ## How to Sync Updates
18 |
19 | Since you're going to be forking this repository, you'll need to synchronize your repo with mine as I continue to add new content.
20 |
21 | 1. [Configuring a remote for a fork](https://help.github.com/articles/configuring-a-remote-for-a-fork/)
22 | 2. [Syncing a fork](https://help.github.com/articles/syncing-a-fork/)
23 |
24 | Read the above two links if you need more information. If not, please do the following commands:
25 |
26 | ```
27 | git remote add upstream https://github.com/training-mode/ruby.git
28 | git fetch upstream
29 | git checkout master
30 | git merge upstream/master
31 | ```
32 |
33 | If you can figure out how to do all those things, you'll have a better grasp on how to use Github and how to work on a group project.
34 |
--------------------------------------------------------------------------------
/advanced/README.md:
--------------------------------------------------------------------------------
1 | Advanced level Ruby exercises
2 | ====
3 |
--------------------------------------------------------------------------------
/intermediate/18_graphics/Keyboard.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/training-mode/ruby/dd9f9039e7b2dc5310bfc8a41fbd40bc3705d182/intermediate/18_graphics/Keyboard.ttf
--------------------------------------------------------------------------------
/intermediate/18_graphics/adding_text_to_image.md:
--------------------------------------------------------------------------------
1 | # Problem
2 | You want to add some text to an image—perhaps a caption or a copyright statement.
3 | # Solution
4 | * Create an RMagick Draw object and call its annotate method, passing in your image and the text.
5 | * The following code adds the copyright string to the bottom-right corner of the mac_coffee.jpeg image.
6 | * It also specifies the font, the text color and size, and other
7 | features of the text:
8 | * Make sure you have installed rmagick gem and you have font file in the same directory as that of a program.
9 |
10 | ```bash
11 | sudo gem install rmagick
12 | ```
13 |
14 | ```ruby
15 | require 'rubygems'
16 | require 'RMagick'
17 | img = Magick::Image.read('mac.jpeg').first
18 | my_text = "Copyrighted by sks147"
19 | copyright = Magick::Draw.new
20 | copyright.annotate(img, 0, 0, 3, 18, my_text) do
21 | self.font = 'Keyboard.ttf'
22 | self.pointsize = 25
23 | self.font_weight = Magick::BoldWeight
24 | self.fill = 'black'
25 | self.gravity = Magick::SouthEastGravity
26 | end
27 | img.write('mac_coffee_copyrighted.jpeg')
28 | ```
29 |
30 | Initial image:
31 | 
32 |
33 | Final image:
34 | 
35 |
36 | # Discussion
37 | The annotate method takes a code block that sets properties on the ```Magick::Draw```
38 | object, describing how the annotation should be done.
39 | What do these attributes do?
40 | * The font attribute selects the font type from among those installed on your system. You can also specify the path to a specific font that is in a nonstandard location (e.g., /home/sks147/Arial.ttf).
41 | * pointsize is the font size in points (the default is 12). By default, there is one pixel per point, so you can just specify the font size in pixels.
42 | * ```font_weight``` accepts a ```WeightType``` constant. This can be a number (100, 200,
43 | 300,…900), BoldWeight (equivalent to 700), or the default of NormalWeight
44 | (equivalent to 400).
45 | * If you need your text to be italicized, you can set the font_style attribute to ```Magick::ItalicStyle```.
46 | * ```fill``` defines the text color. The default is black. You can use X or SVG color names (such as white, red, gray85, and salmon), or you can express the color in terms of RGB values (such as #fff or #ffffff—two of the most common
47 | formats).
48 | * ```gravity``` controls which part of the image will contain the annotated text, subject to the arguments passed in to annotate. ```SouthEastGravity``` means that offsets will be calculated from the bottom-right corner of the image.
49 | > Draw#annotate itself takes six arguments:
50 | * The Image object, or else an ImageList containing the images you want to
51 | annotate.
52 | * The width and height of the rectangle in which the text is to be positioned.
53 | * The x and y offsets of the text, relative to that rectangle and to the gravity of the Draw object.
54 | * The text to be written.
55 |
56 | * In the Solution we wrote:
57 | ```copyright.annotate(img, 0, 0, 3, 18, my_text)```.
58 | The width and height are zeros, which indicates that annotate should use the whole image as its annotation rectangle. Earlier we gave the Draw object a gravity attribute of SouthEastGravity. This means that annotate will position the text at the bottomright corner of the rectangle; that is, at the bottom-right corner of the image itself.
59 | * The offsets of 3 and 18 indicate that the text should start vertically 18 pixels from the bottom of the box, and end horizontally 3 pixels from the right border of the box.
60 | * To position the text in the center of the image, we just change the gravity:
61 | ```copyright.gravity = Magick::CenterGravity```
62 | ```copyright.annotate(img, 0, 0, 0, 0, my_text)```
63 | * Note that we didn’t have to specify any offsets: CenterGravity orients the text to be in
64 | the exact center of the image. Specifying offsets would only move the
65 | text off-center.
--------------------------------------------------------------------------------
/intermediate/18_graphics/mac_coffee.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/training-mode/ruby/dd9f9039e7b2dc5310bfc8a41fbd40bc3705d182/intermediate/18_graphics/mac_coffee.jpeg
--------------------------------------------------------------------------------
/intermediate/18_graphics/mac_coffee_copyrighted.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/training-mode/ruby/dd9f9039e7b2dc5310bfc8a41fbd40bc3705d182/intermediate/18_graphics/mac_coffee_copyrighted.jpeg
--------------------------------------------------------------------------------
/intermediate/18_graphics/text_on_image.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'RMagick'
3 | img = Magick::Image.read('mac_coffee.jpeg').first
4 | my_text = "Copyrighted by sks147"
5 | copyright = Magick::Draw.new
6 | copyright.annotate(img, 0, 0, 3, 18, my_text) do
7 | self.font = 'Keyboard.ttf'
8 | self.pointsize = 25
9 | self.font_weight = Magick::BoldWeight
10 | self.fill = 'black'
11 | self.gravity = Magick::SouthEastGravity
12 | end
13 | img.write('mac_coffee_copyrighted.jpeg')
--------------------------------------------------------------------------------
/intermediate/README.md:
--------------------------------------------------------------------------------
1 | Intermediate level Ruby exercises
2 | ====
3 |
--------------------------------------------------------------------------------
/intro/01_basics/README.md:
--------------------------------------------------------------------------------
1 | # Basics
2 |
3 | Everyone knows ["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program), right?
4 |
5 | Well, in case you don't remember, the way to do it in Ruby is:
6 | ```ruby
7 | puts "Hello, world!"
8 | ```
9 |
10 | ## Variables
11 | You can declare and assign **variables** of different **types**. Whether it's a number, text, or something more complex, there's a particular kind of **syntax** for its creation or modification.
12 |
13 | For example, if you want to declare a String (i.e. a sequence of text), you can do it like this:
14 | ```ruby
15 | variable_name = "programming" # Don't forget to surround with double quotes
16 | ```
17 |
18 | If you want to declare a number, do it like so:
19 | ```ruby
20 | book_count = 5 # Integer
21 | gpa = 3.7 # Float
22 | ```
23 |
24 | Remember, these **objects** are called variables because they can be *changed*.
25 | ```ruby
26 | book_count = 5
27 | book_count = book_count + 2
28 | book_count += 2 # Same as previous statement
29 | book_count = 30/2 # Division
30 | book_count = 4*6 # Multiplication
31 | book_count = 3**2 # Exponents
32 | ```
33 |
34 | See those symbols? ```+```, ```/```, etc.? They're called **operators**. You'll often be using them in the same way you write down math by hand.
35 |
36 | How do you change Strings in Ruby? Well, there are a lot of ways. If you can imagine it, there's probably a way to do it quite easily in Ruby.
37 |
38 | ```ruby
39 | title = "The Matrix"
40 | title.upcase # Returns an uppercase copy of String (THE MATRIX)
41 | title.upcase! # Modifies String "in place": title permanently changed
42 | title.downcase!
43 | ```
44 |
45 | Understanding whether or not you want to modify a variable in *in place* can be important depending on your program, so make sure you understand the difference. While we'll talk about it more later, simply put, adding a "!" to an object's **method** is short-hand for doing something *in place*.
46 |
47 | You'll often need to **concatenate** Strings in your programming, so here's how:
48 | ```ruby
49 | title = "The Lord of the Rings"
50 | title += ": The Return of the King"
51 | ```
52 |
53 | Essentially, you've combined two Strings into one. Pretty convenient. Can you do more?
54 | ```ruby
55 | language = "Ruby"
56 | language * 3 # RubyRubyRuby
57 | ```
58 |
59 | Is that what you expected? If so, then you're thinking like a Rubyist. When Ruby was created, it was meant to be an expressive and productive language. It should be easy to do things like this, not frustrating and verbose! Ruby will allow you to get relatively a lot done without you having to write a lot of code yourself.
60 |
61 | Arguably, the most flexible way to manipulate Strings in Ruby is through **String interpolation**. Probably the easiest way to think about it is you specify a format that the final output should have and then plug in all the **arguments** that need to be used:
62 | ```ruby
63 | franchise = "Final Fantasy"
64 | number = 7
65 | game = "#{franchise} #{number}" # Final Fantasy 7
66 | ```
67 |
68 | See how flexible it is? You can mix and match variables of different types.
69 |
70 | ## Constants
71 | If you expect a value to never change during the life of a program, that's a **constant** and should be treated specially. In some other languages, it is impossible to change a constant after it's been created. Although the Ruby interpreter can't enforce this in the same way, it can alert you with warnings if something in your program tries to change a constant.
72 |
73 | ```ruby
74 | GRAVITATIONAL_CONST = 9.8
75 | GRAVITATIONAL_CONST = 28 # This will cause a warning
76 | ```
77 |
78 | *Note: It is convention to declare constants this way. Remember to declare it with all caps.*
79 |
80 | # Knowledge Check
81 |
82 | 1. Who is the principal designer of the Ruby language?
83 |
84 | # Assignment
85 | family.rb
86 | ```ruby
87 | # Ages of family members
88 | mom = 48
89 | dad = 51
90 | john = 18
91 | mary = 16
92 | ```
93 |
94 | Using the above code, create a program which declares a variable, **age_sum**, that stores the sum of ages of each family member. Also create a variable, **result**, which multiples the mom and dad's ages together and divides that by the difference between John and Mary's ages.
95 |
96 | The program should print the following to the console:
97 | ```ruby
98 | Sum of ages = # ?
99 | Result = # ?
100 | ```
101 |
102 | *Extra Credit: The program should also print the message shown above, but with each line reversed.*
103 |
--------------------------------------------------------------------------------
/intro/01_basics/family.rb:
--------------------------------------------------------------------------------
1 | # Ages of family members
2 | mom = 48
3 | dad = 51
4 | john = 18
5 | mary = 16
6 |
7 | # Your code goes here
8 |
9 |
--------------------------------------------------------------------------------
/intro/02_control_flow/README.md:
--------------------------------------------------------------------------------
1 | # Control Flow
2 |
3 | Oftentimes, programs need dynamic behavior, otherwise a program will always and only ever do *1* thing. Thankfully, **conditional statements** are a staple of programming languages and Ruby is no different. Here are some things you need to know.
4 |
5 | ## If-Else
6 |
7 | A very basic example of an if-else block:
8 |
9 | ```ruby
10 | # 2014 population
11 | japan = 126000000
12 | usa = 319000000
13 |
14 | if japan < usa # Is 126000000 less than 319000000? Yes
15 | puts "U-S-A!" # This is executed
16 | else # This is ignored
17 | puts "Nippon Ichi!"
18 | end
19 | ```
20 |
21 | In an if-else block, the ***if*** statement evaluates a **boolean expression**. This checks whether a comparison is **true** or **false**. If a comparison is found to be true, the program proceeds to the block directly beneath it. If found to be false, the block is ignored and the program checks the next comparison (an ***elsif*** or ***else*** statement).
22 |
23 | If you need to compare values, you must use the **relational operators**:
24 | ```
25 | > # Greater than
26 | < # Less than
27 | == # Equal to
28 | >= # Greater than or equal to
29 | <= # Less than or equal to
30 | ```
31 |
32 | You can have additional statements in your if-else block if you need them. Just use the **elsif** keyword:
33 | ```ruby
34 | temperature = -1
35 | if temperature > 30 # Is -1 > 30? Nope
36 | puts "Hot"
37 | elsif temperature > 15 # Is -1 > 15? Nope
38 | puts "Warm"
39 | elsif temperature > 0 # Is -1 > 0? Nope
40 | puts "Cold"
41 | else # None of the above were true, so this must execute
42 | puts "Very cold!" # This is the result
43 | end
44 | ```
45 |
46 | If it wasn't already clear, you *can* use an ```if``` statement without an ```else``` statement. In some programs, this makes sense:
47 |
48 | ```ruby
49 | person = "King"
50 | if person == "King"
51 | puts "Welcome back, Your Majesty"
52 | end
53 | ```
54 |
55 | Lastly, you can shorten a conditional statement to a 1-liner by using the ```then``` keyword:
56 | ```ruby
57 |
58 | gpa = 4
59 | if gpa == 4 then puts "Strong!" else puts "Try harder..." end
60 | ```
61 |
62 | ## Ternary Expression
63 |
64 | You may encounter this in some programs, but you can represent conditional statements using **ternary expressions**. The above statement can be equivalently written the following way:
65 |
66 | ```ruby
67 | gpa = 4
68 | puts gpa == 4 ? "Strong" : "Try harder..."
69 | ```
70 |
71 | ## Unless
72 |
73 | Does all that make sense? I hope so, because I'm about throw one more kind of conditional keyword at you: **unless**. While **if** statements are testing whether a condition is true, ```unless``` statements are testing whether a statement evaluates to **false**! Here's an example of where you might want to use it:
74 |
75 | ```ruby
76 | is_convict = false
77 | unless is_convict
78 | puts "You can vote."
79 | end
80 | ```
81 |
82 | ```ruby
83 | name = "Scrub"
84 | puts "You are not the best Street Fighter" unless name == "Daigo"
85 | ```
86 |
87 | ## Case
88 |
89 | An alternative to using the **if-else** statement in Ruby is **case**. This construct works pretty much the same way except it can be easier to match multiple values at once.
90 |
91 | ```ruby
92 | age = 24
93 | case age
94 | when 0..12
95 | puts "Child"
96 | when 13..19
97 | puts "Teenager"
98 | when 20..30
99 | puts "Young Adult"
100 | else
101 | puts "Adult"
102 | end
103 | ```
104 |
105 | What you can take away from this is that Ruby offers you quite a bit of flexibility on how to write conditional statements. Try not to feel overwhelmed at all of the options, but instead just focus on one or two styles. Keep using them until you feel really comfortable. At that point you might want to try the others.
106 |
107 | # Knowledge Check
108 | 1. What is the result of ```"c" < "C"```? What about ```"1" > "A"```?
109 |
110 | # Assignment
111 | names.rb
112 |
113 | ```ruby
114 | name1 = "Joffrey Baratheon"
115 | name2 = "Arya Stark"
116 | name3 = "Beric Dondarrion"
117 | name4 = "Melisandre"
118 | ```
119 |
120 | The characters in *Game of Thrones* are quite memorable, but memorizing their names can be difficult. The exercise in this unit is to find the average length of the names shown above and store it in a variable, **avg_length**. After that, you should declare a variable, **my_name**, and assign it the value that a user types into the console. Lastly, you should use an if-else block to compare the length of **my_name** to **avg_length** and print "[my_name] is shorter than average" when it is shorter, and "[my_name] is longer than average" otherwise.
121 |
--------------------------------------------------------------------------------
/intro/02_control_flow/names.rb:
--------------------------------------------------------------------------------
1 | # Game of Thrones characters
2 | name1 = "Joffrey Baratheon"
3 | name2 = "Arya Stark"
4 | name3 = "Beric Dondarrion"
5 | name4 = "Melisandre"
6 |
7 | # Your code goes here
8 |
9 |
--------------------------------------------------------------------------------
/intro/03_loops/README.md:
--------------------------------------------------------------------------------
1 | # Loops
2 |
3 | If I asked you to write a program that prints ```Hi, mom!``` three times, how would you write it? Probably something like this:
4 |
5 | ```ruby
6 | puts "Hi, mom!"
7 | puts "Hi, mom!"
8 | puts "Hi, mom!"
9 | ```
10 |
11 | That's manageable, isn't it? Okay, but what if I asked you to do the same thing except 1000 times? A million times? This is where **loops** come to the rescue.
12 |
13 | ## While
14 |
15 | ```ruby
16 | count = 0
17 |
18 | while count < 1000
19 | puts "Hi, mom!"
20 | count += 1 # Important!
21 | end
22 | ```
23 |
24 | Let's break it down. The block of code contained in the ```while``` section is repeated as long as the variable ```count``` is less than 1000. It's very important you increment count here so that it eventually reaches its termination condition, otherwise you'll end up with an **infinite loop**.
25 |
26 | ```ruby
27 | count = 0
28 |
29 | while count < 1000 # Count will never equal 1000
30 | puts "Hi, mom!" # "Hi, mom!" x infinity
31 | end
32 | ```
33 |
34 | ## Until
35 |
36 | Similar to ```unless``` and ```if```, ```until``` mirrors the behavior of ```while``` except it tests that a certain condition is false rather than true.
37 |
38 | ```ruby
39 | count = 10
40 |
41 | until count < 1
42 | puts "T-minus #{count}..."
43 | count -= 1
44 | end
45 |
46 | puts "Blast off!"
47 | ```
48 |
49 | ## For
50 |
51 | **For-loops** are a common and popular alternative to **while-loops** when you must repeat a sequence of code a certain number of times. We can rewrite the above examples in the following way:
52 |
53 | ```ruby
54 | for i in 1..1000
55 | puts "Hi, mom!"
56 | end
57 | ```
58 |
59 | ```ruby
60 | for count in 10.downto(1)
61 | puts "T-minus #{count}..."
62 | end
63 |
64 | puts "Blast off!"
65 | ```
66 |
67 | Seems pretty succinct, doesn't it? Well, Ruby offers you this additional style:
68 |
69 | ```ruby
70 | (1..3).each do |i|
71 | puts "Round #{i} - FIGHT!"
72 | end
73 | ```
74 |
75 | The important thing to note about these for-loops is that they are storing the iteration of the loop in a variable which can be used inside the loop's scope. If you don't care about that, you might want to use something like this instead:
76 |
77 | ```ruby
78 | 1000.times do
79 | puts "Am I under arrest?"
80 | end
81 | ```
82 |
83 | ## Loop
84 |
85 | The last kind I'd like to mention is invoked using the ```loop``` keyword. Go figure.
86 |
87 | ```ruby
88 | coin_flips = 0
89 |
90 | loop do
91 | coin_flips += 1
92 | puts "Coin flipped #{coin_flips} times."
93 |
94 | if coin_flips == 100
95 | break
96 | end
97 | end
98 |
99 | puts "Game Over"
100 | ```
101 |
102 | ## Break
103 |
104 | Anything that you put in a ```loop``` block will happen infinitely unless you use the keyword ```break```. Using ```break``` inside of a loop will cause the program to leave that block of code and return to the next highest scope. Let's get a bit more complicated and show what that means.
105 |
106 | ```ruby
107 | while true
108 | while true
109 | puts "Inner block"
110 | break
111 | end
112 |
113 | puts "Outer block"
114 | break
115 | end
116 | ```
117 |
118 | Without the ```break``` keyword in nested loop, we would have an infinite loop that prints ```Inner block```. Likewise, without the second ```break```, there'd be an infinite loop of ```Outer block```. However, since we have both, the above code will only print:
119 |
120 | ```ruby
121 | Inner block
122 | Outer block
123 | ```
124 |
125 | From this, we can see that the break statement will only escape from whatever the immediate loop block of code is and return to the next highest scope.
126 |
127 | ## Next
128 |
129 | The next closest relative to ```break``` is the keyword **next**. Using ```next``` in a loop will immediately skip to the subsequent iteration of the loop, ignoring the rest of the statements in the block.
130 |
131 | ```ruby
132 | (0...4).each do |i| # ... is exclusive rather than inclusive (..)
133 | next if i < 2 # Ignore the rest of the loop and begin a new iteration
134 |
135 | puts "The value of i is #{i}"
136 | end
137 | ```
138 |
139 | Since the above code skips the block when i is 0 and 1, the output of this code is:
140 |
141 | ```ruby
142 | The value of i is 2
143 | The value of i is 3
144 | ```
145 |
146 | As you can see, while ```break``` leaves the entire enclosing loop immediately, ```next``` just skips to the next iteration.
147 |
148 | Finally, although Ruby also offers the ```redo``` and ```retry``` keywords when working with loops, you probably won't need to know them right now. If you're curious, please look them up and think of ways to use them.
149 |
150 | # Knowledge Check
151 |
152 | 1. Are variables declared in a for-loop available after a loop has ended?
153 |
154 | # Assignment
155 | fibonacci.rb
156 |
157 | The Fibonacci sequence is a popular programming exercise for new programmers. If you're not familiar with it, the first ten Fibonacci numbers are 1, 1, 2, 3, 5, 8, 13, 21, 34, and 55. You should be able to see a pattern in this sequence. Using conditionals and looping techniques we've covered so far, write some code that will print each of first ten Fibonacci numbers on its own line.
158 |
159 | ```ruby
160 | # Print out all the Fibonacci numbers from 1 to 10 in order
161 |
162 | # Your code goes here
163 |
164 | ```
165 |
--------------------------------------------------------------------------------
/intro/03_loops/fibonacci.rb:
--------------------------------------------------------------------------------
1 | # Print out all the Fibonacci numbers from 1 to 10 in order
2 |
3 | # Your code goes here
4 |
5 |
--------------------------------------------------------------------------------
/intro/04_data_structures/README.md:
--------------------------------------------------------------------------------
1 | #Data Structures#
2 |
3 | A common need when writing programs is to organize data efficiently. This becomes increasingly apparent as your code grows bigger and more sophisticated. Rather than telling you all about it, I'll try to show you.
4 |
5 | **Containers** are a staple of clean, efficient, and well organized programming. They allow us to store many data items together in a logical way for later use. The most fundamental and basic container you'll learn is an **Array**.
6 |
7 | ##Arrays##
8 |
9 | ```ruby
10 | meals_of_the_day = ["Breakfast", "Lunch", "Dinner"]
11 |
12 | puts meals_of_the_day[0] # Breakfast
13 | puts meals_of_the_day[1] # Lunch
14 | puts meals_of_the_day[2] # Dinner
15 | ```
16 |
17 | First thing's first. You can store objects of all kinds in a container, whether they are Integers, Floats, or Strings. To access an individual item in an Array, you use the syntax ```array_name[n]``` where n represents the **index** into the Array. The first item in an Array always corresponds to 0 rather than 1 as you might assume.
18 |
19 | ```ruby
20 | days_of_the_week = [
21 | "Sunday", # 0
22 | "Monday", # 1
23 | "Tuesday", # 2
24 | "Wednesday", # 3
25 | "Thursday", # 4
26 | "Friday", # 5
27 | "Saturday" # 6
28 | ]
29 |
30 | puts days_of_the_week.length # 7
31 | puts days_of_the_week.count # 7
32 | puts days_of_the_week.size # 7
33 | ```
34 |
35 | As you can see, the index corresponding to the last item in an Array is always the length of the Array minus 1. This is due to the 0-indexing of Array elements. If you want to know how many items are currently in an Array, you can call the methods ```length```, ```count```, and ```size``` (Note: we'll delve more into methods later).
36 |
37 | Sometimes it's easier to access Array elements in reverse order. You can see that in the following code:
38 |
39 | ```ruby
40 | array1 = [0, 1, 2, 3]
41 |
42 | puts array1[0] == array1[-4] # true
43 | puts array1[1] == array1[-3] # true
44 | puts array1[2] == array1[-2] # true
45 | puts array1[3] == array1[-1] # true
46 | ```
47 |
48 | One thing that may or may not surprise you is that you can interact with the individual characters in a String object the same way.
49 |
50 | ```ruby
51 | question = "To be, or not to be..."
52 |
53 | puts question[0] # T
54 | puts question[-1] # .
55 | ```
56 |
57 | The Arrays above are **homogenous** because all of the elements in the container are the same type. Similarly, you can create a **heterogenous** or "mixed" Array just as easily.
58 |
59 | ```ruby
60 | mixed_array = [123, "up-down-left-right", 99.99]
61 | ```
62 |
63 | Ruby, being the flexible language that it is, allows you to create an Array using this syntax too:
64 |
65 | ```ruby
66 | array1 = Array.new # [] Empty array
67 | array2 = Array.new(3) # [nil, nil, nil] No default value specified
68 | array3 = Array.new(3, 0) # [0, 0, 0] Default value is 0
69 | ```
70 |
71 | You'll often want to add new items to an Array after you've already declared it. Ruby allows you to do that in the following ways:
72 |
73 | ```ruby
74 | dicaprio_films = ["Catch Me If You Can", "The Aviator", "The Departed"]
75 |
76 | dicaprio_films.push("Blood Diamond") # Add to the end of Array
77 | dicaprio_films << "Revolutionary Road" # Add to the end of Array
78 | dicaprio_films.insert(4, "Body of Lies") # Add at index 4
79 | ```
80 |
81 | You can also delete elements from an Array:
82 |
83 | ```ruby
84 | family = ["Peter", "Lois", "Chris", "Meg", "Stewie", "Brian"]
85 |
86 | family.pop # Removes last item (Brian)
87 | family.delete_at(3) # Removes Meg
88 | ```
89 |
90 | **Multidimensional arrays** are the last kind of Array you should know at this time. An "array-of-arrays" is the most basic container which stores other containers. You'll find this in all sorts of programs.
91 |
92 | ```ruby
93 | student_grades = [[100,97,99], [95,90,97], [0,0,0]]
94 | mixed_array = ["a", ["b","c"], 4]
95 | favorites = [
96 | ["Chipotle", "Qdoba"],
97 | ["Metal Gear Solid 3", "The Last of Us", "Ico"],
98 | ["Ruby", "Python"]
99 | ]
100 | ```
101 |
102 | You should note that the elements in a multidimensional array don't necessarily all need to be the same size! Unless you know the dimensions of an Array before you access it, don't assume anything!
103 |
104 | Speaking of accessing Arrays, how can we do that easily?
105 |
106 | ```ruby
107 | years = [2011, 2012, 2013, 2014]
108 |
109 | for year in years
110 | puts year
111 | end
112 |
113 | multi_array = [
114 | [0,1],
115 | ["a", "b", "c"],
116 | [1.11, 2.22],
117 | [true, false, true]
118 | ]
119 |
120 | for i in multi_array
121 | for item in i
122 | puts item
123 | end
124 | end
125 |
126 | # Equivalent
127 | multi_array.each do |i|
128 | i.each do |item|
129 | puts item
130 | end
131 | end
132 | ```
133 |
134 | As you can see, iterating over Arrays is a piece of cake when using for-loops. As long as you're going over each item sequentially, this kind approach makes the most sense.
135 |
136 | It's a common need to only take subsets of an an Array. For example, if you only want the third, fourth, and fifth items from an Array, you can do that like this:
137 |
138 | ```ruby
139 | courses = ["Geometry", "Biology", "World History", "P.E.", "Jazz Band"]
140 | courses[2..4] # ["World History", "P.E", "Jazz Band"]
141 | ```
142 |
143 | Whether you're building a list of "Top 3 Favorites" or "5 Most Hated," subscripting an Array in this was is very helpful.
144 |
145 | ##Hashes##
146 |
147 | Another common and convenient way to organize data is to map key-value pairs in a container. That is the core idea behind a **Hash** (also known as hashmap or dictionary).
148 |
149 | ```ruby
150 | book = {
151 | "title" => "The Sun Also Rises",
152 | "author" => "Ernest Hemingway",
153 | "page_count" => 251
154 | }
155 |
156 | puts book["title"] # The Sun Also Rises
157 | puts book["author"] # Ernest Hemingway
158 | puts book["page_count"] # 251
159 | ```
160 |
161 | The syntax for accessing a value stored in a Hash is given by ```hash_name["key"]```. For every key in a Hash, there is a corresponding value. If a value doesn't already exist for a given key, a Hash will return ```nil```.
162 |
163 | ```ruby
164 | puts book["publish_date"] # nil
165 | ```
166 |
167 | Similar to Arrays, Hashes can be created with an alternate syntax:
168 |
169 | ```ruby
170 | high_scores = Hash.new # No default value specified
171 | high_scores["gradius"] = 17333150
172 |
173 | answers = Hash.new(false) # Default value is false
174 | puts answers["question_1"] # false
175 | ```
176 |
177 | You can store objects of all kinds in a Hash, and like Arrays, it need not be one level deep:
178 |
179 | ```ruby
180 | course = {
181 | "name" => "History 101", # String
182 | "minutes" => 60, # Integer
183 | "students" => [ # Array of Hashes!
184 | {"name" => "Mary", "age" => 20},
185 | {"name" => "Takeshi", "age" => 21},
186 | {"name" => "John", "age" => 20}
187 | ]
188 | }
189 | ```
190 |
191 | To iterate over a Hash, you can do it two different ways: 1) using a for-loop, or 2) using ```each```:
192 |
193 | ```ruby
194 | for key, value in course
195 | puts "#{key} => #{value}"
196 | end
197 |
198 | course.each do |key, value|
199 | puts "#{key} => #{value}"
200 | end
201 | ```
202 |
203 | #Knowledge Check#
204 | 1. What is the return value of an Array element outside the bounds of the Array?
205 |
206 | #Assignment#
207 | queen.rb
208 |
209 | I'm listening to *Bohemian Rhapsody* right now and so should you! Let's see if you've been absorbing all of these data structures. I've provided the opening lyrics to their signature anthem. Your task is to print out each unique character in the lyrics along with the number of times it appears.
210 |
211 | ```ruby
212 | # A little bit of classic rock
213 |
214 | lyrics = "Is this the real life?"\
215 | "Is this just fantasy?"\
216 | "Caught in a landslide,"\
217 | "No escape from reality."
218 |
219 | # Your code goes here
220 |
221 | ```
222 |
--------------------------------------------------------------------------------
/intro/04_data_structures/queen.rb:
--------------------------------------------------------------------------------
1 | # A little bit of classic rock
2 |
3 | lyrics = "Is this the real life?"\
4 | "Is this just fantasy?"\
5 | "Caught in a landslide,"\
6 | "No escape from reality."
7 |
8 | # Your code goes here
9 |
10 |
--------------------------------------------------------------------------------
/intro/05_functions/README.md:
--------------------------------------------------------------------------------
1 | #Functions#
2 |
3 | Repeating yourself can be very frustrating. The same holds true when you're coding. If you find yourself writing the same set of instructions over and over again, you've probably found a good opportunity to reorganize your code with a **function**. Known by many names (including **subroutine**, **subprogram**, **method** and more), functions are a boon to reusability and programming productivity. In software development, you'll often hear of the virtue of **DRY**: Don't Repeat Yourself.
4 |
5 | ##How?##
6 |
7 | ```ruby
8 | def greeting(person)
9 | puts "Hello, #{person}."
10 | end
11 |
12 | greeting("Clarice") # Hello, Clarice.
13 | greeting "Clarice" # Hello, Clarice.
14 | ```
15 |
16 | The syntax of a basic function is shown above. Using the keyword ```def```, you can declare a function for use in other parts of your code. In the above example, ```person``` is called a function **parameter**. According to your program, the function only expects one object to be passed to it. To execute a function, you use the syntax ```function_name(argument)```. Make note that parameters and **arguments** are conceptually the same except that parameter is the name of what appears in the function signature whereas argument represents the concrete object passed in a function call.
17 |
18 | You might have noticed in the example above that you *can* omit the parentheses around the function argument when calling it. We have seen that many times before when using the ```puts``` function. For functions with a limited number of simple arguments, this can be convenient. However, for functions with more complicated arguments, the readability of your code improves with the inclusion of the explicit parentheses.
19 |
20 | ```ruby
21 | def jump # No arguments
22 | puts "You jumped!"
23 | end
24 |
25 | def add(x, y)
26 | return x + y # This function returns the sum of x and y
27 | end
28 |
29 | def multiply(x, y)
30 | x * y # Implicit return: returns the product of x and y
31 | end
32 |
33 | def roll_dice(sides=6) # Default parameter
34 | return rand(sides) + 1
35 | end
36 |
37 | jump # You jumped!
38 | puts(add(multiply(2,3), multiply(3,4)) # puts(add(6, 12))
39 | # puts(18)
40 | roll_dice # rand(6) + 1
41 | roll_dice(12) # rand(12) + 1
42 | ```
43 |
44 | As you can see above, functions can either **return** a value or not. In ```jump```, the function merely prints some text by itself. On the other hand, ```add``` and ```multiply``` return a value when the function is executed. In the case of ```add```, the function explicitly uses the keyword ```return```. This need not be the case, as you can see in ```multiply```. This is an example of what makes Ruby different from many other languages. **Implicit return** automatically returns whatever the evaluation of the final line in a function is.
45 |
46 | Having seen the above examples, outside of syntax errors, can you think of any ways the above code will behave in unexpected ways? What do you think will happen here?
47 |
48 | ```ruby
49 | add([1,2], [3,4]) # [1, 2, 3, 4]
50 | add([], 1) # no implicit conversion of Fixnum into Array (TypeError)
51 | add(1, "Hi") # String can't be coerced into Fixnum (TypeError)
52 | ```
53 |
54 | This is where we run into the philosophy of the Ruby language and the concept of **Duck Typing**. In many other languages, function parameters must be explicitly typed in the function definition. If you call that function at any point in your program, you *have* to provide the correct arguments to it or your program will not even compile. You'll often hear the terms **static** and **dynamic** thrown around when discussing typing, and of the two, Ruby falls in with the latter.
55 |
56 | This flexibility means that your code can react in multiple ways depending on the type of the objects being manipulated. As you can see above, this can be a double-edged sword if you're not careful. Since Ruby programs are not compiled, errors like this will only be discovered once a program executes (known as a **runtime exception**). As you become more experienced, writing your code with a dynamically typed mindset will become increasingly easy.
57 |
58 | Sometimes it makes sense for a function to have more than one return statement. In fact, it's fairly common for a function to return early based on some conditional checks:
59 |
60 | ```ruby
61 | def validate_user(username)
62 | if username == "admin"
63 | return true
64 | elsif username == "BillLumbergh"
65 | return true
66 | end
67 |
68 | return false
69 | end
70 |
71 | # FYI, you can rewrite the function like this
72 | def validate_user1(username)
73 | return username == "admin" || username == "BillLumbergh"
74 | end
75 |
76 | # Potentially even better
77 | def validate_user2(username)
78 | authorized_users = ["admin", "BillLumbergh"]
79 |
80 | return authorized_users.include? username
81 | end
82 | ```
83 |
84 | Early return statements can often allow programmers to debug a flow of execution more easily. You'll definitely run into this kind of thing in the future, so it's good idea to get a handle on it.
85 |
86 | The last thing I'd like to cover regarding functions is **recursion**. Did you know that a function can return itself? Well, they can! Do you remember the Fibonacci assignment from the Loop module? It turns out that you can solve it pretty easily through recursion:
87 |
88 | ```ruby
89 | def fibonacci(n)
90 | return 1 if n == 1 || n == 2
91 |
92 | return fibonacci(n - 1) + fibonacci(n - 2)
93 | end
94 |
95 | # Alternative
96 | def fibonacci1(n)
97 | n <= 2 ? 1 : fibonacci1(n - 1) + fibonacci1(n - 2)
98 | end
99 |
100 | for i in 1..10
101 | puts fibonacci(i)
102 | end
103 | ```
104 |
105 | Really clean, isn't it? You can think of recursion as the functional analogue to loops. It may take some cognitive rewiring for you to incorporate recursion into your own programming, but it can be a very valuable skill for you to have. But on that note, do you see anything wrong with the code above? How about this...
106 |
107 | ```ruby
108 | puts fibonacci(-1) # stack level too deep (SystemStackError)
109 | ```
110 |
111 | Much like loops, recursion is susceptible to the same problem. **Infinite recursion** will repeat until a program's stack runs out of memory. If you've ever heard of the term **stack overflow**, this is an example of that.
112 |
113 | ##Why?##
114 |
115 | 1. Reusability: functions eliminate redudant parts of your code. This allows you to use a logical piece of programming repeatedly and easily (and even in other programs).
116 | 2. Organization: a function definition isolates a program's behavior into one place. When something goes wrong, it's easier to identify the problem.
117 |
118 | #Knowledge Check#
119 | 1. Does a function call incur any overhead? In other words, is it faster to execute a series of instructions or a function with the same instructions?
120 | 2. What is a default parameter?
121 |
122 | #Assignment#
123 | card_deck.rb
124 |
125 | Just about every home has a deck of cards hanging around somewhere. Are they Bicycle brand? What I'd like you to do is define a function, ```create_deck```, that returns an array of Strings. Each element in the array represents a card, and the cards are sorted in the order of Hearts, Diamonds, Clubs, then Spades, with each suit sorted in ascending order (from 2 to Ace).
126 |
127 |
128 | ```ruby
129 | def create_deck
130 | # Your code goes here
131 | end
132 |
133 | card_deck = create_deck
134 |
135 | puts card_deck[0] == "2h" # 2 of Hearts
136 | puts card_deck[8] == "10h" # 10 of Hearts
137 | puts card_deck[51] == "As" # Ace of Spades
138 | ```
139 |
--------------------------------------------------------------------------------
/intro/05_functions/card_deck.rb:
--------------------------------------------------------------------------------
1 | # Create a standard 52 card deck
2 | # Sort card suits according to Hearts, Diamonds, Clubs, then Spades.
3 | # Sort cards in ascending order, from 2 to Ace.
4 |
5 | def create_deck
6 | # Your code goes here
7 | end
8 |
9 |
10 | card_deck = create_deck
11 |
12 | puts card_deck[0] == "2h" # 2 of Hearts
13 | puts card_deck[8] == "10h" # 10 of Hearts
14 | puts card_deck[51] == "As" # Ace of Spades
15 |
--------------------------------------------------------------------------------
/intro/06_blocks/README.md:
--------------------------------------------------------------------------------
1 | #Blocks#
2 |
3 | If you have a good understanding of functions, **blocks** should be easy to pick up as well. In fact, it's easiest to think of blocks as nameless functions. Let's take a look.
4 |
5 | ```ruby
6 | [1, 2, 3].each do |i| # Block start
7 | puts i
8 | end # Block end
9 |
10 | [4, 5, 6].each { |i| puts "Hi" * i } # This is also a block
11 | ```
12 |
13 | As you can see, the ```each``` method expects a block as an argument. This can be invoked with the keywords ```do``` and ```end```, or by using braces (```{}```).
14 |
15 | You might be wondering, "How is this helpful?" Well, in the previous examples, passing a block to a function essentially lets you customize the behavior of that function. Specifically, even though we're calling ```each``` on arrays of integers in every case, the block ultimately determines what happens to the elements. This **abstraction** shifts the focus onto the function's caller and ultimately makes the function more flexible.
16 |
17 | Arrays actually have a lot of different methods which accept blocks as arguments. Here are a few:
18 |
19 | ```ruby
20 | array1 = [1, 11, 111, 1111]
21 | array1.map! { |i| i -= 1 } # array1 == [0, 10, 110, 1110]
22 |
23 | songs = ["My Favorite Things", "Climb Ev'ry Mountain", "Do-Re-Mi"]
24 | songs.sort! { |first, second| second <=> first } # Sort descending order
25 |
26 | primes = [2, 3, 5, 7, 11, 13, 17, 19]
27 | primes.select { |i| i > 10 } # [11, 13, 17, 19]
28 | ```
29 |
30 | Likewise, hashes also have methods which accept blocks as arguments.
31 |
32 | ```ruby
33 | imdb_ratings = {
34 | "The Shawshank Redemption" => 9.2,
35 | "The Godfather" => 9.2,
36 | "The Godfather: Part II" => 9.0,
37 | "The Dark Knight" => 8.9,
38 | "Pulp Fiction" => 8.9
39 | }
40 |
41 | imdb_ratings.select { |k, v| v > 9 }
42 | imdb_ratings.reject { |k, v| v > 9 }
43 |
44 | more_ratings = {
45 | "12 Angry Men" => 8.9,
46 | "Schindler's List" => 8.9
47 | }
48 |
49 | imdb_ratings.merge!(more_ratings)
50 | ```
51 |
52 | Are you wondering how you can write your own functions to work with blocks? The secret lies in the keyword ```yield```:
53 |
54 | ```ruby
55 | def block_required
56 | yield # Simplest way to execute a block
57 | end
58 |
59 | def block_optional
60 | yield if block_given? # Only executes block if it's provided
61 | puts "Finished."
62 | end
63 |
64 | def random_attack
65 | attacks = {
66 | 0 => "Jab",
67 | 1 => "Strong",
68 | 2 => "Fierce",
69 | 3 => "Short",
70 | 4 => "Forward",
71 | 5 => "Roundhouse"
72 | }
73 |
74 | yield attacks[rand(attacks.length)] if block_given?
75 | end
76 |
77 | block_required { puts "Yielding block" } # Yielding block
78 | block_optional # Finished.
79 |
80 | random_attack { |attack| puts attack }
81 | random_attack # Does nothing
82 | ```
83 |
84 | The first, ```block_required```, shows the absolute minimum needed to execute a function which accepts a block as an argument. By itself, ```yield``` will merely execute the contents of the block passed to it. On the other hand, ```block_optional``` shows that within a function's body, you can check whether or not a block was provided. This allows you to add instructions that will execute no matter if a block is passed or not. Lastly, ```random_attack``` is an example of a function that yields a value back to the block. Keep in mind that it is possible to yield more than one value from a function. As you become more experienced, you might encounter times where this is appropriate.
85 |
86 | #Knowledge Check#
87 | 1. What's the difference between calling ```songs.sort``` and ```songs.sort!```?
88 |
89 | #Assignment#
90 | game_rankings.rb
91 |
92 | The Nintendo 64 was a very popular game console when I was growing up. People often like to argue about games based on their scores from magazines and other critical publications. Your task in this assignment is to find the average score of the top 10 games in ```game_rankings```. Once you have this value, *using blocks*, print out only the games in the list who have a score greater than the calculated average.
93 |
94 | ```ruby
95 | # Top 10 Nintendo 64 games from Gamerankings
96 |
97 | game_rankings = {
98 | "The Legend of Zelda: Ocarina of Time" => 0.9754,
99 | "Super Mario 64" => 0.9641,
100 | "GoldenEye 007" => 0.9470,
101 | "Perfect Dark" => 0.9455,
102 | "The Legend of Zelda: Majora's Mask" => 0.9195,
103 | "1080: TenEighty Snowboarding" => 0.8960,
104 | "Conker's Bad Fur Day" => 0.8928,
105 | "Excitebike 64" => 0.8907,
106 | "Turok 2: Seeds of Evil" => 0.8896,
107 | "Paper Mario" => 0.8881
108 | }
109 |
110 | # Your code goes here
111 |
112 | ```
113 |
--------------------------------------------------------------------------------
/intro/06_blocks/game_rankings.rb:
--------------------------------------------------------------------------------
1 | # Top 10 Nintendo 64 games from Gamerankings
2 |
3 | game_rankings = {
4 | "The Legend of Zelda: Ocarina of Time" => 0.9754,
5 | "Super Mario 64" => 0.9641,
6 | "GoldenEye 007" => 0.9470,
7 | "Perfect Dark" => 0.9455,
8 | "The Legend of Zelda: Majora's Mask" => 0.9195,
9 | "1080: TenEighty Snowboarding" => 0.8960,
10 | "Conker's Bad Fur Day" => 0.8928,
11 | "Excitebike 64" => 0.8907,
12 | "Turok 2: Seeds of Evil" => 0.8896,
13 | "Paper Mario" => 0.8881
14 | }
15 |
16 | # Your code goes here
17 |
18 |
--------------------------------------------------------------------------------
/intro/07_symbols/README.md:
--------------------------------------------------------------------------------
1 | #Symbols#
2 |
3 | What is a Ruby **Symbol**? While it is similar to a String, it is best to think of it as conceptually closer to a name. What separates a Symbol from a String can be illustrated more succinctly in the following examples.
4 |
5 | ##Identity##
6 |
7 | ```ruby
8 | string1 = "Antifragile"
9 | string2 = "Antifragile"
10 | string3 = string1
11 |
12 | string1 == string2 # true
13 | string1.object_id == string2.object_id # false
14 | string1.object_id == string3.object_id # true
15 | ```
16 |
17 | When you compare two Strings, Ruby checks if the value of Strings are the same. This is usually what you'll want to know in your average program. However, if you actually compare a String's ```object_id``` with another newly declared String's ID, even if the Strings have the same value, the identifiers will be different. The reason for this has to do with the way a Ruby program keeps track of all the objects currently in its memory. Contrast that with ```string3```: when you compare the ```object_id``` of ```string1``` and ```string3```, you will see they are actually *equal*! In this way, you can see that ```string3``` is actually just pointing to the same thing in memory as ```string1```.
18 |
19 |
20 | Let's see what happens when you try a similar test with Symbols instead:
21 |
22 | ```ruby
23 | symbol1 = :antifragile
24 | symbol2 = :antifragile
25 | symbol3 = symbol1
26 |
27 | symbol1 == symbol2 # true
28 | symbol1.object_id == symbol2.object_id # true
29 | symbol1.object_id == symbol3.object_id # true
30 | ```
31 |
32 | All of the comparisons evaluate to true! This means that all of these Symbols actually point to the *same* object in the program's memory. This fact highlights one important consequence of using Symbols over Strings: since they are only stored once, **they save memory**.
33 |
34 | ##Immutability##
35 |
36 | We'll explore the topic of mutability more deeply in future modules, but since we're introducing Symbols in this lesson, it's important we touch on it right now. By default, Strings are *mutable*, meaning that their values can be freely changed without also changing the underlying ```object_id```.
37 |
38 | ```ruby
39 | song = "Good Riddance"
40 | song.object_id # e.g. 69872901258840
41 | song << " (Time of Your Life)" # Good Riddance (Time of Your Life)
42 | song.object_id # Unchanged!
43 | ```
44 |
45 | Even though we've modified the String's text, we're still dealing with the original object in memory.
46 |
47 | When something is labeled as **immutable**, the opposite is true. An immutable object *cannot* be changed directly. For Strings, immutability can be invoked by the ```freeze``` method.
48 |
49 | ```ruby
50 | album = "R U Still Down?"
51 | album.object_id # e.g. 69872900814920
52 | album.freeze # Object is now immutable
53 | album << " (Remember Me)" # RuntimeError: can't modify frozen String
54 |
55 | album = "Still I Rise"
56 | album.object_id # Changed!
57 | ```
58 |
59 | As you can see, when you call ```freeze```, you can no longer make changes to the String's text. Attempts to do so will produce a ```RuntimeError```. Make note that this is different from the second assignment of ```album```. If you set a variable to an entirely new String such as ```Still I Rise```, you're essentially pointing to an entirely different object in memory.
60 |
61 | With all that out of the way, how does this pertain to Symbols? The answer is that **Symbols are immutable by default**.
62 |
63 | ```ruby
64 | film = :"Die Hard" # Alternate way to declare a Symbol
65 | film.to_s # Return String representation (Die Hard)
66 | ```
67 |
68 | Once a Symbol is declared, the only way it can be changed is if it is completely overwritten with a new assignment. That is what distinguishes an immutable object from a mutable one.
69 |
70 | Finally, how can we leverage this knowledge with what we already know about Ruby? The answer to that is found in hashes:
71 |
72 | ```ruby
73 | castlevania_games = [
74 | {
75 | :title => "Castlevania",
76 | :platforms => ["NES"],
77 | :release_date => "September 26, 1986"
78 | },
79 | {
80 | :title => "Super Castlevania IV",
81 | :platforms => ["SNES"],
82 | :release_date => "October 31, 1991"
83 | },
84 | {
85 | :title => "Castlevania: Symphony of the Night",
86 | :platforms => ["Playstation", "Sega Saturn"],
87 | :release_date => "March 20, 1997"
88 | }
89 | ]
90 | ```
91 |
92 | It is common practice to use Symbols as keys in a hash due to their inherent immutability and memory saving properties. If you anticipate that a particular String will be reused many times over the course of a program's life, a Symbol can be quite helpful.
93 |
94 | ##Other##
95 |
96 | As a quick reference, here are the ways you can create Symbols:
97 |
98 | ```ruby
99 | state1 = :california
100 | state2 = :"Rhode Island"
101 | state3 = "Montana".to_sym
102 | state4 = "Texas".intern
103 | ```
104 |
105 | The methods ```to_sym``` and ```intern``` are helpful if you need to convert a collection of Strings into Symbols for some reason:
106 |
107 | ```ruby
108 | taxonomic_ranks = [
109 | "Life",
110 | "Domain",
111 | "Kingdom",
112 | "Phylum",
113 | "Class",
114 | "Order",
115 | "Family",
116 | "Genus",
117 | "Species"
118 | ]
119 |
120 | my_symbols = []
121 |
122 | taxonomic_ranks.each { |rank| my_symbols << rank.intern }
123 | ```
124 |
125 | One final tip: if you ever need to know what all of the Symbols in your Ruby program are, you can check the array, ```Symbol.all_symbols```. You'll find a lot of Symbols that are reused by the many parts of Ruby, but any Symbols you have also declared will be in there!
126 |
127 | #Knowledge Check#
128 |
129 | 1. Are Symbols garbage collected?
130 |
131 | #Assignment#
132 | sites.rb
133 |
134 | Without a doubt, we all spend a lot of time on the web. Your task in this module is to make a request to three different homepages: Reddit, Facebook, and Spotify. By including the ```open-uri``` module, you can easily get the data from a website through the ```open``` function. This function accepts a block and yields a File object representing the contents of the webpage. Create a hash, ```url_map```, and store the size of each page in it. The keys in this hash *must* be Symbols.
135 |
136 | ```ruby
137 | require "open-uri"
138 |
139 | urls = [
140 | "https://www.reddit.com",
141 | "https://www.facebook.com",
142 | "https://www.spotify.com"
143 | ]
144 |
145 | # Your code goes here
146 |
147 | ```
148 |
--------------------------------------------------------------------------------
/intro/07_symbols/sites.rb:
--------------------------------------------------------------------------------
1 | require "open-uri"
2 |
3 | urls = [
4 | "https://www.reddit.com",
5 | "https://www.facebook.com",
6 | "https://www.spotify.com"
7 | ]
8 |
9 | # Your code goes here
10 |
11 |
--------------------------------------------------------------------------------
/intro/08_procs_lambdas/README.md:
--------------------------------------------------------------------------------
1 | #Procs and Lambdas#
2 |
3 | In an earlier module, we covered the basics of Blocks and how they can be passed to functions. The content of this section is closely related but also particularly nuanced considering the slight differences between Blocks, *Procs* and *Lambdas*.
4 |
5 | ##Blocks##
6 |
7 | The main difference between Blocks and the others is that they are *not* objects. We have not yet covered this topic in detail, but we will in the subsequent module. Suffice it to say, Blocks are short-lived and one of the few constructs in the Ruby language that are not truly objects. It might be easier to characterize them as part of the syntax of a function.
8 |
9 | ```ruby
10 | { puts "This will not print" } # SyntaxError
11 | { puts "This is not an object" }.class # Syntax
12 | sound = { puts "Meow" } # SyntaxError
13 | [3, 4, 5].each { |x| puts x**3 } # Okay!
14 | ```
15 |
16 | As you can see, Blocks can't be executed by themselves, nor can they can be assigned to a variable and reused. Blocks are only valid when passed as an argument to a compatible function. Finally, you can pass at most *one* block as an argument to a function.
17 |
18 | ##Procs##
19 |
20 | Conceptually, Procs are reusable Blocks. They *can* be declared and used later. Functions that accept Blocks will also work with Procs: all you need to do is prefix the Proc variable with an ampersand when it's passed as an argument.
21 |
22 | ```ruby
23 | squared = Proc.new { |x| x**2 }
24 | to_string = Proc.new { |x| x.to_s }
25 |
26 | squared.class # Proc
27 |
28 | array1 = [10, 11, 12]
29 | array1.map!(&squared) # [100, 121, 144]
30 | array1.map!(&to_string) # ["100", "121", "144"]
31 |
32 | array2 = [100, 110, 120]
33 | array2.map!(&squared) # [10000, 12100, 14400]
34 | array2.map!(&to_string) # ["10000", "12100", "14400"]
35 | ```
36 |
37 | Rather than passing a Proc to a function and then using the ```yield``` keyword, you can execute a Proc directly by using its ```call``` method.
38 |
39 | ```ruby
40 | yell = Proc.new { puts "HEY!" }
41 | yell.call # HEY!
42 | ```
43 |
44 | Unlike Blocks, functions can accept multiple Procs as arguments:
45 |
46 | ```ruby
47 | def multiple_parameters(proc1, proc2)
48 | proc1.call(3) # 27
49 | proc2.call # Finished
50 | end
51 |
52 | cubed = Proc.new { |x| x**3 }
53 | finished = Proc.new { puts "Finished" }
54 |
55 | multiple_parameters(cubed, finished)
56 | ```
57 |
58 | ##Lambdas##
59 |
60 | A Lambda object is very similar to a Proc. In fact, if you inspect the ```class``` attribute, it will tell you it's a Proc type! Although that may be true, Lambdas and Procs are actually different in two important ways:
61 |
62 | ```ruby
63 | random_proc = Proc.new { |x| puts rand(x) }
64 | random_lambda = lambda { |x| puts rand(x) }
65 |
66 | random_proc.call(5) # Okay
67 | random_lambda.call(5) # Okay
68 |
69 | random_proc.call # Okay
70 | random_lambda.call # ArgumentError
71 |
72 | random_proc.call(5, 6) # Okay
73 | random_lambda.call(5, 6) # ArgumentError
74 | ```
75 |
76 | As you can see above, even though the Proc and the Lambda have the same contents, they handle the number of arguments differently. They both work as expected when one argument is passed to ```call```. However, when an incorrect number of arguments is passed to a Lambda, ```call``` will throw an ArgumentError. Compare that to what happens with a Proc. The Proc will *not* check the number of arguments passed to it. Specifically, if a Proc requires an argument but doesn't receive one, it will return ```nil```. If too many arguments are provided, it will ignore the unnecessary ones.
77 |
78 | ```ruby
79 | def early_proc
80 | early = Proc.new { return "Called Proc!" }
81 | puts early.call # Exits function
82 | puts "End of early_proc"
83 | end
84 |
85 | def full_lambda
86 | full = lambda { return "Called Lambda!" }
87 | puts full.call
88 | puts "End of full_lambda"
89 | end
90 |
91 | early_return # Nothing printed
92 | full_lambda # "Called Lambda!" and "End of full_lambda" are both printed
93 | ```
94 |
95 | The above example illustrates how Procs and Lambdas differ when ```return``` is included in their bodies. In the case of the former, the execution will leave the function early when ```call``` is invoked. By contrast, the Lambda will continue with the remaining instructions in the function.
96 |
97 | ##All Three##
98 |
99 | It's possible to write a function which accepts a block, a proc, *and* a lambda. The secret is to prefix a function parameter with ```&```:
100 |
101 | ```ruby
102 | def chant(&block) # Notice the &
103 | yield "Go!" if block_given?
104 | end
105 |
106 | # Block
107 | chant { |word| puts "Block:\t#{x}" } # Block: Go!
108 |
109 | # Proc
110 | double = Proc.new { |word| puts "Proc:\t#{x * 2}" }
111 | chant(&double) # Proc: Go!Go!
112 |
113 | # Lambda
114 | triple = lambda { |x| puts "Lambda:\t#{x * 3}" } # Lambda: Go!Go!Go!
115 | chant(&triple)
116 | ```
117 |
118 | #Knowledge Check#
119 |
120 | 1. What does the Hash method ```delete_if``` do?
121 |
122 | #Assignment#
123 | towers.rb
124 |
125 | I see quite a few famous buildings from my window and they reminded me of other famous landmarks around the world. Using the provided array, create a new one, ```sorted_array```, which rearranges the items in the original in *descending* order according to their heights. Afterwards, iterate over the newly created array using the lambda, ```conversion```, which will print out both the name of the tower as well as its height in inches, rounded to 1 decimal point.
126 |
127 | ```ruby
128 | # Height in meters
129 | towers = [
130 | { :name => "Berliner Fernsehturm", :height => 368.0 },
131 | { :name => "Canton Tower", :height => 600.0 },
132 | { :name => "Eiffel Tower", :height => 324.0 },
133 | { :name => "Tokyo Tower", :height => 332.6 },
134 | { :name => "Stratosphere Tower", :height => 350.2 }
135 | ]
136 |
137 | factor = 100/2.54 # Converts centimeters to inches
138 |
139 | conversion = lambda do |x|
140 | # Your code goes here
141 | end
142 |
143 | # Your code goes here
144 | ```
145 |
--------------------------------------------------------------------------------
/intro/08_procs_lambdas/towers.rb:
--------------------------------------------------------------------------------
1 | # Height in meters
2 | towers = [
3 | { :name => "Berliner Fernsehturm", :height => 368.0 },
4 | { :name => "Canton Tower", :height => 600.0 },
5 | { :name => "Eiffel Tower", :height => 324.0 },
6 | { :name => "Tokyo Tower", :height => 332.6 },
7 | { :name => "Stratosphere Tower", :height => 350.2 }
8 | ]
9 |
10 | factor = 100/2.54 # Converts centimeters to inches
11 |
12 | conversion = lambda do |x|
13 | # Your code goes here
14 | end
15 |
16 | # Your code goes here
17 |
--------------------------------------------------------------------------------
/intro/09_classes/README.md:
--------------------------------------------------------------------------------
1 | #Classes#
2 |
3 | Phew, we've finally made it. Most introductory programming curricula culminate with the subject of **object-oriented programming**, so it's very important we cover this as objects are *everywhere*.
4 |
5 | *Note: I highly recommend you look up the history of OOP in other sources. You might find it interesting.*
6 |
7 | Do you remember how we coded a book as hash? It's in module 4 in case you don't remember:
8 |
9 | ```ruby
10 | book = {
11 | "title" => "The Sun Also Rises",
12 | "author" => "Ernest Hemingway",
13 | "page_count" => 251
14 | }
15 | ```
16 |
17 | This piece of code is already quite similar to what an Object is. An Object is a data structure that has **fields** (also called **attributes** or **data members**), and **methods**.
18 |
19 | ```ruby
20 | class EmptyClass
21 | # No fields or methods.
22 | end
23 |
24 | class GreetingClass
25 | # Has one method, no attributes
26 | def say_hello
27 | puts "Hello!"
28 | end
29 | end
30 |
31 | class Book
32 | # Has data members, so include initialize
33 | def initialize(title, author, page_count)
34 | @title = title
35 | @author = author
36 | @page_count = page_count
37 | end
38 |
39 | def describe
40 | puts "#{@title} by #{@author} (#{@page_count} pages)"
41 | end
42 | end
43 | ```
44 |
45 | The keyword ```class``` is what makes the magic happen. The class declaration above has defined what a ```Book``` Object is. This short snippet describes a Book as having three attributes: ```title```, ```author```, and ```page_count```. Make note that the proper syntax for declaring an attribute is ```@attribute_name```: it must be preceded by the ```@``` character to be valid and it must appear in the ```initialize``` method.
46 |
47 | What is a method? Simply put, a method is just a function that is a part of a class. The purpose of a method is to interact with an Object's attributes in some way, whether that's changing them or merely passing them along to a different part of your code.
48 |
49 | What does ```initialize``` do? In this case, it is a special method which must be included for any object which has attributes. As the name suggests, ```initialize``` is the process through which an Object will initialize its state when it's first created:
50 |
51 | ```ruby
52 | book1 = Book.new("The Sun Also Rises", "Ernest Hemingway", 251)
53 | book1.describe # The Sun Also Rises by Ernest Hemingway (251 pages)
54 |
55 | book2 = Book.new # ArgumentError
56 | ```
57 |
58 | Here, ```book1``` is an **instance** of the Book class. Using the ```new``` method, you can create an instance of an Object (Note: we've seen this before with things like Hash). For it to work properly, you need to pass the correct number of arguments to the ```initialize``` method, otherwise you will get an error.
59 |
60 | Be careful that you're passing the correct data types when creating an Object. Remember, the Ruby philosophy follows Duck Typing. Based on the class definition above, there's nothing wrong with this:
61 |
62 | ```ruby
63 | book3 = Book.new(0,111,"Not a number")
64 | book3.describe # 0 by 111 (Not a number pages)
65 | ```
66 |
67 | The last thing I'd like to introduce in this module are **class variables**. While attributes are associated with an individual instance of a class and are likely to be different, class variables are owned by the class itself.
68 |
69 | ```ruby
70 | class CoinFlip
71 | @@flip_count = 0 # Class variable
72 |
73 | def initialize
74 | @side = rand(2)
75 | @@flip_count += 1
76 | end
77 |
78 | def result # Translate attribute to English
79 | return "Heads" if @side == 0
80 | "Tails"
81 | end
82 |
83 | def self.number_of_flips # Makes class variable accessible
84 | @@flip_count
85 | end
86 | end
87 |
88 | flips = []
89 | for i in 1..10
90 | flips << CoinFlip.new
91 | end
92 |
93 | puts CoinFlip.number_of_flips # 10
94 |
95 | flips.each { |flip| puts flip.result }
96 | ```
97 |
98 | What I've shown above is a popular pattern in programming to keep track of how many of a given Object have been created. The ```initialize``` method increments the class variable serving as a counter. Since the visibility of a variable is limited by default, a new method, ```self.number_of_flips```, is included so that other parts of your code can get this information. We will explore the topic of scope and visibility in the next couple of modules.
99 |
100 | Make sure you understand the difference between a class and an instance. In case you're confused, the former is like a blueprint for the Object while the other is a concrete representation of the Object.
101 |
102 | #Knowledge Check#
103 |
104 | 1. What does the method ```respond_to?``` do?
105 |
106 | #Assignment#
107 | phones.rb
108 |
109 | In real world applications, classes are used to model both abstract and concrete things. For example, the characteristics of a smartphone can be represented in a basic class. Using what you've learned, complete the Phone class below so that it has the attributes 1) brand, 2) model, 3) operating_system, and 4) release_date. I've already included an array of hashes which you will convert to Phone instances. These newly formed instances should be added to ```new_phones```.
110 |
111 | ```ruby
112 | require "date"
113 |
114 | class Phone
115 | # Your code goes here
116 | end
117 |
118 | phones = [
119 | {
120 | :brand => "Apple",
121 | :model => "iPhone 1st gen",
122 | :operating_system => "iPhone OS 1.0",
123 | :release_date => Date.new(2007, 6, 29)
124 | },
125 | {
126 | :brand => "Google",
127 | :model => "Nexus One",
128 | :operating_system => "Android 2.1 Eclair",
129 | :release_date => Date.new(2010, 1, 5)
130 | },
131 | {
132 | :brand => "Samsung",
133 | :model => "Galaxy S",
134 | :operating_system => "Android 2.3.6 Gingerbread",
135 | :release_date => Date.new(2010, 6, 4)
136 | }
137 | ]
138 |
139 | new_phones = []
140 |
141 | # Your code goes here
142 |
143 | ```
144 |
--------------------------------------------------------------------------------
/intro/09_classes/phones.rb:
--------------------------------------------------------------------------------
1 | require "date"
2 |
3 | class Phone
4 | # Your code goes here
5 | end
6 |
7 | phones = [
8 | {
9 | :brand => "Apple",
10 | :model => "iPhone 1st gen",
11 | :operating_system => "iPhone OS 1.0",
12 | :release_date => Date.new(2007, 6, 29)
13 | },
14 | {
15 | :brand => "Google",
16 | :model => "Nexus One",
17 | :operating_system => "Android 2.1 Eclair",
18 | :release_date => Date.new(2010, 1, 5)
19 | },
20 | {
21 | :brand => "Samsung",
22 | :model => "Galaxy S",
23 | :operating_system => "Android 2.3.6 Gingerbread",
24 | :release_date => Date.new(2010, 6, 4)
25 | }
26 | ]
27 |
28 | new_phones = []
29 |
30 | # Your code goes here
31 |
--------------------------------------------------------------------------------
/intro/10_scope/README.md:
--------------------------------------------------------------------------------
1 | #Scope#
2 |
3 | Although we haven't discussed visibility at length in earlier modules, we've already been dealing with it in one form or another.
4 |
5 | ##Local##
6 |
7 | ```ruby
8 | [1, 2, 3].each { |x| puts x }
9 |
10 | puts x # NameError
11 | ```
12 |
13 | Variables declared inside a block only exist within that block. Attempting to access them outside of that results in a NameError.
14 |
15 | ```ruby
16 | require "open-uri"
17 | require "json"
18 |
19 | def load_reddit
20 | json_object = JSON.parse(open("https://www.reddit.com/.json").read)
21 | end
22 |
23 | json_object # NameError
24 | ```
25 |
26 | The same is true for variables declared within a function. Only what is returned by the function is available to callers.
27 |
28 | **Local** scope is the most limited visibility in an application and represents the shortest lifetime of a variable. When execution reaches the end of a block, the locally declared variable is no longer accessible and will be destroyed.
29 |
30 | ##Instance##
31 |
32 | As discussed in the previous module, **instance**-level scope pertains the visibility of data members to an individual Object. Although other instances of the same class can be created, they won't be able to access each other's values. Remember, instance variables are referenced with an ```@``` before the field name.
33 |
34 | ##Class##
35 |
36 | **Class**-level visibility is usually saved for variables that are convenient to share between instances of class. Whether that's some kind of counter, a constant value, or something else, class variables can be useful in such cases. Class variables begin with ```@@```.
37 |
38 | ```ruby
39 | class Simulator
40 | @@GRAV_ACCEL_CONST = 9.83
41 |
42 | # Other methods
43 | end
44 | ```
45 |
46 | ##Global##
47 |
48 | As their name implies, **global** scoped variables are visibile *anywhere* in your program. To declare a global, use the ```$``` character as a variable prefix.
49 |
50 | While you might be tempted to include them often in your code, please resist! It is considered best practices to use global variables *sparingly*. "For what reasons?" you ask. That in itself can be a much longer discussion, but the gist of it is that since global variables are accessible anywhere in a program, it becomes a lot more difficult to debug a complex application. If you're really interested in making them a part of your skillset, you might want to consider using them mostly for constants rather than variables that might be changed in the future.
51 |
52 | ```ruby
53 | class ErrorMessage
54 | $ERROR1 = "You did something bad!"
55 | $ERROR2 = "You did something REALLY bad!"
56 | $ERROR3 = "Oh no..!"
57 | end
58 |
59 | puts "#{$ERROR1} #{$ERROR2} #{$ERROR3}" # Accessible anywhere!
60 | ```
61 |
62 | #Knowledge Check#
63 |
64 | 1. What is the scope of variables declared in for-loops?
65 |
66 | #Assignment#
67 | basic_game.rb
68 |
69 | If you play any sort of multiplayer games, you'll understand that there's usually a process for adding additional players. For arcade games, you'd insert your tokens and press Start. For online games, you have to enter a matchmaking portal to find your opponents.
70 |
71 | In this assignment, you will need to define two classes: **Player** and **Game**. A Player can have many different attributes, but at a minimum, define a **name**. The Game class should have two fields: **name** and **players**. The players attribute should be an array which will store Player instances. The Game class should also have a method, **add_player**, which will you will need to complete. Remember, defensive programming is a virtue! Arguments passed to a method should be validated and checked for correctness before updating an instance. To that end, incorporate a class variable, **MAX_PLAYER_COUNT**, that is used in your method.
72 |
73 | ```ruby
74 | class Player
75 | # Your code goes here
76 | end
77 |
78 | class Game
79 | # Your code goes here
80 | end
81 |
82 | game = Game.new("RPS")
83 |
84 | players = [
85 | Player.new("John"),
86 | Player.new("Lizzy"),
87 | Player.new("Clair"),
88 | Player.new("Brad"),
89 | 15
90 | ]
91 |
92 | players.each { |player| game.add_player(player) }
93 | ```
94 |
--------------------------------------------------------------------------------
/intro/10_scope/basic_game.rb:
--------------------------------------------------------------------------------
1 | class Player
2 | # Your code goes here
3 | end
4 |
5 | class Game
6 | # Your code goes here
7 | end
8 |
9 | game = Game.new("RPS")
10 |
11 | players = [
12 | Player.new("John"),
13 | Player.new("Lizzy"),
14 | Player.new("Clair"),
15 | Player.new("Brad"),
16 | 15
17 | ]
18 |
19 | players.each { |player| game.add_player(player) }
20 |
--------------------------------------------------------------------------------
/intro/11_encapsulation/README.md:
--------------------------------------------------------------------------------
1 | #Encapsulation#
2 |
3 | **Information hiding** is an important principle in software development, though it might be a little difficult to understand as a beginner. All of the examples and programs we've worked on so far have been very basic, and you as the developer can see how all of the pieces work. After all, you've been writing the code yourself.
4 |
5 | However, in the real world, the projects you work on will likely include code that another person crafted. You do not necessarily need to know how a certain class or function works internally: in most circumstances, you only care that it does what it says. For example, you don't know how an array's ```sort``` is implemented, but you do care that works correctly. Likewise, when you make a request to a website using ```open```, as a caller, you don't need to concern yourself with the specifics.
6 |
7 | When you write classes, you should take the concept of **encapsulation** to heart. What attributes in a class should be accessible to the outside world? By contrast, what data members should remain hidden? The more sophisticated you become in this mode of thinking, the better of an object-oriented programming you will be.
8 |
9 | ```ruby
10 | class Television
11 | def initialize(brand, width, resolution, hertz)
12 | @brand = brand
13 | @width = width
14 | @resolution = resolution
15 | @hertz = hertz
16 | end
17 | end
18 |
19 | tv1 = Television.new("Samsung", 32, 1080, 60)
20 | tv1.brand # NoMethodError
21 | ```
22 |
23 | As you can see above, a Television object is declared but none of its internals are available to the outside world. In this way, we can say all of its data members are **private**. As a matter of fact, you can use the ```private``` keyword on methods as well to change their default visibility which happens to be **public**.
24 |
25 | ```ruby
26 | class Television
27 | def initialize(brand, width, resolution, hertz)
28 | @brand = brand
29 | @width = width
30 | set_resolution(resolution) # Validate argument
31 | @hertz = hertz
32 | end
33 |
34 | public # Note: methods are public by default
35 | def describe
36 | puts "#{@brand} #{@width}-inch #{@resolution}p #{@hertz}Hz"
37 | end
38 |
39 | private # This method can only be called within the class
40 | def set_resolution(resolution)
41 | raise ArgumentError if not resolution > 0
42 | @resolution = resolution
43 | end
44 | end
45 |
46 | tv1 = Television.new("Samsung", 32, 1080, 60)
47 | tv1.describe # Samsung 32-inch 1080p 60Hz
48 | tv1.set_resolution(4000) # NoMethodError
49 | ```
50 |
51 | Including the ```public``` keyword on methods is not necessary but it can make your code easier to read. With that said, the method, ```set_resolution```, is a simple example of when you might want to make something private. If you only intend to set the data members of an instance once, there shouldn't be a reason that a method like ```set_resolution``` should be public. Typically, internal methods that do things like validation should remain hidden.
52 |
53 | So when is it acceptable for the internal attributes of an instance to be accessible to the outside world? It certainly would be helpful if we could type something like ```tv1.brand``` and get its value directly! With that in mind, the most fundamental choice you have as a developer when limiting a class has to do with permissions: should you grant outside callers **read** access, **write** access, or **both**?
54 |
55 | You've probably come across this in other kinds of applications. If you've ever worked on a shared document, you can control who has view privileges (i.e. read-only) and who can edit it (i.e. read+write). The same basic idea underlies classes and the design of **interfaces**.
56 |
57 | ```ruby
58 | class Notebook
59 | attr_reader :length
60 | attr_writer :width
61 | attr_accessor :title
62 |
63 | def initialize(length, width, title)
64 | @length = length
65 | @width = width
66 | @title = title
67 | end
68 | end
69 |
70 | notebook = Notebook.new(11.0, 9.8, "Chinese 101")
71 | notebook.length # Okay (read access)
72 |
73 | notebook.width # NoMethodError
74 | notebook.width = 10 # Okay (write access)
75 |
76 | notebook.title # Okay (read access)
77 | notebook.title = "Calculus" # Okay (write access)
78 | ```
79 |
80 | When you put the methods ```attr_reader```, ```attr_writer```, or ```attr_accessor``` in front of a symbol that corresponds to a class field, the visibility on that data member will be modified. The first, ```attr_reader```, makes an attribute readable but *not* writable. The second does the exact opposite: an attribute can be modified but not read. Finally, ```attr_accessor``` is the most permissive, allowing a field to be freely read and changed.
81 |
82 | For your own reference, Ruby automagically translates the ```attr_****``` business into the following equilavent pieces of code:
83 |
84 | ```ruby
85 | # attr_reader :length
86 | def length
87 | @length
88 | end
89 |
90 | # attr_writer :width
91 | def width=(value)
92 | @width = value
93 | end
94 |
95 | # attr_accessor :title
96 | def title
97 | @title
98 | end
99 |
100 | def title=(value)
101 | @title = value
102 | end
103 | ```
104 |
105 | As an aside, in other languages, these kinds of methods are usually called **getters** and **setters**.
106 |
107 | #Knowledge Check#
108 |
109 | 1. What are the benefits of using a setter method over directly making changes to an instance variable?
110 |
111 | #Assignment#
112 | netflix.rb
113 |
114 | If you're like most people, you probably watch a lot of Netflix every week. In this assignment, complete the NetflixAccount class implementation. Make sure that some validation is performed before adding items to either ```my_list``` or ```recently_watched```. Also, specify that each attribute is readable.
115 |
116 | ```ruby
117 | class Movie
118 | attr_reader :title
119 |
120 | def initialize(title)
121 | @title = title
122 | end
123 | end
124 |
125 | class NetflixAccount
126 | # Your code goes here
127 |
128 | def initialize(username)
129 | @username = username
130 | @my_list = []
131 | @recently_watched = []
132 | end
133 |
134 | def add_to_my_list(movie)
135 | # Your code goes here
136 | end
137 |
138 | def remove_from_my_list(movie)
139 | # Your code goes here
140 | end
141 |
142 | def watch(movie)
143 | # Your code goes here
144 | end
145 | end
146 |
147 | movies = [
148 | Movie.new("Seven Samurai"),
149 | Movie.new("Wall Street"),
150 | Movie.new("Big Hero 6")
151 | ]
152 |
153 | account = NetflixAccount.new("user123")
154 | account.watch(movies[0])
155 | account.add_to_my_list(movies[1])
156 | account.add_to_my_list(movies[2])
157 | account.remove_from_my_list(movies[2])
158 | ```
159 |
--------------------------------------------------------------------------------
/intro/11_encapsulation/netflix.rb:
--------------------------------------------------------------------------------
1 | class Movie
2 | attr_reader :title
3 |
4 | def initialize(title)
5 | @title = title
6 | end
7 | end
8 |
9 | class NetflixAccount
10 | # Your code goes here
11 |
12 | def initialize(username)
13 | @username = username
14 | @my_list = []
15 | @recently_watched = []
16 | end
17 |
18 | def add_to_my_list(movie)
19 | # Your code goes here
20 | end
21 |
22 | def remove_from_my_list(movie)
23 | # Your code goes here
24 | end
25 |
26 | def watch(movie)
27 | # Your code goes here
28 | end
29 | end
30 |
31 | movies = [
32 | Movie.new("Seven Samurai"),
33 | Movie.new("Wall Street"),
34 | Movie.new("Big Hero 6")
35 | ]
36 |
37 | account = NetflixAccount.new("user123")
38 | account.watch(movies[0])
39 | account.add_to_my_list(movies[1])
40 | account.add_to_my_list(movies[2])
41 | account.remove_from_my_list(movies[2])
42 |
--------------------------------------------------------------------------------
/intro/12_inheritance/README.md:
--------------------------------------------------------------------------------
1 | #Inheritance#
2 |
3 | The past couple of modules should have given you some practice on how to describe abstract and concrete things as Objects. Classes represent things as having attributes and methods. Wouldn't it be convenient if we could reuse these classes for related Objects without having to rewrite essentially the same code? That's where **inheritance** comes in.
4 |
5 | ##Basic##
6 |
7 | In programming, inheritance is a process that allows one class to take on the attributes and methods of a different class.
8 |
9 | ```ruby
10 | class Sword
11 | def slash
12 | puts "Graaah!"
13 | end
14 | end
15 |
16 | class Katana < Sword
17 | end
18 |
19 | sword = Sword.new
20 | katana = Katana.new
21 |
22 | sword.slash # Graaah!
23 | katana.slash # Graaah!
24 | ```
25 |
26 | In this example, the Katana class inherits from the Sword class. The reason for this is that inheritance is meant to model an **is-a** relationship. Since a katana is a kind of sword, this makes sense. By contrast, a Katana class probably shouldn't inherit from an Axe class even though they're both sharp weapons. The relationship between a **parent** class and a **child** class should be logically close.
27 |
28 | ##Override and Extend##
29 |
30 | The problem with the previous example is that it doesn't do anything interesting. The code above says that Swords and Katanas are essentially the same thing. Thankfully, inheritance gives us the freedom to customize or **extend** the original class' attributes and methods.
31 |
32 | ```ruby
33 | class Sword
34 | def slash
35 | puts "Graaah!"
36 | end
37 | end
38 |
39 | class Katana < Sword
40 | # New field
41 | attr_reader :forger
42 |
43 | def initialize(forger)
44 | @forger = forger
45 | end
46 |
47 | def slash
48 | # Overrides parent class
49 | puts "Vanish!"
50 | end
51 |
52 | def parry
53 | # New method
54 | puts "Deflected"
55 | end
56 | end
57 | ```
58 |
59 | In a child class, you are allowed to add new attributes and methods, as shown in the updated Katana class. More interestingly, you can **override** methods so that a child class can do something different while preserving the rest of its characteristics. In real world applications, this can be a tremendous timesaver.
60 |
61 | ##Super##
62 |
63 | When you override a method, you don't have to toss out everything. By using the keyword ```super```, you can still call the super class (a.k.a. parent class) method in addition to any new functionality.
64 |
65 | ```ruby
66 | def slash
67 | # Overrides parent class
68 | super # Graaah!
69 | puts "Vanish!"
70 | end
71 | ```
72 |
73 | Note that you don't *have* to put ```super``` at the beginning of the method. As a matter of fact, the ordering of your call to the super class method could influence the behavior of your child class in a material way.
74 |
75 | ##Multiple Inheritance##
76 |
77 | You might have wondered, "Can you inherit from more than one class?" The answer in Ruby is **no**. Although **multiple inheritance** is possible in older languages like C++, many newer languages have chosen to forbid it. As class hierarchies get increasingly complex, multiple inheritance can introduce problems into class structure that are difficult to fix. I highly recommend you look into these issues yourself and come to your own conclusions.
78 |
79 | ```ruby
80 | class MotorVehicle
81 | end
82 |
83 | class Bicycle
84 | end
85 |
86 | # Inherit from first parent class
87 | class Motorcycle < MotorVehicle
88 | end
89 |
90 | # Attempt to inherit from second class
91 | class Motorcycle < Bicycle
92 | end # mismatch for class Motorcycle (TypeError)
93 | ```
94 |
95 | As you can see, explicit attempts at multiple inheritance will throw a TypeError.
96 |
97 | #Knowledge Check#
98 |
99 | 1. What is an abstract class?
100 | 2. What is an interface?
101 |
102 | #Assignment#
103 | robots.rb
104 |
105 | The singularity is upon us. Take the provided class skeletons and be creative! Incorporate the techniques you've learned in this module and describe these robots using your understanding of object-oriented programming.
106 |
107 | ```ruby
108 | class Robot
109 | # Your code goes here
110 | end
111 |
112 | class Ultron < Robot
113 | # Your code goes here
114 | end
115 |
116 | class MegaMan < Robot
117 | # Your code goes here
118 | end
119 |
120 | class Glados < Robot
121 | # Your code goes here
122 | end
123 | ```
124 |
--------------------------------------------------------------------------------
/intro/12_inheritance/robots.rb:
--------------------------------------------------------------------------------
1 | class Robot
2 | # Your code goes here
3 | end
4 |
5 | class Ultron < Robot
6 | # Your code goes here
7 | end
8 |
9 | class MegaMan < Robot
10 | # Your code goes here
11 | end
12 |
13 | class Glados < Robot
14 | # Your code goes here
15 | end
16 |
--------------------------------------------------------------------------------
/intro/13_modules/README.md:
--------------------------------------------------------------------------------
1 | #Modules#
2 |
3 | Sometimes referred to as **namespaces**, modules are a way to organize related pieces of code together. Constants, functions, and even classes can be contained in a module.
4 |
5 | ##Namespacing##
6 |
7 | "Why bother?"
8 |
9 | Imagine you are working on a bigger codebase and you decide to incorporate a helpful package written by a third party. In the absence of modules, it becomes increasingly likely you will encounter a name collision. For instance, if you have classes and functions with common names such as "Document" or "read," sooner or later you're bound to clash with other pieces of code. Modules are a solution to this kind of problem.
10 |
11 | ```ruby
12 | module Word
13 | class Document
14 | end
15 | end
16 |
17 | module Pdf
18 | class Document
19 | end
20 | end
21 |
22 | document1 = Word::Document.new
23 | document2 = Pdf::Document.new
24 | ```
25 |
26 | The **scope resolution operator**, ```::```, is used to reference something contained in a module. Constants are a good candidate for storage in a namespace:
27 |
28 | ```ruby
29 | module Cardinal
30 | NORTH = 0
31 | SOUTH = 1
32 | EAST = 2
33 | WEST = 3
34 | end
35 |
36 | class Compass
37 | attr_accessor :direction
38 |
39 | def initialize
40 | @direction = Cardinal::NORTH
41 | end
42 | end
43 |
44 | compass = Compass.new
45 | compass.direction = Cardinal::EAST
46 | ```
47 |
48 | Likewise functions and even nested modules can be enclosed by a namespace:
49 |
50 | ```ruby
51 | module Physics
52 | module Newtonian
53 | # Include module name before method name
54 | def Newtonian.calculate_force(mass, acceleration)
55 | mass * acceleration
56 | end
57 | end
58 |
59 | module Einsteinian
60 | SPEED_OF_LIGHT = 299792458
61 | end
62 | end
63 |
64 | force = Physics::Newtonian::calculate_force(10, 2)
65 | speed = Physics::Einsteinian::SPEED_OF_LIGHT
66 | ```
67 |
68 | As you can see, by deeply nesting modules, it becomes more of burden to type. That ultimately is the tradeff between safety and convenience. However, if you're the type that favors convenience, Ruby allows you to do the following:
69 |
70 | ```ruby
71 | Isaac = Physics::Newtonian
72 | puts Isaac::calculate_force(20, 2)
73 | ```
74 |
75 | In this way, ```Isaac``` is an **alias** for the nested module.
76 |
77 | Hopefully these snippets have highlighted the benefits of namespacing in your programs.
78 |
79 | ##Mixins##
80 |
81 | Although I emphasized that multiple inheritance is disallowed in Ruby, the truth is that you can achieve a similar effect by using classes and modules together. The secret lies in the keyword ```include``` and **mixins**.
82 |
83 | ```ruby
84 | module RunnableMixin
85 | attr_reader :is_running
86 |
87 | def run
88 | unless @is_running
89 | @is_running = true
90 | puts "#{self.class}: Started running"
91 | end
92 | end
93 |
94 | def stop
95 | if @is_running
96 | @is_running = false
97 | puts "#{self.class}: Stopped running"
98 | end
99 | end
100 | end
101 |
102 | module FlyableMixin
103 | attr_reader :is_flying
104 |
105 | def fly
106 | # Only fly if not already flying
107 | unless @is_flying
108 | @is_flying = true
109 | puts "#{self.class}: Took flight"
110 | end
111 | end
112 |
113 | def land
114 | # Only land if currently flying
115 | if @is_flying
116 | @is_flying = false
117 | puts "#{self.class}: Landed"
118 | end
119 | end
120 | end
121 |
122 | class Rocket
123 | include FlyableMixin
124 | end
125 |
126 | class Bird
127 | include FlyableMixin
128 | include RunnableMixin
129 | end
130 |
131 | rocket = Rocket.new
132 | bird = Bird.new
133 |
134 | bird.fly # Bird: Took flight
135 | puts bird.is_flying # true
136 | bird.land # Bird: Landed
137 | bird.run # Bird: Started running
138 | bird.stop # Bird: Stopped running
139 | ```
140 |
141 | Mixins are used to add attributes and methods to classes. In real life, a rocket and a bird are very dissimilar, but when modeled in software, they very well could be alike in some ways. In the example above, you can make a Rocket and a Bird both "flyable" but only a Bird "runnable." Even though ```is_running``` and ```is_flying``` aren't declared as instance variables in either class, the mixin updates the classes to make them compatible.
142 |
143 | Can you imagine a scenario where this might be useful? As a matter of fact, in game development, combining characteristics in this way can be quite common. Think about which game objects should be able to run, jump, fly, or be destructable and think how different those game entities could be.
144 |
145 | A less common feature of mixins is achieved through the ```extend``` keyword.
146 |
147 | ```ruby
148 | module Mixin
149 | def info
150 | puts "This is a class-level function"
151 | end
152 | end
153 |
154 | class Building
155 | extend Mixin
156 | end
157 |
158 | Building.info # This is a class-level function
159 | ```
160 |
161 | When combined with a class, ```extend``` will incorporate a mixin module's functions at the class level rather than the instance level.
162 |
163 | ##Require##
164 |
165 | You might recall from earlier content that these kind of statements were included in some of the examples and assignments:
166 |
167 | ```ruby
168 | require "date"
169 | require "open-uri"
170 | ```
171 |
172 | When you ```require``` a file, you are loading its contents into your application. Ruby is conveniently bundled with a lot of helpful packages like these, making your life as a programmer easier. It's a good practice to keep logical parts of your code in seperate files. Trust me, you don't want to have to wade through excessively long source files if it can be avoided.
173 |
174 | The ```require``` method attempts to load a Ruby file that exists on your environment **path**. Since ```date``` is automatically in the correct location, it should work right out of the box. However, if you have files in the same directory, for example, you might want to use ```require_relative``` instead:
175 |
176 | ```ruby
177 | # file1.rb
178 | require "date"
179 |
180 | module MyDate
181 | def MyDate.today
182 | Date.today
183 | end
184 | end
185 | ```
186 |
187 | ```ruby
188 | # file2.rb
189 | require_relative "file1"
190 |
191 | today = MyDate::today
192 | ```
193 |
194 | As you can see, using ```require_relative``` can be easier when importing files that are close to one another in a project.
195 |
196 | ##Final Thoughts##
197 |
198 | After taking all this in, you might be wondering to yourself, "Modules seem pretty similar to classes. How are they *different*?" Good question. Well, the main difference is that you **cannot** instantiate a module. That feature is still reserved for classes. If you intend to use modules in an object-oriented manner, at the end of the day, you will still need an instance of a class, otherwise you will just have a collection of constants and functions.
199 |
200 | #Knowledge Check#
201 |
202 | 1. Can you declare private methods in a Module?
203 |
204 | #Assignment#
205 | 1. mixins.rb
206 | 2. fighter.rb
207 |
208 | "Be water, my friend."
209 |
210 | A legendary entertainer and arguably the greatest martial artist of the 20th century, Bruce Lee is a hero to many. Rather than be constrained by dogma, his *Jeet Kune Do* took elements from a number of fighting styles. In this assignment, I'd like you to add fighting techniques to WingChunMixin, BoxingMixin, and FencingMixin. Using your knowledge from this unit, synthesize these styles into the JeetKuneDoMixin. Finally, combine this mixin with the Fighter class so that Bruce Lee may live in code.
211 |
--------------------------------------------------------------------------------
/intro/13_modules/fighter.rb:
--------------------------------------------------------------------------------
1 | require "logger"
2 | require_relative "mixins"
3 |
4 | module Logging
5 | # Provided for your convenience
6 | def logger
7 | Logging.logger
8 | end
9 |
10 | def self.logger
11 | @logger ||= Logger.new(STDOUT)
12 | end
13 | end
14 |
15 | class Fighter
16 | # Your code goes here
17 | end
18 |
19 | fighter = Fighter.new("Bruce Lee")
20 | fighter.uppercut
21 | fighter.block
22 | fighter.riposte
23 | fighter.straight_kick
24 |
--------------------------------------------------------------------------------
/intro/13_modules/mixins.rb:
--------------------------------------------------------------------------------
1 | module WingChunMixin
2 | # Your code goes here
3 | end
4 |
5 | module BoxingMixin
6 | # Your code goes here
7 | end
8 |
9 | module FencingMixin
10 | # Your code goes here
11 | end
12 |
13 | module JeetKuneDoMixin
14 | # Your code goes here
15 | end
16 |
--------------------------------------------------------------------------------
/intro/14_objects/README.md:
--------------------------------------------------------------------------------
1 | #Objects#
2 |
3 | By now, you should have a pretty good understanding of classes and why they can be helpful for programmers. What we haven't discussed yet is Ruby's interpretation of this paradigm and what makes it different from many other languages.
4 |
5 | In Ruby, **everything is an Object** (with only a few exceptions).
6 |
7 | ##Primitives##
8 |
9 | In languages like C++ and Java, things like Integers, Floats, individual Characters, and Booleans are all considered **primitive data types**, the fundamental building blocks from which other **composite types** can be constructed. Primitives are *not* full-fledged Objects and cannot be extended through inheritance. By contrast, in Ruby these fundamental data types are in fact objects.
10 |
11 | ```ruby
12 | 10.class # Fixnum
13 | Fixnum.superclass # Integer
14 | Integer.superclass # Numeric
15 | Numeric.superclass # Object
16 | Object.superclass # BasicObject
17 |
18 | 11.1.class # Float
19 | Float.superclass # Numeric
20 |
21 | "some string".class # String
22 | String.superclass # Object
23 |
24 | true.class # TrueClass
25 | TrueClass.superclass # Object
26 |
27 | false.class # FalseClass
28 | FalseClass.superclass # Object
29 | ```
30 |
31 | Calling the ```superclass``` method on a class name will tell you the parent class in the inheritance tree. Attempts to call ```superclass``` on BasicObject will return ```nil```, therefore it is the root of all Objects in Ruby.
32 |
33 | ##Nil##
34 |
35 | In many other languages, **null** is a special type which represents the complete absence of data. To programmers with a background in those languages, the Ruby equivalent, **nil**, might be surprising:
36 |
37 | ```ruby
38 | nil.class # NilClass
39 | NilClass.superclass # Object
40 | ```
41 |
42 | That's right, ```nil``` itself is an Object in Ruby.
43 |
44 | ##How about...##
45 |
46 | In case you were wondering...
47 |
48 | ```ruby
49 | Fixnum.class # Class
50 | Class.class # Class
51 | Class.superclass # Module (interesting!)
52 | Module.superclass # Object
53 | ```
54 |
55 | Calling the method, ```class```, on class names returns... Class. What is the parent of Class? Believe it or not, it is Module.
56 |
57 | ##What are *not* Objects?##
58 |
59 | It's pretty difficult to distill in a single sentence, but I'll try.
60 |
61 | "Can you put it on the right-hand side of an assignment operator (```=```) by itself?" If the answer is no, it's not an Object. That means things like operators, looping constructs, methods, and *Blocks* are all exceptions to the rule.
62 |
63 | The last one may or may not surprise you.
64 |
65 | ```ruby
66 | not_a_block = { puts "This doesn't work" } # Syntax error
67 |
68 | some_lambda = lambda { puts "Am I an object?" }
69 | some_lambda.class # Proc
70 | Proc.superclass # Object
71 | ```
72 |
73 | Remember, Procs and Lambdas are in fact Objects.
74 |
75 | #Knowledge Check#
76 |
77 | 1. What are the advantages of using ```responds_to?``` over ```is_a?```?
78 |
79 | #Assignment#
80 | 1. rpg.rb
81 | 2. heroes.csv
82 | 3. enemies.csv
83 |
84 | Role-playing games have a long history. My first experiences with RPGs were on the Super Nintendo in 1991, a number of years removed from their tabletop roots.
85 |
86 | Your goal in this assignment is to implement the classic turn-based battle system found in many Japanese RPGs. The Character and Party classes are provided for your own convenience. The helper method, ```convert_to_character```, also needs your attention. If you want to add additional fields or methods to the Battle class to assist in the ```run``` logic, you're welcome to do so.
87 |
--------------------------------------------------------------------------------
/intro/14_objects/enemies.csv:
--------------------------------------------------------------------------------
1 | Goblin,60,5
2 | Wyrm,150,10
3 |
--------------------------------------------------------------------------------
/intro/14_objects/heroes.csv:
--------------------------------------------------------------------------------
1 | Rock,200,5
2 | Leon,170,7
3 | Rosa,150,10
4 |
--------------------------------------------------------------------------------
/intro/14_objects/rpg.rb:
--------------------------------------------------------------------------------
1 | require "csv"
2 |
3 | module RPG
4 | class Character
5 | attr_reader :name
6 | attr_reader :hit_points
7 | attr_reader :attack_power
8 |
9 | def initialize(name, hit_points, attack_power)
10 | @name = name
11 | @hit_points = hit_points
12 | @attack_power = attack_power
13 | end
14 |
15 | def attack
16 | @attack_power * rand
17 | end
18 |
19 | def take_damage(damage)
20 | if @hit_points - damage > 0
21 | @hit_points -= damage
22 | else
23 | @hit_points = 0
24 | end
25 | end
26 |
27 | def is_dead
28 | @hit_points == 0 ? true : false
29 | end
30 |
31 | def to_s
32 | "#{@name}: #{@hit_points}HP,#{@attack_power}ATK"
33 | end
34 | end
35 |
36 | class Party
37 | attr_reader :characters
38 |
39 | def initialize
40 | @characters = []
41 | end
42 |
43 | def add(character)
44 | raise TypeError unless character.is_a?(Character)
45 | @characters << character
46 | end
47 |
48 | def is_defeated
49 | @characters.all? { |character| character.is_dead }
50 | end
51 |
52 | def to_s
53 | @characters.to_s
54 | end
55 | end
56 |
57 | class Battle
58 | def initialize(hero_party, enemy_party)
59 | @hero_party = hero_party
60 | @enemy_party = enemy_party
61 | end
62 |
63 | def run
64 | # Your code goes here
65 | end
66 | end
67 | end
68 |
69 | def convert_to_character(array)
70 | # Helper method
71 | # Your code goes here
72 | end
73 |
74 | hero_party = RPG::Party.new
75 | enemy_party = RPG::Party.new
76 |
77 | hero_file = CSV.read("heroes.csv")
78 | hero_file.each do |line|
79 | hero = convert_to_character line
80 | hero_party.add(hero)
81 | end
82 |
83 | enemy_file = CSV.read("enemies.csv")
84 | enemy_file.each do |line|
85 | enemy = convert_to_character line
86 | enemy_party.add(enemy)
87 | end
88 |
89 | battle = RPG::Battle.new(hero_party, enemy_party)
90 | battle.run
91 |
--------------------------------------------------------------------------------
/intro/15_exceptions/README.md:
--------------------------------------------------------------------------------
1 | #Exceptions#
2 |
3 | Sometimes, software can go awry. As we all know, when applications go wrong, they can go very, *very* wrong. This is usually a result of a program reaching an unanticipated or erroneous state. In order to manage these dangerous parts of programming, it is common for programmers to make use of **Exceptions**.
4 |
5 | ##Raise##
6 |
7 | An Exception is a special kind of Object that, when **raised** (i.e. thrown), terminates an application immediately. However, Exceptions are typically not used by themselves. As a matter of fact, your application can terminate gracefully or even recover with the help of an **exception handler**, a block of code which executes when a certain kind of Exception is raised.
8 |
9 | ```ruby
10 | puts "All systems go..."
11 | raise "This is an error message" # RuntimeError
12 | puts "What happened?" # Never executed
13 | ```
14 |
15 | The ```raise``` keyword will trigger an Exception. In this case, an untyped raise will produce a RuntimeError by default and terminate the script before it can execute the last statement. If you want to specify the type of Exception, you need to add the class name directly after ```raise```:
16 |
17 | ```ruby
18 | raise TypeError, "Argument is not of the desired type (Character)"
19 | ```
20 |
21 | ##Custom Exceptions##
22 |
23 | When you write your own applications, you might decide that you want to define your own Exception classes to highlight errors that are unique to your own project. In such circumstances, it's advised that you inherit from the StandardError class:
24 |
25 | ```ruby
26 | class MyException < StandardError
27 | end
28 | ```
29 | There are a couple dozen built-in Exception descedant types in Ruby. If you'd like to know what they are, I recommend reviewing the class hierarchy in the official Ruby documentation.
30 |
31 | ##Exception Handlers##
32 |
33 | The structure for handling an Exception is a block of code wrapped in ```begin```, ```rescue```, and ```end```. To illustrate this, let's update the first example from this module:
34 |
35 | ```ruby
36 | begin
37 | puts "All systems go..."
38 | raise "This is an error message"
39 | rescue Exception => e
40 | puts e.message # If you want more information,
41 | puts e.backtrace.inspect # You can see the error details
42 | puts "Fix the problem here"
43 | end
44 |
45 | puts "What happened?" # Okay
46 | ```
47 |
48 | The statements which follow ```begin``` are what the program should do under normal circumstances. However, the statements following ```rescue``` are instructions that should be executed following a certain kind of error. Whether it's terminating a program with an error message or performing some kind of recovery, that's up to you.
49 |
50 | One thing to note about the previous snippet is that when used alone, ```rescue``` will match with all kinds of Exceptions that are raised. Although it's tempting to do so, it is considered bad practice to catch errors this way. By using such code, when an Exception occurs your application will tell you no useful informtion when something goes wrong. All you will see is one exit point from the block of code. A much better approach is to handle individual Exception types:
51 |
52 | ```ruby
53 | begin
54 | # Your expected code
55 | rescue MyException
56 | # What happens when MyException is raised
57 | rescue TypeError
58 | # How to handle a TypeError
59 | rescue ArgumentError
60 | # Something different for ArgumentError
61 | else
62 | # For all other Exceptions (not ideal, but possible)
63 | end
64 | ```
65 |
66 | One final feature to consider in an exception handler is the ```ensure``` keyword. Instructions that follow ```ensure``` are executed whether an Exception is raised or not! A traditional use-case for this kind of feature is to clean up application resources when something goes wrong. For example, if an error occurs while you're in the middle of writing to a file or a database, you'd want to close anything that might be still open.
67 |
68 | ```ruby
69 | begin
70 | # Usually executes, no problem
71 | rescue
72 | # Sometimes or never
73 | ensure
74 | # Always executes
75 | end
76 | ```
77 |
78 | *Side note: in other languages, these keywords are usually named the following:*
79 |
80 | Ruby | Other
81 | -------|------
82 | raise | throw
83 | begin | try
84 | rescue | catch
85 | ensure | finally
86 |
87 | ##Common Errors##
88 |
89 | ###User Input###
90 |
91 | Whenever you prompt a user for input, they have the freedom to type to just about anything. If you write your code naively like this:
92 |
93 | ```ruby
94 | puts "Please enter a number between 0 and 9:"
95 | number = gets.chomp.to_i
96 | ```
97 |
98 | you're opening up your program to a logical error. When ```to_i``` is called on a String that can't be converted into an Integer, it returns 0 by default *which would appear to be correct (i.e. a number between 0 and 9)*. Likewise, the user is free to enter numbers outside of the mandated range, opening up the application to a different error.
99 |
100 | A much better solution would be something like:
101 |
102 | ```ruby
103 | is_valid = false
104 |
105 | until is_valid
106 | begin
107 | puts "Please enter a number between 0 and 9:"
108 | number = Integer(gets.chomp) # Can raise an ArgumentError
109 | raise ArgumentError unless number.between?(0, 9)
110 | is_valid = true
111 | rescue ArgumentError
112 | puts "Invalid input. Please try again:"
113 | end
114 | end
115 | ```
116 |
117 | ###Opening a File###
118 |
119 | Sometimes you're going to need to read from a file. What's wrong with this code?
120 |
121 | ```ruby
122 | some_file = File.open("some_file", "r")
123 | ```
124 |
125 | There's no guarantee that some_file actually exists at that location! Try one of these instead:
126 |
127 | ```ruby
128 | some_file = File.open("some_file", "r") unless File.exists?("some_file")
129 | ```
130 |
131 | ```ruby
132 | some_file = File.open("some_file", "r") rescue nil
133 | ```
134 |
135 | ```ruby
136 | filename = "some_file"
137 |
138 | begin
139 | some_file = File.open(filename, "r")
140 | rescue Errno::ENOENT
141 | puts "File '#{filename}' does not exist"
142 | end
143 | ```
144 |
145 | ###Divide by Zero###
146 |
147 | This is so common, it's one of Ruby's built-in Exceptions.
148 |
149 | ```ruby
150 | budget = 100
151 | number_of_children = 0
152 | allowance_per_child = budget / number_of_children # ZeroDivisionError
153 | ```
154 |
155 | A solution:
156 |
157 | ```ruby
158 | budget = 100
159 | number_of_children = 0
160 |
161 | begin
162 | allowance_per_child = budget / number_of_children
163 | rescue ZeroDivisionError
164 | allowance_per_child = 0
165 | end
166 | ```
167 |
168 | ##Final Thoughts##
169 |
170 | You will always have discretion on when and where to address errors in your code. With that said, many programmers would agree that throwing an error "early" and catching it "late" is a best practice. What this is really getting at is the idea that handling errors at the "higher" levels of your code (i.e. those closer to the end user) is better than fixing them far too soon.
171 |
172 | For the sake of example, imagine you're reading in a file which contains information on real estate properties. It's often the case that the raw data you're working with is incomplete or perhaps even wrong. When you're converting those entries into Property instances, during the validation process you have the choice of raising an ArgumentError or handling the error immediately.
173 |
174 | properties.csv (Bad zip and rent input)
175 |
176 | street |city |state|zip |rent
177 | --------------|--------|-----|-----|---------
178 | Awesome Street|New York|NY | |2400
179 | Tiny Road |Roanoke |VA |24011|xfxx00
180 | 101st Ave |Seattle |WA |98101|-98000000
181 |
182 | ```ruby
183 | require "csv"
184 |
185 | class Property
186 | def initialize(street, city, state, zip, rent)
187 | # Handle errors here?
188 | @street = street
189 | @city = city
190 | @state = state
191 | @zip = zip
192 | @rent = rent
193 | end
194 | end
195 |
196 | def convert_to_property(array)
197 | # Or here?
198 | end
199 |
200 | properties_file = CSV.read("properties.csv")
201 | properties_file.each do |line|
202 | convert_to_property line
203 | end
204 | ```
205 |
206 | If you opt for the latter, you are making a judgment that an error must be corrected whenever a Property instance cannot be created, *regardless* of how your program is creating that kind of Object. In the case of the former, you are placing the responsibility on the higher levels of your application on what is the proper course of action. In that way, it's potentially more flexible and easier to debug when Exceptions are passed up the chain.
207 |
208 | #Knowledge Check#
209 |
210 | 1. What is the parent class of NoMethodError?
211 |
212 | #Assignment#
213 | 1. auth.rb
214 | 2. users.csv
215 |
216 | Authentication exists in all kinds of applications. In this assignment, your task is to write robust implementations for the methods ```load_users``` and ```login```. I've added a sample CSV file with two valid entries and one invalid pair. To help you out, I've already included some validation in the User ```new``` method to guide you towards your solution.
217 |
218 | *Side note: Never, under ANY circumstances, store login credentials in plain text. This is purely meant to be instructive and NOT a real world solution. Passwords should always be encrypted in some way.*
219 |
--------------------------------------------------------------------------------
/intro/15_exceptions/auth.rb:
--------------------------------------------------------------------------------
1 | require "csv"
2 | require "logger"
3 |
4 | class AuthenticationError < StandardError
5 | end
6 |
7 | class User
8 | attr_reader :username
9 | attr_reader :password
10 |
11 | def initialize(username, password)
12 | raise ArgumentError unless /^[A-Za-z0-9_-]{3,16}$/.match(username)
13 | raise ArgumentError unless /^[A-Za-z0-9!@#$%^&*()_+-=]{6,16}$/.match(password)
14 | @username = username
15 | @password = password
16 | end
17 | end
18 |
19 | class Application
20 | def initialize
21 | @logger = Logger.new(STDOUT)
22 | @users = {}
23 | @is_authenticated = false
24 | end
25 |
26 | def load_users(filename)
27 | # Your code goes here
28 | end
29 |
30 | def login
31 | until @is_authenticated
32 | # Your code goes here
33 | end
34 |
35 | @logger.info("Login successful!")
36 | end
37 |
38 | def run
39 | login
40 | end
41 | end
42 |
43 | application = Application.new
44 | application.load_users("users.csv")
45 | application.run
46 |
--------------------------------------------------------------------------------
/intro/15_exceptions/users.csv:
--------------------------------------------------------------------------------
1 | taBoo2,djSMk8!!!!
2 | hunter2,hunter2
3 | adsf,adfs
4 |
--------------------------------------------------------------------------------
/intro/16_yaml/README.md:
--------------------------------------------------------------------------------
1 | #YAML#
2 |
3 | Sending Objects over the internet and between applications is a very common task in programming. For smaller, self-contained programs, you are unlikely ever going to need to **serialize** and **deserialize** your Ruby Objects into other formats: you'll just use the Objects in their natural representation. However, as your applications grow and become increasingly interconnected with other programs, you'll soon find that not everything is written in Ruby! For example, a Java Object is not the same as a Ruby Object.
4 |
5 | If you had to create equivalent data structures in both, how would you go about doing that? That very question is the starting point for data serialization and markup languages like YAML. Although originally named **Yet Another Markup Language**, YAML was later changed to **YAML Ain't Markup Language**, a recursive acronym, when its scope outgrew its beginnings.
6 |
7 | ##Example##
8 |
9 | YAML functionality is not included in the Ruby interpreter by default. To begin working with YAML, you must ```require``` the YAML module which ships with the Ruby installation.
10 |
11 | ```ruby
12 | require "yaml"
13 |
14 | class Item
15 | attr_accessor :name
16 |
17 | def initialize(name)
18 | @name = name
19 | end
20 | end
21 |
22 | class Meal
23 | attr_accessor :name
24 |
25 | def initialize(name)
26 | @name = name
27 | @items = []
28 | end
29 |
30 | def add(item)
31 | @items << item
32 | end
33 | end
34 |
35 | # Build Ruby Objects
36 | toast = Item.new("Toast")
37 | butter = Item.new("Butter")
38 |
39 | breakfast = Meal.new("Breakfast")
40 | breakfast.add(toast)
41 | breakfast.add(butter)
42 |
43 | burger = Item.new("Burger")
44 | fries = Item.new("Fries")
45 |
46 | lunch = Meal.new("Lunch")
47 | lunch.add(burger)
48 | lunch.add(fries)
49 |
50 | # Serialize to YAML Objects and write to file
51 | yaml_data = [breakfast, lunch].to_yaml
52 | File.open("meals.yml", "w") { |file| file.write yaml_data }
53 |
54 | # Convert YAML to Ruby Objects
55 | ruby_data = YAML::load yaml_data
56 |
57 | # Load YAML from file
58 | ruby_from_file = YAML.load_file "meals.yml"
59 | ```
60 |
61 | The output saved to meals.yml:
62 | ```yaml
63 | ---
64 | - !ruby/object:Meal
65 | name: Breakfast
66 | items:
67 | - !ruby/object:Item
68 | name: Toast
69 | - !ruby/object:Item
70 | name: Butter
71 | - !ruby/object:Meal
72 | name: Lunch
73 | items:
74 | - !ruby/object:Item
75 | name: Burger
76 | - !ruby/object:Item
77 | name: Fries
78 | ```
79 |
80 | As you can see, a YAML document has a well-defined structure for representing data and it's *human-readable*. The magic words for converting a Ruby Object to a YAML equivalent is the method ```to_yaml```. Likewise, to deserialize YAML into Ruby Objects, you can either use ```YAML::load``` for existing YAML data, or you can use ```YAML.load_file``` to translate a full YAML document.
81 |
82 | ##What About CSV?##
83 |
84 | In previous modules, we've used CSV files to store Object attributes that are later converted into known classes, and it worked perfectly fine. What advantages does YAML have over it?
85 |
86 | The main point to recognize about the CSV format is that it works great for representing a matrix of "flat" data structures. A CSV file is like a spreadsheet that only contains numbers and Strings: none of the individual cell items are more complicated Objects with their own sets of attributes. Those kinds of Objects cannot be easily represented in the same way. Conversely, serialization formats like YAML do in fact preserve more complicated data structures. For times when you need that additional complexity, YAML can be a desirable solution.
87 |
88 | #Knowledge Check#
89 |
90 | 1. When you serialize a Ruby Object into YAML, are methods also represented in the output document?
91 |
92 | #Assignment#
93 | 1. order.rb
94 | 2. order.yml
95 |
96 | When was the last time you ordered a movie from Amazon? How about the last time you went grocery shopping? In this assignment, you will implement a basic calculator that reads a file, ```order.yml```, and prints a receipt of the items, their total cost, the taxed amount, and the grand total as you would see in a typical bill or invoice. Make note that this assignment uses the BigDecimal module for greater price accuracy.
97 |
--------------------------------------------------------------------------------
/intro/16_yaml/order.rb:
--------------------------------------------------------------------------------
1 | require "bigdecimal"
2 | require "yaml"
3 |
4 | class Item
5 | attr_reader :name
6 | attr_reader :price
7 |
8 | def initialize(name, price)
9 | @name = name
10 | @price = price
11 | end
12 | end
13 |
14 | class Order
15 | @@WIDTH1 = 20
16 | @@WIDTH2 = 8
17 | @@SALES_TAX_RATE = 0.08875
18 | attr_reader :items
19 |
20 | def initialize
21 | @items = []
22 | end
23 |
24 | def add(item)
25 | # Your code goes here
26 | end
27 |
28 | def print_receipt
29 | @items.each do |item|
30 | puts "#{item.name.ljust(@@WIDTH1)}" +
31 | "#{item.price.truncate(2).to_s("F").rjust(@@WIDTH2)}"
32 | end
33 | puts "".ljust(@@WIDTH1 + @@WIDTH2, "=")
34 | puts "#{"Total".ljust(@@WIDTH1)}" +
35 | "#{base.truncate(2).to_s("F").rjust(@@WIDTH2)}"
36 | puts "#{"Tax".ljust(@@WIDTH1)}#{tax.truncate(2).to_s("F").rjust(@@WIDTH2)}"
37 | puts "".ljust(@@WIDTH1 + @@WIDTH2, "=")
38 | puts "#{"Grand Total".ljust(@@WIDTH1)}" +
39 | "#{total_cost.truncate(2).to_s("F").rjust(@@WIDTH2)}"
40 | end
41 |
42 | private
43 | def base
44 | # Your code goes here
45 | end
46 |
47 | private
48 | def tax
49 | # Your code goes here
50 | end
51 |
52 | private
53 | def total_cost
54 | # Your code goes here
55 | end
56 | end
57 |
58 | order = YAML.load_file "order.yml"
59 | order.print_receipt
60 |
--------------------------------------------------------------------------------
/intro/16_yaml/order.yml:
--------------------------------------------------------------------------------
1 | --- !ruby/object:Order
2 | items:
3 | - !ruby/object:Item
4 | name: Interstellar
5 | price: !ruby/object:BigDecimal 18:0.1999E2
6 | - !ruby/object:Item
7 | name: The Imitation Game
8 | price: !ruby/object:BigDecimal 18:0.1795E2
9 | - !ruby/object:Item
10 | name: Nightcrawler
11 | price: !ruby/object:BigDecimal 18:0.1799E2
12 |
--------------------------------------------------------------------------------
/intro/17_xml/README.md:
--------------------------------------------------------------------------------
1 | #XML#
2 |
3 | **Extensible Markup Language** is another extremely common data serialization format that you will encounter in your programming pursuits. XML has been a staple of data transmission in networked applications for many years, and when paired with things like XML Schema and XSLT, its scope and value is further expanded.
4 |
5 | ```xml
6 |
7 | Guardians of the Galaxy
8 | $20.00
9 | Movies
10 |
11 | ```
12 |
13 | An object represented using XML is in essence a hierarchy of parent and child tags. A basic attribute value is enclosed with tags like ``````. By design XML is inflexible in its syntax, so while it helps validate datatypes and document structure, it means you will sometimes have to deal with issues related to malformed documents.
14 |
15 | ##Local File##
16 |
17 | ```ruby
18 | require "rexml/document"
19 |
20 | xml_file = File.new("some_file.xml")
21 | xml_doc = REXML::Document.new(xml_file)
22 | ```
23 |
24 | ##Network File##
25 |
26 | ```ruby
27 | require "open-uri"
28 | require "rexml/document"
29 |
30 | reddit_xml = open("https://www.reddit.com/.xml").read
31 | xml_doc = REXML::Document.new(reddit_xml)
32 | ```
33 |
34 | #Knowledge Check#
35 |
36 | 1. ?
37 |
38 | #Assignment#
39 | ?.rb
40 |
--------------------------------------------------------------------------------
/intro/README.md:
--------------------------------------------------------------------------------
1 | #Intro Level Ruby Exercises#
2 |
3 | Everyone's gotta start somewhere, right? These modules are for absolute beginners. If you don't know anything about Ruby or maybe even programming in general, start reading here.
4 |
--------------------------------------------------------------------------------