├── .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 |
--------------------------------------------------------------------------------