|
# tip calculator |
|
# |
|
# bill = 40 |
|
# num_people = 3 |
|
# tip_percent = 0.20 |
|
# |
|
# tip = bill * tip_percent |
|
# total = bill + tip |
|
# my_share = total / num_people |
|
# puts my_share |
|
|
|
|
|
# String interpolation |
|
# |
|
# age = 21 |
|
# puts "I'm #{age} years old" |
|
|
|
|
|
# Mutator Methods |
|
# |
|
# arr = [1,2,3] |
|
# p arr.delete(3) |
|
# p arr |
|
|
|
|
|
# Bang Mutator Methods |
|
# |
|
# str = "LAB Miami" |
|
# puts "Non-mutating" |
|
# puts str.downcase |
|
# puts str |
|
# puts "Mutating" |
|
# puts str.downcase! |
|
# puts str |
|
|
|
|
|
# My own mutators |
|
# a = 1 |
|
# a = a.next |
|
# p a |
|
|
|
|
|
# incrementors |
|
# a = 1 |
|
# a -= 1 # a = a - 1 |
|
# p a |
|
# a += 2 # a = a + 2 |
|
# p a |
|
# a *= 2 # a = a * 2 |
|
# p a |
|
|
|
# incrementors apply to anything that supports + |
|
# a = [1,2,3] |
|
# a += [4,5,6] # a = a + [4,5,6] |
|
# a -= [1] # a = a - [1] |
|
# p a |
|
|
|
|
|
# default value |
|
# b = false |
|
# # the default value of b is 1 |
|
# # if b is nil, then set it to 1 |
|
# b ||= 1 # b = b || 1 |
|
# p b |
|
|
|
# default value for undefined vars |
|
# c ||= 2 # default c to 2 |
|
# p c |
|
|
|
# breaking default value |
|
# d = false |
|
# d ||= true # default to true |
|
# p d |
|
|
|
|
|
# Constants |
|
# CONST = 1 |
|
# CONST = 12 |
|
# puts CONST |
|
|
|
# Math Constants |
|
# p Math::PI |
|
|
|
# Constants can be reset, |
|
# but you should never do this |
|
# Math::PI = 3 |
|
# p Math::PI |
|
|
|
|
|
# Flow Control |
|
# |
|
# dead_people = ["Mortimer", "Ethel", "Buford"] |
|
# alive_people = ["Megan", "Joe", "Kelly"] |
|
# sick_people = alive_people.slice(0, 2) |
|
# |
|
# name = "Mortimer" |
|
# |
|
# if dead_people.include? name |
|
# puts "Don't send a questionnaire to #{name}" |
|
# elsif sick_people.include? name |
|
# puts "Send a questionnaire to #{name} later" |
|
# else |
|
# puts "Send a questionnaire to #{name}" |
|
# end |
|
|
|
|
|
# Un-executed blocks are "ignored" |
|
# |
|
# if false |
|
# complete nonsense |
|
# else |
|
# # 2-level nesting |
|
# unless sick_people.include? name |
|
# # 3-level nesting |
|
# if true |
|
# # nesting this much is bad |
|
# end |
|
# end |
|
# end |
|
|
|
# refactor: single-line if |
|
# |
|
# if alive_people.include? name |
|
# puts 'Questionnaire' |
|
# end |
|
#puts 'Questionnaire' if alive_people.include? name |
|
|
|
|
|
# refactor: assignment if |
|
# |
|
# if alive_people.include? name |
|
# message = "#{name} is alive!" |
|
# else |
|
# message = "Sorry for your loss." |
|
# end |
|
# |
|
# message = if alive_people.include? name |
|
# "#{name} is alive!" |
|
# else |
|
# "Sorry for your loss." |
|
# end |
|
|
|
# refactor: ternary statement if |
|
# |
|
# message = is_alive? ? "Alive!" : "Sorry." |
|
|
|
|
|
# infinite loops |
|
# |
|
# loop do |
|
# puts "Hello" |
|
# end |
|
|
|
|
|
# while/until loops |
|
# |
|
# i = 0 |
|
# until i >= 11 |
|
# puts i |
|
# i += 1 |
|
# # safety break |
|
# break if i > 1000 |
|
# end |
|
|
|
# refactor: 1-line while/until |
|
# |
|
# i = 0 |
|
# puts "#{i += 1}" while i < 10 |
|
# i = 0 |
|
# puts "#{i += 1}" until i == 10 |
|
|
|
|
|
# for-loop |
|
# uncommon in Ruby |
|
# |
|
# for name in alive_people |
|
# puts "Send questionnaire to #{name}" |
|
# end |
|
|
|
|
|
# breaking an infinite loop |
|
# i = 0 |
|
# loop do |
|
# puts "Hello #{i}" |
|
# i += 1 |
|
# if i > 10 |
|
# break |
|
# end |
|
# end |
|
|
|
|
|
# what is a method? |
|
# |
|
# p "".size |
|
# meth = "".method(:size) |
|
|
|
# A method is a data type in Ruby |
|
# it has a class and it's own methods. |
|
# |
|
# p meth.class |
|
# p meth.methods |
|
# |
|
# A method has methods! |
|
|
|
# Since a method is data, I can pass |
|
# it as an argument to anther method. |
|
# def method_method(a_method_arg) |
|
# p a_method_arg.class |
|
# # ... and I can call it. |
|
# p a_method_arg.call |
|
# end |
|
# |
|
# method_method(meth) |
|
|
|
# defensive programming |
|
# |
|
# def add_two(number) |
|
# # duck-type checking |
|
# if number.respond_to? :+ |
|
# if number.respond_to? :push |
|
# number.push(2) |
|
# else |
|
# number + 2 |
|
# end |
|
# end |
|
# end |
|
# |
|
# test-driven development |
|
# def test |
|
# p add_two(1) == 3 |
|
# p add_two(1.4) == 3.4 |
|
# p add_two(nil).nil? |
|
# p add_two({}).nil? |
|
# p add_two([]) == [2] |
|
# p add_two(true).nil? |
|
# p add_two("") # Fix this bug in your hw. |
|
# end |
|
# |
|
# test |
|
|
|
|
|
# implicit returns |
|
# a method returns whatever the last line |
|
# evaluates to |
|
# |
|
# def one |
|
# puts "Some code" |
|
# puts "Some code" |
|
# puts "Some code" |
|
# puts "Some code" |
|
# 1 + 0 |
|
# end |
|
# |
|
# p one |
|
|
|
# Use an explicit return if you'd like |
|
# to return right away, skipping the |
|
# rest of the method |
|
# |
|
# def find_ten |
|
# i = 0 |
|
# loop do |
|
# if i == 10 |
|
# return i # stop when I find 10 |
|
# end |
|
# i += 1 |
|
# end |
|
# end |
|
# |
|
# p find_ten |
|
|
|
|
|
# Passing a method as a "block argument" |
|
# |
|
# def say_hello(argument) |
|
# puts "Hello" |
|
# end |
|
# |
|
# say_hello_method = method(:say_hello) |
|
# 5.times(&say_hello_method) |
|
|
|
# This & syntax changes the method into |
|
# a block argument, which is not the same |
|
# as an argument. |
|
# p say_hello_method |
|
# p &say_hello_method # displays nothing |
|
# |
|
# This syntax is awkward in Ruby, so there's |
|
# a common shortcut. |
|
|
|
|
|
# The Ruby-preferred way to pass a block |
|
# argument. |
|
# |
|
# do/end |
|
# 5.times do |
|
# puts "Hello" |
|
# end |
|
|
|
# brackets { ... } |
|
# 5.times { puts "Hello" } |
|
|
|
# The shorter syntax can be multi-line |
|
# 3.times { |
|
# puts "Hello" |
|
# puts "World" |
|
# } |
|
|
|
|
|
# A block, like a method, can accept an |
|
# argument |
|
# 5.times do |number| |
|
# puts "Hello #{number}" |
|
# end |
|
# |
|
# The times method is excellent for counting. |
|
# It injects the current count into the block. |
|
# If you'd like to count starting at 1 |
|
# 5.times { |number| |
|
# puts number + 1 |
|
# } |
|
# If you'd like to count or by 10s |
|
# 5.times { |number| |
|
# puts number * 10 |
|
# } |
|
# This is Ruby-preferred over for/while loops |
|
# for counting. |
|
|
|
|
|
# What is times doing? |
|
# |
|
# p 5.times |
|
# p 5.times.class |
|
# p 5.times.methods |
|
# |
|
# An Enumerator is a "lightweight" Array |
|
# e.g. Array has 63 methods Enumerator doesn't |
|
# p ([].methods - 5.times.methods).count |
|
|
|
# Enumerator is so closely related to Array |
|
# that you can easily convert it into one. |
|
# p 5.times.to_a |
|
# |
|
# But you can't mutate an Enumerator |
|
# 5.times << 5 |
|
# |
|
# Or skip to the middle. |
|
# 5.times[2] |
|
# |
|
# Enumerators are for looping/iterating/counting. |
|
|
|
|
|
# Ranges |
|
# Also lightweight Arrays |
|
# Just specify start and end points |
|
# p (0..3).to_a |
|
# p (0...3).to_a |
|
|
|
|
|
# The Array.each method also returns an |
|
# Enumerator |
|
# |
|
# arr = [1,2,3,4,5] |
|
# p arr.each |
|
# |
|
# An enumerator typically accepts block args. |
|
# |
|
# For each, the block executes once for *each* item |
|
# in the array. |
|
# The current item is yielded/injected into the block. |
|
# |
|
# arr.each do |number| |
|
# p number |
|
# end |
|
# |
|
# each works on Arrays and Array-like types |
|
# like Ranges and Enumerators |
|
# |
|
# (1..5).each do |number| |
|
# # blocks can be long |
|
# if number.even? |
|
# puts "Even" |
|
# else |
|
# puts "Odd" |
|
# end |
|
# end |
|
|
|
|
|
# Map is similar to each, except it returns a |
|
# mutated array. |
|
# |
|
# arr = (1..5).to_a |
|
# p "Initial array", arr |
|
# |
|
# arr = arr.each { |num| |
|
# num.even? ? "Even" : "Odd" |
|
# } |
|
# p "Each leaves the array unchanged", arr |
|
# |
|
# arr = arr.map { |num| |
|
# num.even? ? "Even" : "Odd" |
|
# } |
|
# p "Map changes the array", arr |
|
|
|
|
|
# select (aka find_all) filters the array |
|
# |
|
# arr = (1..5).to_a |
|
# arr = arr.select { |num| num.even? } |
|
# p arr |
|
|
|
|
|
# If you only remember how to use each, everything |
|
# else can be implemented with it. |
|
# It's just more code. |
|
# |
|
# select-ing with each |
|
# |
|
# arr = (1..5).to_a |
|
# output = [] |
|
# arr.each do |num| |
|
# output << num if num.even? |
|
# end |
|
# p output |
|
# |
|
# Someday you'll recognize that this pattern |
|
# can be refactored with select, but this is ok. |
|
|
|
# Opposite of select is reject |
|
# arr = (1..5).to_a |
|
# arr = arr.reject do |num| |
|
# num.even? |
|
# end |
|
# p arr |
|
|
|
|
|
# Add a ! to map, reject, select, etc. |
|
# to mutate the variable in-place |
|
# |
|
# arr = (1..5).to_a |
|
# arr.reject! do |num| |
|
# num.even? |
|
# end |
|
# p arr |
|
|
|
|
|
# Writing your own method that accepts a block |
|
# argument |
|
# |
|
# def puts_block |
|
# # make the block optional |
|
# if block_given? |
|
# # if the block is given, call it with some args |
|
# puts yield("Wyncode", "Hello", "Wyncode") |
|
# end |
|
# end |
|
# |
|
# block is optional |
|
# puts_block |
|
# |
|
# block with args |
|
# puts_block do |name| |
|
# "Hello #{name}!" |
|
# end |
|
# |
|
# There's no such thing as an "invalid number of |
|
# arguments" error with blocks. Whatever doesn't fall |
|
# into the "bucket" (|name|) is ignored. |
|
|
|
|
|
# Review: refactoring with a basic method |
|
# |
|
# puts "*"*50 |
|
# puts "Game intro" |
|
# puts "*"*50 |
|
|
|
# puts "*"*50 |
|
# puts "Act 1" |
|
# puts "*"*50 |
|
|
|
# puts "*"*50 |
|
# puts "Game Ending" |
|
# puts "*"*50 |
|
|
|
# pattern: |
|
# puts "*"*50 |
|
# puts ??? |
|
# puts "*"*50 |
|
# |
|
# some data goes in the ??? hole in the template |
|
# |
|
# def display_scene(text) |
|
# puts "*"*50 |
|
# puts text |
|
# puts "*"*50 |
|
# end |
|
# |
|
# Now my code is much shorter. |
|
# |
|
# display_scene("Game intro") |
|
# display_scene("Act 1") |
|
# display_scene("Game Ending") |
|
|
|
|
|
# Refactoring with a block method |
|
# |
|
# Recognize a different pattern: |
|
# puts "*"*50 |
|
# ??? |
|
# puts "*"*50 |
|
# |
|
# An entire line of code goes in the ??? hole in |
|
# the template. When I want to treat a line (or lines) |
|
# of code as data, I use a block. |
|
# |
|
# def display_scene |
|
# puts "*"*50 |
|
# yield |
|
# puts "*"*50 |
|
# end |
|
# |
|
# My code is shorter, but still longer than |
|
# the refactoring above. |
|
# |
|
# display_scene { puts "Game intro" } |
|
# display_scene { puts "Act 1" } |
|
# display_scene { puts "Game Ending" } |
|
|
|
# But I can do things with a block that I |
|
# can't do with a String. I can shove multiple lines |
|
# of code into the hole. |
|
# |
|
# display_scene { |
|
# puts "Intro" |
|
# puts "This is the intro to my game" |
|
# puts "Options: 1, 2, 3" |
|
# answer = gets |
|
# puts answer.nil? ? "Nothing selected" : answer.chomp |
|
# } |
|
|
|
# Using modules to organize your methods |
|
# |
|
# require "./alphabet_testers.rb" |
|
# puts AlphabetTesters.a?("A") |
|
# puts AlphabetTesters::PI |
|
# AlphabetTesters.print("hi") |
|
|
|
# include AlphabetTesters |
|
# puts a?("A") |
|
# print("hi") # print is broken |
|
|
|
|
|
# default and "rest" arguments |
|
# |
|
# def add_two(number = 0, *rest) |
|
# if rest.size > 0 |
|
# puts "Seriously? #{rest}" |
|
# end |
|
# number + 2 |
|
# end |
|
|
|
# puts add_two(1) |
|
# puts add_two(1.5) |
|
# |
|
# # test passes b/c of default args |
|
# puts add_two |
|
# |
|
# # test passes b/c of rest args |
|
# puts add_two(1,2,3,4,5,6,7,8,9) |
|
|
|
|
|
# examples of standard library methods that |
|
# use variable length arguments |
|
# |
|
# refactoring of: |
|
# puts 1 |
|
# puts "Hello" |
|
# puts "World" |
|
# puts "*" * 20 |
|
# puts 1, "Hello", "World", "*" * 20 |
|
|
|
# arr = [] |
|
# refactoring of: |
|
# arr.push 1 |
|
# arr.push 2 |
|
# arr.push 3 |
|
# arr.push 4 |
|
# arr.push(1, 2, 3, 4) |
|
# p arr |
|
|
|
|
|
# This methods supports any number of args |
|
# |
|
# def rest_method(*rest) |
|
# rest.each do |arg| |
|
# # do something with arg |
|
# end |
|
# end |
|
# |
|
# rest_method |
|
# rest_method(1) |
|
# rest_method(1,2,3,4,5) |
|
|
|
|
|
#### |
|
## Classes & OOP |
|
#### |
|
|
|
# class Table |
|
# attr_accessor :num_legs |
|
|
|
# # attr_accessor is short for these 2 lines |
|
# # attr_reader :num_legs |
|
# # attr_writer :num_legs |
|
|
|
# # attr_reader is short for these 3 lines |
|
# # def num_legs |
|
# # @num_legs |
|
# # end |
|
|
|
# # attr_writer is short for these 3 lines |
|
# # def num_legs=(value) |
|
# # @num_legs = value |
|
# # end |
|
|
|
# # promises to run after you call Table.new |
|
# def initialize(num_legs) |
|
# # initialize your instance variables here |
|
# @tabletop = [] |
|
# @num_legs = num_legs |
|
# end |
|
|
|
# # instance method |
|
# def put_on(something) |
|
# @tabletop << something |
|
# end |
|
|
|
# # instance method |
|
# def look_at |
|
# @tabletop |
|
# end |
|
|
|
# # class method |
|
# def self.has_legs? |
|
# true |
|
# end |
|
# end |
|
|
|
# # arguments passed to new are forwarded to |
|
# # initialize |
|
# t = Table.new 4 |
|
# p t.num_legs |
|
|
|
# # use an instance method on an instance of the Table class |
|
# t.put_on 1 |
|
# p t.look_at |
|
|
|
# # the instance method updates an instance variable |
|
# # |
|
# # the table instance t remembers the previous value of |
|
# # the instance variable @num_legs (aka it's "state") |
|
# t.put_on 2 |
|
# p t.look_at |
|
|
|
# # The table's instance variables can be changed. |
|
# p t.num_legs |
|
# t.num_legs = 3 |
|
# p t.num_legs |
|
|
|
# # Class-scoped variables are accessed from the Table Class, |
|
# # not a particular table instance |
|
# p Table.has_legs? |
|
|
|
#### |
|
## Inheritance |
|
#### |
|
|
|
# # an empty class... |
|
# class Car |
|
# end |
|
|
|
# # ... is still a thing (a Class) |
|
# p Car.class |
|
# # ... that has methods |
|
# p Car.methods.sort |
|
|
|
# # an instance of an empty class |
|
# my_car = Car.new |
|
# # ... is also a thing (in this case, a Car) |
|
# p my_car.class |
|
# # ... with methods |
|
# p my_car.methods.sort |
|
|
|
# # If the class is empty, where do these methods come from? |
|
# # These methods are "inherited" from the class's "ancestors" |
|
# p Car.ancestors |
|
|
|
# # We can define our own class hierarchy |
|
# class Vehicle |
|
# attr_accessor :engine, :tires |
|
# end |
|
|
|
# # A Car "is a" type of Vehicle |
|
# class Car < Vehicle |
|
# end |
|
|
|
# # A Motorcycle "is a" type of Vehicle |
|
# class Motorcycle < Vehicle |
|
# end |
|
|
|
# # The < syntax adds Vehicle to each "subclasses" |
|
# # list of ancestors. |
|
# p Car.ancestors |
|
# p Motorcycle.ancestors |
|
|
|
# # superclass returns the immediate ancestor (after self) |
|
# p Car.superclass |
|
# p Motorcycle.superclass |
|
|
|
# # attributes in the ancestors are "inherited" by each |
|
# # subclass |
|
# car = Car.new |
|
# # a car "has an" engine |
|
# p car.engine |
|
|
|
# mc = Motorcycle.new |
|
# # a motorcycle also "has an" engine |
|
# p mc.engine |
|
|
|
# # How to modules apply to class inheritance? |
|
|
|
# # modules can be used to describe "cross-cutting" features |
|
# # lots of things that aren't related to each other can talk: |
|
# # people, robots, toys, that dog on YouTube, etc. |
|
# # a person "is not a" robot |
|
# # a toy "is not a" person |
|
# # etc. |
|
# module Talkative |
|
# def speak |
|
# puts "Hello world!" |
|
# end |
|
# end |
|
|
|
# # to apply this "feature" module to a class, include it |
|
# # the module is being used as a "mixin" |
|
# class Kitt < Car |
|
# include Talkative |
|
# end |
|
|
|
# # The Kitt car can talk, even though it didn't inherit that |
|
# # ability from Car |
|
# kitt = Kitt.new |
|
# kitt.speak |
|
|
|
# # Including a module "mixin" adds it to the ancestor list |
|
# # after self, but before the superclass |
|
# p Kitt.ancestors |
|
|
|
# # But Ruby doesn't change the superclass |
|
# p Kitt.superclass |
|
|
|
#### |
|
## Object-oriented thinking |
|
#### |
|
|
|
# class Table |
|
# attr_accessor :tabletop |
|
|
|
# def initialize |
|
# @tabletop = [] |
|
# end |
|
|
|
# # How should "add" work in the context of a Table? |
|
# def add(item) |
|
# @tabletop.push(item) |
|
# end |
|
# end |
|
|
|
# # I'm not worried about |
|
# # addition in the abstract |
|
# # String or Array addition |
|
# # I only need to define addition in the context of a Table |
|
# # I've "oriented" the add method around an "object". |
|
# table = Table.new |
|
# table.add(nil) |
|
# table.add({}) |
|
# table.add([]) |
|
# table.add(Table.new) |
|
# p table.tabletop |
|
|
|
#### |
|
## class variables |
|
#### |
|
|
|
# class Table |
|
# # instance variable accessor |
|
# attr_reader :id |
|
|
|
# # there's no class_attr_accessor in Ruby |
|
# # but you can initialize it like this |
|
# @@next_table_id = 1 |
|
|
|
# # instance methods can access class variables |
|
# def initialize |
|
# # stamp this table with a serial number |
|
# @id = @@next_table_id |
|
# # and increment the number so the next one is unique |
|
# @@next_table_id += 1 |
|
# end |
|
|
|
# # class scoped getter method for a class attribute |
|
# def self.next_table_id |
|
# @@next_table_id |
|
# end |
|
|
|
# # instance-scoped getter method for a class attribute |
|
# def preview_next_id |
|
# @@next_table_id |
|
# end |
|
|
|
# # instance-scoped setter method for a class attribute |
|
# def change_next_id(next_id) |
|
# @@next_table_id = next_id |
|
# end |
|
# end |
|
|
|
# # class vars (@@next_table_id) are private by default |
|
# # you need a getter method to access them |
|
# p Table.next_table_id |
|
|
|
# t = Table.new |
|
# p t.id |
|
|
|
# # A class method is not available from an instance |
|
# # p t.next_table_id |
|
|
|
# # But class variables are accessible from instance methods |
|
# p t.preview_next_id |
|
|
|
# # And class variables can by changed by instance methods |
|
# t.change_next_id(100) |
|
|
|
# # An instance of a table can communicate with the |
|
# # "Table factory", getting and changing data. |
|
# p Table.next_table_id |
|
|
|
# # The changed data can affect a table that hasn't been |
|
# # created yet. |
|
# t = Table.new |
|
# p t.id |
|
|
|
# # The class variable implements a table unique id generator |
|
# # every new table gets a new, unique id (counting from 1) |
|
# Table.new |
|
# Table.new |
|
# Table.new |
|
# t2 = Table.new |
|
# p t2.id |
|
|
|
#### |
|
## Don't use class variables if you don't have to |
|
#### |
|
|
|
# class variable tip calculator |
|
# Ruby is telling you not to do this by making you work |
|
# harder. |
|
# class TipCalculator |
|
# @@tip_amount = 0 |
|
# @@tip_percent = 0 |
|
# @@bill = 0 |
|
|
|
# def self.tip_amount |
|
# @@tip_amount |
|
# end |
|
|
|
# def self.tip_percent |
|
# @@tip_percent |
|
# end |
|
|
|
# def self.bill |
|
# @@bill |
|
# end |
|
# end |
|
|
|
# # Don't treat the TipCalculator factory as |
|
# # a TipCalculator instance |
|
# p TipCalculator.tip_amount |
|
# p TipCalculator.tip_percent |
|
# p TipCalculator.bill |
|
|
|
# # correct object-oriented tip calculator |
|
# class TipCalculator |
|
# # so much easier |
|
# attr_accessor :tip_amount, :tip_percent, :bill |
|
# end |
|
|
|
# # even if there will only ever be one TipCalculator in your |
|
# # program, go ahead and define a "factory" and create that |
|
# # one |
|
# t = TipCalculator.new |
|
# p t.tip_amount |
|
# p t.tip_percent |
|
# p t.bill |
|
|
|
#### |
|
## public, private, protected methods |
|
#### |
|
|
|
# class Table |
|
# @@factory_secret = "a factory secret" |
|
|
|
# def initialize |
|
# @table_secret = "a table secret" |
|
# end |
|
|
|
# # this is available on every table |
|
# def a_public_method |
|
# # a private method can only be called from within |
|
# # the class |
|
# a_private_method |
|
# end |
|
|
|
# # a table can call a protected method of another table |
|
# def send_message(table) |
|
# table.a_protected_method |
|
# end |
|
|
|
# protected |
|
# def a_protected_method |
|
# puts "a protected method" |
|
# end |
|
|
|
# private |
|
# def a_private_method |
|
# @table_secret |
|
# end |
|
# end |
|
|
|
# t = Table.new |
|
# methods are public by default |
|
# that means if you have an instance, you can call |
|
# the method |
|
#p t.a_public_method |
|
|
|
# private methods cannot be called from "the outside" |
|
# p t.a_private_method |
|
|
|
# The initialize method is private by default |
|
# p t.initialize |
|
|
|
# Privacy isn't stricly enforced. |
|
# You can get around it using the send method |
|
# p t.send :a_private_method |
|
# p t.send :initialize |
|
|
|
# t = Table.new |
|
# a protected method can't be called from the "outside" |
|
#t.a_protected_method |
|
|
|
# Tables can call each other's protected method. |
|
# Think of "protected" as "family secrets". |
|
# t2 = Table.new |
|
# t2.send_message(t) |
|
|
|
#### |
|
## Why private methods? |
|
#### |
|
|
|
# class BankTransfer |
|
# def transfer |
|
# # break up the transfer into a |
|
# # withdrawal followed-by a deposit |
|
# # for better readability |
|
# withdraw |
|
# deposit |
|
# end |
|
|
|
# private |
|
# def withdraw |
|
# end |
|
# def deposit |
|
# end |
|
# end |
|
|
|
# transfer = BankTransfer.new |
|
|
|
# # A transfer is a "transaction". Withdraw & Deposit |
|
# # must happen together or not at all. |
|
# transfer.transfer |
|
|
|
|
|
# Don't allow the withdraw and deposit methods |
|
# to be called individiually. You created them for |
|
# yourself (refactoring), not to add new features to |
|
# the object. |
|
# transfer.deposit |
|
|
|
#### |
|
## inheritance and overriding |
|
#### |
|
|
|
# class Parent |
|
# def whoami |
|
# puts "I'm a parent" |
|
# end |
|
# end |
|
|
|
# class Child < Parent |
|
# # child overrides parent's whoami |
|
# def whoami |
|
# puts "I'm a child" |
|
# end |
|
# end |
|
|
|
# c = Child.new |
|
# # use the override method |
|
# c.whoami |
|
|
|
# # method lookup goes in ancestor list order |
|
# # if a method is not defined in the first |
|
# # ancestor, check the second, and so on. |
|
# p Child.ancestors |
|
|
|
#### |
|
## using super, embrace-and-extend |
|
#### |
|
|
|
# class Person |
|
# def speak |
|
# "I'm a person" |
|
# end |
|
# end |
|
|
|
# class Parent < Person |
|
# end |
|
|
|
# class Child < Parent |
|
# def speak |
|
# # super is a call to speak one level up |
|
# # the chain |
|
# # |
|
# # call Parent's speak method, then do more |
|
# # |
|
# # the parent has some good ideas, but child |
|
# # wants to do more |
|
# super + " who is a child." |
|
# end |
|
# end |
|
|
|
# # parent inherits speak method from person |
|
# p = Parent.new |
|
# puts p.speak |
|
|
|
# # child embrace-and-extends the speak method |
|
# c = Child.new |
|
# puts c.speak |
|
|
|
#### |
|
## not so super |
|
#### |
|
|
|
# class Parent |
|
# def speak(arg) |
|
# puts arg |
|
# end |
|
# end |
|
|
|
# class Child < Parent |
|
# def speak_gibberish |
|
# # You don't need to use super if this |
|
# # method name doesn't match the parent |
|
# # method name. |
|
# speak "goo goo" |
|
# end |
|
# end |
|
|
|
# c = Child.new |
|
# c.speak_gibberish |
|
|
|
#### |
|
## Automatic argument forwarding |
|
#### |
|
|
|
# class Parent |
|
# def speak(arg) |
|
# puts arg |
|
# end |
|
# end |
|
|
|
# class Child < Parent |
|
# def speak(arg) |
|
# puts "YOLO" |
|
# # arguments list looks empty, but arg is |
|
# # automatically forwarded |
|
# super |
|
# end |
|
# end |
|
|
|
# c = Child.new |
|
# c.speak("Hello") |
|
|
|
#### |
|
## stop automatic forwarding |
|
#### |
|
|
|
# class Parent |
|
# def speak |
|
# puts "No!" |
|
# end |
|
# end |
|
|
|
# class Child < Parent |
|
# def speak(arg) |
|
# puts "YOLO" |
|
# # unless you explicitly pass no arguments |
|
# # arg will be automatically forwarded |
|
# super() |
|
# end |
|
# end |
|
|
|
# c = Child.new |
|
# c.speak("hi") |
|
|
|
#### |
|
## monkeypatching |
|
#### |
|
|
|
# This doesn't override the String class, |
|
# it adds to it. Ruby Classes are "open". |
|
# class String |
|
# def monkey? |
|
# self.eql? "monkey" |
|
# end |
|
|
|
# # This is bad patching |
|
# def +(other_string) |
|
# self.to_i + other_string.to_i |
|
# end |
|
# end |
|
|
|
# # You can add silly/useful things. |
|
# p "monkey".monkey? |
|
# p "not a monkey".monkey? |
|
|
|
# # Monkeypatching doesn't break old things |
|
# # p "".size |
|
|
|
# # unless you break them on purpose |
|
# p "1" + "1" |
|
# p "abc" + "def" |
|
|
|
#### |
|
## raise errors |
|
#### |
|
|
|
# class Table |
|
# attr_accessor :num_legs |
|
|
|
# def initialize(num_legs) |
|
# if num_legs > 0 |
|
# @num_legs = num_legs |
|
# else |
|
# # custom error prevents invalid table |
|
# # from being created |
|
# raise "Invalid number of legs" |
|
# end |
|
# end |
|
# end |
|
|
|
# table = Table.new(1) |
|
|
|
# prevent an invalid table from ever leaving |
|
# the "Table factory" |
|
#table = Table.new(-1) |
|
|
|
|
|
#### |
|
## custom errors and rescuing |
|
#### |
|
|
|
# def add_two(number) |
|
# if not number.respond_to? :+ |
|
# # raise a particular type of error |
|
# raise ArgumentError, "Invalid argument" |
|
# elsif number == 0 |
|
# raise "I just don't like the number zero!" |
|
# end |
|
# number + 2 |
|
# end |
|
|
|
# begin |
|
# add_two(0) |
|
# this line is ignored |
|
# rescue ArgumentError => e |
|
# # rescue only ArgumentErrors |
|
# puts "You: #{e.message}. Me: Sorry! My bad." |
|
# rescue => e |
|
# # rescue all StandardErrors |
|
# puts "You: #{e.message}. Me: What?!" |
|
# p e.backtrace |
|
# end |
|
|
|
# puts "Rescued from the crash!" |
|
|
|
#### |
|
## where to rescue |
|
#### |
|
|
|
# def a |
|
# b |
|
# end |
|
|
|
# def b |
|
# # you can rescue at any line in the |
|
# # backtrace |
|
# c rescue nil |
|
# end |
|
|
|
# def c |
|
# d |
|
# end |
|
|
|
# def d |
|
# raise "Boom!" |
|
# end |
|
|
|
# begin |
|
# a |
|
# puts "No errors" |
|
# rescue => e |
|
# puts e.backtrace |
|
# end |
|
# puts "Recovered" |
|
|
|
#### |
|
## errors that aren't rescued by default |
|
#### |
|
|
|
# begin |
|
# exit |
|
# rescue # does't rescue SystemExit error |
|
# # program is saved by default |
|
# puts "No exit for you!" |
|
# ensure |
|
# # program is doomed by default |
|
# puts "This will run anyway, right before the program exits." |
|
# end |
|
# puts "This will not run after exit" |
|
|
|
#### |
|
## Ruby Web Clients |
|
#### |
|
|
|
# require 'httparty' |
|
# # require 'json' # json is required by httparty already |
|
|
|
# # make an HTTP GET request |
|
# response = HTTParty.get('https://api.github.com/users/eddroid') |
|
|
|
# # details of the HTTP response stored in the Ruby object |
|
# puts response.body, response.code, response.message, |
|
# response.headers.inspect |
|
|
|
# # The response is a custom, namespaced Class introduced by HTTParty |
|
# puts response.class, response.headers.class |
|
|
|
# # Even though this is an HTTParty::Response::Headers object, it |
|
# # looks like a Hash. |
|
# puts response.headers |
|
# p response.headers |
|
|
|
# # and "quacks" like a Hash |
|
# puts response.headers["content-type"] |
|
# # So the body of this response is JSON. |
|
|
|
# # But Ruby says the body is a String. A String of JSON. |
|
# puts response.body.class |
|
# puts response.body |
|
|
|
# # To convert a JSON String into a Hash, use the JSON class |
|
# hash_body = JSON.parse response.body |
|
|
|
# puts "My GitHub id is #{hash_body['id']}" |
|
# puts "My name is #{hash_body['name']}" |
|
|
|
#### |
|
## Sending data to an API |
|
#### |
|
|
|
# require 'httparty' |
|
|
|
# # To send data to a server, we first have to define some data (in a Hash) |
|
# data = {first_name: 'Ed', username: 'Edbot'} |
|
|
|
# # response = HTTParty.put('http://requestb.in/vggtewvg', body: data) |
|
# # p response.body |
|
|
|
# # If API wants JSON data, then tell the server we're sending JSON. |
|
# my_headers = {'Content-Type' => 'application/json'} |
|
|
|
# # ... then send those headers and a JSON-Stringified Hash |
|
# response = HTTParty.put('http://requestb.in/vggtewvg', |
|
# body: JSON.dump(data), headers: my_headers) |
|
# p response.body |
|
|
|
#### |
|
## web scraping |
|
#### |
|
|
|
# require 'httparty' |
|
# require 'nokogiri' |
|
|
|
# # Any page on the web is technically an API. |
|
# response = HTTParty.get('http://finance.yahoo.com/q?s=AAPL') |
|
|
|
# # The response type is text/html |
|
# # Makes sense... this content is meant for a browser |
|
# # But it's not very api-developer-friendly |
|
# p response.headers["content-type"] |
|
|
|
# # parse the HTML string into a document object |
|
# dom = Nokogiri::HTML(response.body) |
|
|
|
# # It's a Nokogiri::HTML::Document object |
|
# # Use this to search for documentation on how this works. |
|
# p dom.class |
|
|
|
# # We'll use xpath to scan this document |
|
# # You'll find CSS (which we'll learn about later) |
|
# # to be easier to use here. |
|
|
|
# # This is a list (Array-ish, a Nokogiri::XML::NodeSet) of |
|
# # every HTML element on the page |
|
# # "//*" is a line of xpath code, a separate programming language |
|
# p dom.xpath("//*").class, dom.xpath("//*").size |
|
|
|
# # A list (NodeSet) of every span element on the page |
|
# p dom.xpath("//span").class, dom.xpath("//span").size |
|
|
|
# # A list (NodeSet) of every span with id='yfs_l84_aapl' |
|
# # p.s. That's an el-84, not a one-84. |
|
# list_of_results = dom.xpath("//span[@id='yfs_l84_aapl']") |
|
|
|
# # The list contains only one item |
|
# p list_of_results.class, list_of_results.size |
|
|
|
# # Grab the 1st item |
|
# # This works because NodeSet works like an Array |
|
# # (even though it isn't). |
|
# result = list_of_results.first |
|
|
|
# # This is a Nokogiri::XML::Element. |
|
# # Lookup the documentation to see how this works. |
|
# p result.class |
|
|
|
# # The text (or content) of the element is the stock price. |
|
# p result.text, result.content |
|
|
|
# # Once you have the price in a String, |
|
# # you can do what you want with it. |
|
# puts "The current price of AAPL is $#{result.text}" |
|
|