├── .travis.yml ├── LICENSE ├── amazon ├── app.rb └── classes │ ├── order.rb │ ├── product.rb │ └── user.rb ├── person ├── person1.rb ├── person2.rb ├── person3.rb ├── person4.rb ├── person5.rb └── self.rb ├── readme.md └── variables.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | before_script: 3 | - gem install rspec 4 | - git clone https://github.com/ga-dc/lesson-plan-checklist 5 | script: 6 | - rspec lesson-plan-checklist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | -------------------------------------------------------------------------------- /amazon/app.rb: -------------------------------------------------------------------------------- 1 | require "pry" 2 | 3 | require_relative "classes/user" 4 | require_relative "classes/product" 5 | require_relative "classes/order" 6 | 7 | alice = User.new("Alice") 8 | bob = User.new("Bob") 9 | sneakers = Product.new("Air Jordans", 99.00) 10 | alice.order(sneakers) 11 | bob.order(sneakers) 12 | 13 | binding.pry 14 | 15 | puts "Done!" 16 | -------------------------------------------------------------------------------- /amazon/classes/order.rb: -------------------------------------------------------------------------------- 1 | class Order 2 | attr_reader :user, :product 3 | @@all = [] 4 | 5 | def initialize(product, user) 6 | @user = user 7 | @product = product 8 | @@all.push(self) 9 | end 10 | 11 | def Order.all 12 | @@all 13 | end 14 | 15 | def Order.total_revenue 16 | total = 0 17 | @@all.each do |order| 18 | total += order.product.price 19 | end 20 | return total 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /amazon/classes/product.rb: -------------------------------------------------------------------------------- 1 | class Product 2 | attr_accessor :orders, :price, :title 3 | @@all = [] 4 | 5 | def initialize(title, price) 6 | @title = title 7 | @price = price 8 | @orders = [] 9 | @@all.push(self) 10 | end 11 | 12 | def total_sales 13 | return (self.price * @orders.count) 14 | end 15 | 16 | def Product.named name 17 | return @@all.select{|p| p.title == name}.first 18 | end 19 | 20 | def Product.all 21 | @@all 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /amazon/classes/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | attr_reader :orders 3 | 4 | def initialize(name) 5 | @name = name 6 | @password = encode_password(name) 7 | @orders = [] 8 | end 9 | 10 | def order(product) 11 | order = Order.new(product, self) 12 | @orders.push(order) 13 | product.orders.push(order) 14 | end 15 | 16 | def authorize(password) 17 | puts "*" * 10 18 | if compare_password(password) 19 | puts "You're authorized!" 20 | else 21 | puts "Incorrect password" 22 | end 23 | puts "*" * 10 24 | end 25 | 26 | private 27 | def encode_password input 28 | input.reverse 29 | end 30 | 31 | def compare_password input 32 | if input == @password 33 | return true 34 | else 35 | return false 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /person/person1.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | class Person 4 | end 5 | 6 | binding.pry 7 | 8 | puts "program complete" # fixes an issue with binding.pry if it's the last line of a program 9 | -------------------------------------------------------------------------------- /person/person2.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | class Person 4 | def initialize() 5 | puts("new person created") 6 | end 7 | end 8 | 9 | binding.pry 10 | # bob = Person.new # "new person created" 11 | 12 | puts "program complete" # fixes an issue with binding.pry if it's the last line of a program 13 | -------------------------------------------------------------------------------- /person/person3.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | class Person 4 | def initialize(initial_name) 5 | @name = initial_name 6 | end 7 | 8 | def introduce 9 | puts "Hello, I'm #{@name}" 10 | end 11 | end 12 | 13 | 14 | # 1. Each instance gets a unique value for that var 15 | # 2. Can be used anywhere in the class 16 | 17 | me = Person.new("Adam Bray") 18 | me.introduce # prints "Hello, I'm Adam Bray" 19 | 20 | jesse = Person.new("Jesse Shawl") 21 | jesse.introduce # prints "Hello, I'm Jesse Shawl" 22 | 23 | binding.pry 24 | 25 | 26 | puts "program complete" # fixes an issue with binding.pry if it's the last line of a program 27 | -------------------------------------------------------------------------------- /person/person4.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | class Person 4 | def initialize(initial_name, initial_hunger_level) 5 | @name = initial_name 6 | @hunger_level = initial_hunger_level 7 | end 8 | 9 | def introduce 10 | puts "Hello, I'm #{@name}" 11 | end 12 | 13 | # GETTERS 14 | def name 15 | return @name 16 | end 17 | 18 | def hunger_level 19 | return @hunger_level 20 | end 21 | 22 | # SETTERS 23 | def name=(new_name) 24 | @name = new_name 25 | end 26 | 27 | def hunger_level=(new_hunger_level) 28 | if new_hunger_level < 0 29 | @hunger_level = 0 30 | else 31 | @hunger_level = new_hunger_level 32 | end 33 | end 34 | 35 | end 36 | 37 | me = Person.new("Adam Bray", 10) 38 | binding.pry 39 | 40 | 41 | # # Using Getters 42 | # me.name # returns "Adam Bray" 43 | # me.hunger_level # returns 10 44 | # 45 | # # Using / Testing Setters 46 | # me.name = "Adam Bray, Esq." # changes name 47 | # me.name # returns "Adam Bray, Esq." 48 | # 49 | # me.hunger_level = 5 # changes hunger level 50 | # me.hunger_level # returns 5 51 | # me.hunger_level = -8 # changes hunger level, according to rules 52 | # me.hunger_level # returns 0 53 | 54 | 55 | puts "program complete" # fixes an issue with binding.pry if it's the last line of a program 56 | -------------------------------------------------------------------------------- /person/person5.rb: -------------------------------------------------------------------------------- 1 | require 'pry' 2 | 3 | # functionally identical to the previous example, much less code 4 | class Person 5 | attr_accessor :name 6 | attr_reader :hunger_level 7 | 8 | def intitialize(initial_name, initial_hunger_level) 9 | @name = initial_name 10 | @hunger_level = initial_hunger_level 11 | end 12 | 13 | def introduce 14 | puts "Hello, I'm #{@name} " 15 | end 16 | 17 | # Custom setter for hunger_level 18 | def hunger_level=(new_hunger_level) 19 | if new_hunger_level < 0 20 | @hunger_level = 0 21 | else 22 | @hunger_level = new_hunger_level 23 | end 24 | end 25 | 26 | end 27 | 28 | binding.pry 29 | 30 | me = Person.new 31 | puts "program complete" # fixes an issue with binding.pry if it's the last line of a program 32 | -------------------------------------------------------------------------------- /person/self.rb: -------------------------------------------------------------------------------- 1 | class Person 2 | attr_accessor :name 3 | attr_reader :hunger_level 4 | 5 | def intitialize(initial_name, initial_hunger_level) 6 | @name = initial_name 7 | @hunger_level = initial_hunger_ 8 | end 9 | 10 | def hunger_level=(new_hunger_level) 11 | if new_hunger_level < 0 12 | @hunger_level = 0 13 | else 14 | @hunger_level = new_hunger_level 15 | end 16 | end 17 | 18 | def demonstrate_using_self 19 | # GETTERS 20 | # works, but referencing the instance variable is ususally not ideal 21 | puts @name 22 | 23 | # even better. this calles the name *method* on the current instance, which 24 | # returns the underlying instance variable's value. (self is implied) 25 | puts name 26 | 27 | # explicit use of self, but not necessary, as we see in the above line: 28 | puts self.name 29 | 30 | # SETTERS 31 | 32 | # works, but NOT GOOD PRACTICE, as we should use the interface (setter 33 | # method), which enforces valid data 34 | @hunger_level = -10 35 | 36 | # INCORRECT/DOESN'T WORK, creates a local variable instead of calling the 37 | # setter method like we want 38 | hunger_level = 10 39 | 40 | # CORRECT, calls the instance method to set the value 41 | self.hunger_level = 10 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Intro to Object-Oriented Programming in Ruby 2 | 3 | ## Learning Objectives 4 | 5 | - Explain the relationship between `.new()` and `def initialize()` 6 | - Distinguish whether a piece of data is best-suited to being stored in a local, instance, or class variable 7 | - Explain whether given data is best-suited to having its accessibility defined by `attr_accessor`, `attr_reader`, `attr_writer`, or none of the above 8 | - Properly define instance and class variables 9 | - List two ways of defining class methods 10 | 11 | ## Framing: What is OOP? 12 | 13 | Ruby is an object-oriented language. That means it's based on the idea that you'll build your application with objects in mind. 14 | 15 | As you learned with OOJS, an object is a collection of related attributes (properties) and methods (behavior). You can think of an object as a little machine: it has displays you can read and buttons you can push. 16 | 17 | When you write an object-oriented application, the idea is that you write the blueprints for these machines, and then you write a sequence of events your users can initiate in which these machines interact with each other. 18 | 19 | Much of the code you see today will be very similar to what you encountered in last week's OOJS class. That's because classes are a concept that have been around for quite some time but only just introduced in Javascript (ECMAScript 2016/ES6). Use this pre-existing knowledge to your advantage in today's class! 20 | 21 | ## You Do: The OOP Process (10 minutes / 0:10) 22 | 23 | We've talked quite a bit about object oriented programming as a paradigm, but we haven't talked much about how to break a problem down into object components. 24 | 25 | #### Example: Monopoly 26 | 27 | > "Monopoly is a game where players try to accumulate wealth through property ownership and money" 28 | 29 | - Game Board 30 | - Players 31 | - Game Tokens 32 | - Property Cards 33 | - Money 34 | 35 | #### Example: Facebook 36 | 37 | > "Facebook is an application where users can post statuses and add friends." 38 | 39 | - Users 40 | - Statuses 41 | - Friends 42 | 43 | Breaking your ideas down gives you a starting place for what those objects may be. 44 | 45 | Spend 10 minutes working with a partner to come up with at least three types of objects that you might define when creating the following examples. Add your responses as a comment on [this issue](https://github.com/ga-wdi-lessons/ruby-oop/issues/17). We'll go over your answers as a class. 46 | 47 | 1. Amazon 48 | 2. A Homework Grading App 49 | 3. An Attendance Taking App 50 | 4. Lyft 51 | 52 | > A helpful approach might be to take the "nouns" involved in the application and say they are objects. 53 | 54 | ## Our First Object (10 minutes / 0:20) 55 | 56 | Say that we have a car. Each of us has a mental model of what a car is: it has four wheels, runs on gas, has a steering wheel that allows us to drive it, etc. This blueprint is like a **class**. Now, when we see a car in front of us, this is like an **instance**, it's an actual **object** in front of us. Each **object** has its blueprint, and is an **instance** of that blueprint or **class**. 57 | 58 | A **class** is a blueprint from which objects are made. In Javascript we used classes, which operate very similarly to **classes** in Ruby. Each object made from a class is an instance of that class. Each instance of a class is an object. 59 | 60 | Let's define a `User` class. We'll be using `binding.pry` to test our code. 61 | 62 | > Aside: pry is a ruby gem that allows us to work with ruby code in an IRB (interactive ruby shell). It's similar to working in our developer console in a web browser with javascript. 63 | 64 | ```bash 65 | $ touch app.rb 66 | $ gem install pry # run this if you haven't installed pry yet 67 | ``` 68 | 69 | ```rb 70 | require "pry" 71 | 72 | class User 73 | 74 | def set_name_to(some_string) 75 | @name = some_string 76 | end 77 | 78 | def get_name 79 | return @name 80 | end 81 | 82 | def greet 83 | puts "Hi! My name is #{@name}!" 84 | end 85 | 86 | end 87 | 88 | binding.pry 89 | 90 | puts "end of file" 91 | ``` 92 | 93 |
94 | What about this Ruby class looks similar to a Javascript class? 95 | 96 | The `class` keyword. The class contains methods. 97 |
98 | 99 | 100 | Now let's generate some instances of this class... 101 | 102 | ```rb 103 | alice = User.new 104 | alice.set_name_to("alice") 105 | puts alice.get_name 106 | 107 | madhatter = User.new 108 | madhatter.set_name_to("Mad Hatter") 109 | puts madhatter.get_name 110 | 111 | alice.greet 112 | madhatter.greet 113 | ``` 114 | 115 | #### Some Questions 116 | 117 | Is `User` a(n)... 118 | - class? 119 | - instance? 120 | 121 | Is `alice` a(n)... 122 | - class? 123 | - instance? 124 | 125 | `User.greet` throws an error. `alice.greet` works fine. So we can deduce that the `greet` method can only be called on... 126 | - instances of the `User` class? 127 | - the `User` class itself? 128 | 129 | Thus, would it make sense to call `greet` a(n)... 130 | - "instance method"? 131 | - "class method"? 132 | 133 | `User.new` works fine. `alice.new` throws an error. So we can deduce that the `new` method can only be called on... 134 | - instances of the `User` class? 135 | - the `User` class itself? 136 | 137 | Thus, it would be make sense to call `new` a(n)... 138 | - "instance method"? 139 | - "class method"? 140 | 141 |
142 | 143 | 144 | `class User` works fine. `class user` throws an error. What's a rule we can deduce about classes from this? 145 | 146 | 147 | > Class names must begin with a capital letter. This is not optional. 148 | 149 |
150 | 151 | 152 |
153 | 154 | `class UserName` works fine. `class User Name` throws an error. What's a rule we can deduce about classes from this? 155 | 156 | 157 | > Class names cannot contain spaces. 158 | 159 |
160 | 161 | ## Initializing Users (10 minutes / 0:30) 162 | 163 |
164 | What was the purpose of a constructor function in Javascript classes? 165 | 166 | > To initialize any properties we want a class instance to have when it is created. 167 | 168 |
169 | 170 | Ruby classes have an equivalent to Javascript constructors: the `initialize` method! 171 | 172 | ```rb 173 | require 'pry' 174 | 175 | class User 176 | 177 | def initialize 178 | puts "I'm a new User" 179 | end 180 | 181 | def set_name_to(some_string) 182 | @name = some_string 183 | end 184 | 185 | def get_name 186 | return @name 187 | end 188 | 189 | def greet 190 | puts "Hi! My name is #{@name}!" 191 | end 192 | 193 | end 194 | 195 | binding.pry 196 | 197 | puts "end of file" 198 | ``` 199 | 200 | ```rb 201 | alice = User.new 202 | alice.greet 203 | 204 | madhatter = User.new 205 | madhatter.greet 206 | 207 | 208 | puts alice 209 | puts madhatter 210 | ``` 211 | 212 |
213 | What can we conclude about the relationship of `def initialize` and `.new`? (Hint: it serves the same purpose as Javascript's constructor function) 214 | - The `initialize` method is run every time `.new` is called. 215 | - Use `.new` to create a new object. 216 | - `initialize` is called automatically if defined in a class. 217 | - `.new` is a method of the class. 218 | - `initialize` is a method of the instance. 219 | - Call to `new` must come first; until you call `new` there is no instance to call `initialize` on. 220 |
221 | 222 |
223 | How is this different from other User instance methods we've seen? 224 | 225 | `initialize` can only be called by the `.new` class method (i.e. it only runs once when the object is initially created). 226 | 227 |
228 | 229 | 230 | ### You Can Pass Arguments to `initialize` 231 | 232 | `initialize` is a special method in its relationship to `.new`, but otherwise it behaves like any other method. This means you can pass arguments to it (again, just like Javascript's `constructor`)... 233 | 234 | ```rb 235 | require "pry" 236 | 237 | class User 238 | 239 | def initialize(firstname, lastname) 240 | puts "I'm a new User named #{firstname} #{lastname}" 241 | end 242 | 243 | end 244 | 245 | binding.pry 246 | 247 | puts "end of file" 248 | ``` 249 | 250 | ```rb 251 | # pry 252 | harry = User.new("Harry", "Potter") 253 | # I'm a new User named Harry Potter 254 | # => # 255 | ``` 256 | 257 | ### Instance Variables (10 minutes / 0:40) 258 | 259 | Let's create a method that prints the full name of the user. 260 | 261 | In Ruby, normal variables are available only inside the method in which they were created. 262 | 263 | If you put an `@` before the variable's name, it becomes an instance variable and therefore available inside the entire `instance` in which it was created. 264 | 265 | ```rb 266 | class User 267 | 268 | def initialize(firstname, lastname) 269 | @firstname = firstname 270 | @lastname = lastname 271 | end 272 | 273 | def full_name 274 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 275 | end 276 | 277 | end 278 | ``` 279 | 280 | ```rb 281 | # pry 282 | harry = User.new("Harry", "Potter") 283 | # => # 284 | harry.full_name 285 | # => "Harry Potter" 286 | ``` 287 | 288 | ### Getting and Setting Properties 289 | 290 | To **get** Harry's first name, we can't simply type `harry.firstname`. To **set** Harry's first name, we can't simply type `harry.firstname = "Harry"` 291 | 292 | The only things available **outside** an instance are its methods. `@firstname` is a property, not a method. We can't access data inside of an instance unless it contains methods that let us do so. 293 | 294 | To make a property "gettable" and "settable", we need to create "getter" and "setter" methods for it. 295 | 296 | ```rb 297 | class User 298 | 299 | def initialize(firstname, lastname) 300 | @firstname = firstname 301 | @lastname = lastname 302 | end 303 | 304 | def full_name 305 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 306 | end 307 | 308 | def get_firstname 309 | return @firstname 310 | end 311 | 312 | def set_firstname(firstname) 313 | @firstname = firstname 314 | end 315 | 316 | end 317 | ``` 318 | 319 | ```rb 320 | # pry 321 | harry = User.new("Harry", "Potter") 322 | # => # 323 | puts harry.get_firstname 324 | # "Harry" 325 | harry.set_firstname("Ginny") 326 | puts harry.get_firstname 327 | # "Ginny" 328 | ``` 329 | 330 | ## attr_accessor 331 | 332 | Recall how we couldn't simply type `Harry.firstname = "some other name"` the previous example. 333 | 334 | ```rb 335 | class User 336 | 337 | def initialize(firstname, lastname) 338 | @firstname = firstname 339 | @lastname = lastname 340 | end 341 | 342 | def full_name 343 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 344 | end 345 | 346 | def get_firstname 347 | return @firstname 348 | end 349 | 350 | def set_firstname(firstname) 351 | @firstname = firstname 352 | end 353 | 354 | end 355 | ``` 356 | 357 | ```rb 358 | harry = User.new("Harry", "Potter") 359 | harry.firstname = "Ginny" 360 | # This throws an error 361 | harry.set_firstname("Ginny") 362 | puts harry.get_firstname 363 | # => 364 | ``` 365 | 366 | If only there were a way to define a class so that we don't have to define a getter and setter method for every single property... 367 | 368 | ```rb 369 | class User 370 | 371 | attr_accessor :firstname, :lastname 372 | 373 | def initialize(firstname, lastname) 374 | @firstname = firstname 375 | @lastname = lastname 376 | end 377 | 378 | def full_name 379 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 380 | end 381 | 382 | end 383 | ``` 384 | 385 | ```rb 386 | nayana = User.new("Nayana", "Davis") 387 | nayana.firstname = "Nayana" 388 | puts nayana.firstname 389 | ``` 390 | 391 |
392 | We now can directly access properties on the User instance, so we can deduce that `attr_accessor` is a shortcut that does what? 393 | 394 | > It creates getter and setter methods for the `firstname` instance variable. 395 | 396 |
397 | 398 | ### `attr_accessor` is actually a shortcut that combines two other shortcuts 399 | 400 | #### `attr_accessor` is `attr_reader` combined with `attr_writer`. 401 | 402 | `attr_reader` makes an attribute readable, `attr_writer` makes an attribute writeable. `attr_accessor` makes an attribute both readable **AND** writeable. 403 | 404 | To illustrate the difference between `attr_reader` and `attr_writer`, let's have a look at the code below. 405 | 406 | ```rb 407 | class User 408 | attr_reader :firstname 409 | attr_writer :lastname 410 | 411 | def initialize(firstname, lastname) 412 | @firstname = firstname 413 | @lastname = lastname 414 | end 415 | 416 | def full_name 417 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 418 | end 419 | 420 | end 421 | ``` 422 | ```rb 423 | hermione = User.new("Hermione", "Granger") 424 | hermione.firstname 425 | # => "Hermione" 426 | hermione.lastname 427 | # => Error! 428 | hermione.firstname = "Ginny" 429 | # => Error! 430 | hermione.lastname = "Weasley" 431 | hermione.full_name 432 | # => "Hermione Weasley" 433 | ``` 434 | 435 | `attr_reader` creates a *getter* method only. Trying to do `hermione.firstname = "Ginny"` will fail. 436 | 437 | `attr_writer` creates a *setter* method only. Trying to do `puts hermione.lastname` will fail. 438 | 439 | `attr_accessor` creates getters and setters. 440 | > You will most commonly use `attr_accessor` 441 | ------- 442 | 443 | ## Break (10 minutes / 1:00) 444 | 445 | ------- 446 | 447 | ## You Do: Monkies! (20 minutes / 1:20) 448 | 449 | For the next exercise, clone down the repo linked below: 450 | https://github.com/ga-wdi-exercises/oop_monkey 451 | 452 | ------- 453 | 454 | ## Class Attributes / Variables (5 minutes / 1:25) 455 | 456 | Let's come up with a way of keeping track of how many users have been created total... 457 | 458 | ```rb 459 | class User 460 | attr_accessor :firstname, :lastname 461 | @@all = 0 462 | 463 | def count 464 | return @@all 465 | end 466 | 467 | def initialize(firstname, lastname) 468 | @firstname = firstname 469 | @lastname = lastname 470 | @@all += 1 471 | end 472 | 473 | def full_name 474 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 475 | end 476 | end 477 | ``` 478 | 479 | ```rb 480 | harry = User.new("Harry", "Potter") 481 | harry.count 482 | # => 1 483 | ron = User.new("Ron", "Weasley") 484 | harry.count 485 | # => 2 486 | ron.count 487 | # => 2 488 | draco = User.new("Draco", "Malfoy") 489 | harry.count 490 | # => 3 491 | ron.count 492 | # => 3 493 | draco.count 494 | # => 3 495 | ``` 496 | 497 | But there's something weird going on here: note that we aren't counting the number of Rons, Harrys or Dracos. Think about what `.count` might be returning. More on this in a moment! 498 | 499 | A variable name beginning with `@@` is a **class variable**. Every instance of a class has the same value for this variable. It cannot be accessed with `attr_accessor`. You have to actually create a method to access it. 500 | 501 | ### Class Attributes and Methods Together (10 minutes / 1:40) 502 | 503 | A method name beginning with the class name is a **class method**. It is attached to the class itself, rather than to instances. There are also methods you call on `User` itself. So far we've only seen `.new`.It would make more sense if, in order to retrieve the total number of users, we ran `User.count` instead of `harry.count`... 504 | 505 | ```rb 506 | class User 507 | attr_accessor :firstname, :lastname 508 | @@all = 0 509 | 510 | def initialize(firstname, lastname) 511 | @firstname = firstname 512 | @lastname = lastname 513 | @@all += 1 514 | end 515 | 516 | def full_name 517 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 518 | end 519 | 520 | # You could also define this as `def self.count`, where self represents the class 521 | def User.count 522 | return @@all 523 | end 524 | 525 | end 526 | ``` 527 | 528 | ```rb 529 | ginny = User.new("Ginny", "Weasley") 530 | ginny.count 531 | # => Error! 532 | User.count 533 | # => 1 534 | ``` 535 | 536 | ## Self (5 minutes / 1:45) 537 | 538 | `self` is a special variable that contains the current instance of an object (like `this` in Javascript). It's how the object refers to it*self*. 539 | 540 | `self` has another context as well: `def self.all` Here, `self` refers to `class User`. What does this mean? It means that the method `.all` is called on the class `User`, much like `.new`, and is therefore a class method. 541 | 542 | ```rb 543 | class User 544 | attr_accessor :firstname, :lastname 545 | @@all = [] 546 | 547 | def initialize(firstname, lastname) 548 | @firstname = firstname 549 | @lastname = lastname 550 | # here, `self` refers to the current instance 551 | puts "Creating #{self.firstname}" 552 | @@all.push(self) 553 | end 554 | 555 | def full_name 556 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 557 | end 558 | 559 | # Can also be written as `def User.all` 560 | # here, `self` refers to the class 561 | def self.all 562 | return @@all 563 | end 564 | 565 | end 566 | ``` 567 | 568 | ```rb 569 | draco = User.new("Draco", "Malfoy") 570 | # "Creating Draco" 571 | luna = User.new("Luna", "Lovegood") 572 | # "Creating Luna" 573 | bellatrix = User.new("Bellatrix", "LeStrange") 574 | # "Creating Bellatrix" 575 | User.all 576 | # => [#, #, #] 577 | ``` 578 | 579 | ## Break (10 minutes / 1:55) 580 | 581 | ## You Do: Orange Tree (25 minutes / 2:20) 582 | 583 | > From Chris Pine's "Learn to Program - Second Edition": p 112, section 13.6 584 | 585 | Make an OrangeTree class that has... 586 | 587 | - a `height` method that returns its height in feet 588 | - it's initial value should be determined by some input 589 | - hint: you don't necessarily have to define the method 590 | - a `one_year_passes` method that, when called, ages the tree one year. Start the age at `0`. 591 | 592 | > Test your code. 593 | 594 | - Each year the tree grows taller by one foot 595 | - After 50 years the tree should "die" (its height goes to 0) 596 | 597 | > Test your code. 598 | 599 | - After the first 5 years, the tree bears 20 oranges 600 | - You should be able to `count_the_oranges`, which returns the number of oranges on the tree 601 | 602 | > Test your code. 603 | 604 | - You should be able to `pick_an_orange`, which reduces the number of oranges by 1 605 | - Ensure that your tree cannot have negative oranges 606 | - Ensure that after each year your tree has 20 total oranges again 607 | 608 | > Test your code. 609 | 610 | - The number of oranges the tree bears each year is equal to 20 plus the age of the tree 611 | 612 | #### Bonus 613 | 614 | Create an `OrangeTreeOrchard` class that manages multiple `OrangeTrees`. It can... 615 | 616 | - Age all the trees by one year 617 | - Pick and count all the fruit 618 | - Calculate average height and fruit of all orange trees 619 | 620 | ## Closing / Questions (10 minutes / 2:30) 621 | 622 | ## Sample Questions 623 | 624 | - Create a Ruby class for a student, initialized with a name and an age. 625 | - Write a getter for name and age, and a setter for name only 626 | - Create a new student and demonstrate using all the methods 627 | - Explain the difference between local and instance variables 628 | 629 | ## Glossary 630 | 631 | * **Class**: a blueprint for objects 632 | * **Instance**: an object that is created using a class 633 | * **Instance Variable**: a property that is particular to an instance 634 | * **Class Variable:** a property that is accessible by all instances of a class 635 | * **Instance Method**: a method that can be called by an instance of a class (e.g., `sample_user.reset_password`) 636 | * **Class Method**: a method that can be called by a class (e.g., `User.list_user`) 637 | * **`initialize`**: a class method that, when triggered, creates an instance and assigns initial properties 638 | * **`.new`**: a class method that, when called, triggers its `initialize` method 639 | * **`attr_accessor`**: a setting that allows you to directly "get" or "set" an instance variable 640 | 641 | ------ 642 | 643 | ## Bonus: Public and Private (5 minutes / 1:50) 644 | 645 | ### You Do 646 | 647 | - Draw a picture of a machine, real or imaginary, that has inputs (buttons, switches, keypads...) and displays (dials, lights, screens...). Label what they do. 648 | - Most machines have internal gauges or memories that help it make decisions: temperature monitors, voltage monitors, hard disks, and so on. These are visible only inside the machine: whoever's using the machine can't see them. Draw two of these on your machine and label them. 649 | 650 | By default all instance and class methods are *public*, except for `def initialize` which is *private*. This means they're visible to other objects. An analogy: they're functions that have their own buttons on the outside of the machine, like a car's turn signal. 651 | 652 | There may be methods that all other objects don't need to know about. 653 | 654 | ```rb 655 | class User 656 | attr_accessor :firstname, :lastname 657 | @@all = [] 658 | 659 | def initialize(firstname, lastname, password) 660 | @firstname = firstname 661 | @lastname = lastname 662 | @password = encrypt(password) 663 | @@all.push(self) 664 | end 665 | 666 | def full_name 667 | return "#{@firstname.capitalize} #{@lastname.capitalize}" 668 | end 669 | 670 | def User.all 671 | return @@all 672 | end 673 | 674 | private 675 | def encrypt(input) 676 | return input.reverse 677 | end 678 | 679 | end 680 | ``` 681 | 682 | ```rb 683 | harry = User.new("Harry", "Potter", "Expecto Patronum") 684 | # # 685 | harry.encrypt("Expecto Patronum") 686 | # Error! Private method `encrypt` 687 | ``` 688 | 689 | Putting `private` in front of methods means they can be used inside the object, but are not available outside it. An analogy: they're functions that do not have their own buttons on the outside of the machine, like a car's air filter. 690 | 691 | `private` is useful mostly for keeping things organized. Consider jQuery: It's already cluttered enough, with all these methods like `.fadeOut` and `.css`. It has lots of other methods hidden inside it that we don't really need to know about. 692 | 693 | ## Review: Why OOP? 694 | 695 | #### Easy to Understand 696 | 697 | Objects help us build programs that model how we tend to think about the world. 698 | Instead of a bunch of variables and functions (procedural style), we can group 699 | relevant data and functions into objects, and think about them as individual, 700 | self-contained units. This grouping of properties (data) and methods is called 701 | *encapsulation*. 702 | 703 | #### Managing Complexity 704 | 705 | This is especially important as our programs get more and more complex. We can't 706 | keep all the code (and what it does) in our head at once. Instead, we often want 707 | to think just a portion of the code. 708 | 709 | Objects help us organize and think about our programs. If I'm looking at code 710 | for a Squad object, and I see it has associated *people*, and those people can 711 | dance when the squad dances, I don't need to think about or see all the code 712 | related to a person dancing. I can just think at a high level "ok, when a squad 713 | dances, all it's associated people dance". This is a form of *abstraction*... I 714 | don't need to think about the details, just what's happening at a high-level. 715 | 716 | #### Ensuring Consistency 717 | 718 | One side effect of *encapsulation* (grouping data and methods into objects) is 719 | that these objects can be in control of their data. This usually means ensuring 720 | consistency of their data. 721 | 722 | Consider the bank account example... I might define a bank account object 723 | such that you can't directly change it's balance. Instead, you have to use the 724 | `withdrawl` and `deposit` methods. Those methods are the *interface* to the 725 | account, and they can enforce rules for consistency, such as "balance can't be 726 | less than zero". 727 | 728 | #### Modularity 729 | 730 | If our objects are well-designed, then they interact with each other in 731 | well-defined ways. This allows us to refactor (rewrite) any object, and it 732 | should not impact (cause bugs) in other areas of our programs. 733 | 734 | ## Extra Practice: Scrabble 735 | 736 | Clone this exercise and follow the instructions in the readme. 737 | 738 | **[Scrabble Word Scorer](https://github.com/ga-dc/scrabbler)** 739 | 740 | ## Resources 741 | 742 | - [Variables cheat sheet](variables.md) 743 | - Other exercises 744 | - [Monkeys](https://github.com/ga-wdi-exercises/oop_monkey) 745 | - [Application Config](https://github.com/ga-wdi-exercises/ruby_application_configuration) 746 | - [Superheroes](https://github.com/ga-wdi-exercises/superheros) 747 | - Screencasts 748 | - WDI8, Robin 749 | - [Part 1](https://www.youtube.com/watch?v=-pAFSheGDr0) 750 | - [Part 2](https://www.youtube.com/watch?v=ZgZCtns27pE) 751 | - [Part 3](https://youtu.be/npM249VzB0I) 752 | - [Part 4](https://youtu.be/su_XYcj_Cpk) 753 | -------------------------------------------------------------------------------- /variables.md: -------------------------------------------------------------------------------- 1 | Variables in Ruby 2 | 3 | * `$DATA_DIR = "/data/set1"` 4 | * **global variable** 5 | * global scope 6 | * starts with `$` 7 | * snake_case 8 | 9 | * `my_fav_color = "red"` 10 | * **local variable** 11 | * local scope (only valid in the current method) 12 | * lower_snake_case 13 | 14 | * `PI = 3.14` 15 | * **constant** 16 | * local scope 17 | * can't be changed 18 | * ALL_CAPS_SNAKE_CASE 19 | 20 | * `@name = "Adam"` 21 | * **instance variable** 22 | * scoped to the class definition 23 | * starts with `@` 24 | * lower_snake_case 25 | * each instance gets its own unique copy of the variable 26 | 27 | * `@@person_count` 28 | * **class variable** 29 | * scope: anywhere in the class 30 | * two `@`s 31 | * one copy shared within the class and it's instance 32 | 33 | 34 | 35 | Instance Methods: 36 | * defined without self in the name 37 | * called on **instances** 38 | * can reference instance vars 39 | * can reference class vars 40 | 41 | Class Methods: 42 | * define with like: `def self.method_name` 43 | * called on the Class: e.g. `Person.person_count` 44 | * can reference class vars 45 | * can't reference instance methods/vars 46 | --------------------------------------------------------------------------------