├── 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 | ![mac_coffee](https://github.com/sks147/ruby/raw/master/intermediate/18_graphics/mac_coffee.jpeg) 32 | 33 | Final image: 34 | ![mac_coffee](https://github.com/sks147/ruby/raw/master/intermediate/18_graphics/mac_coffee_copyrighted.jpeg) 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 | --------------------------------------------------------------------------------