Skip to content

Instantly share code, notes, and snippets.

@eddroid
Last active October 7, 2015 21:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save eddroid/f712afa13ae6b68da564 to your computer and use it in GitHub Desktop.
Save eddroid/f712afa13ae6b68da564 to your computer and use it in GitHub Desktop.
C6: scratchpad
module AlphabetTesters
# self methods
# activated on require
# must be called with namespace
# e.g. AlphabetTesters.a?
def self.a?(letter)
letter.downcase == "a"
end
def self.b?(letter)
letter.downcase == "b"
end
# Keep print up here in the self section
# to "namespace" it and prevent yourself from
# accidentally breaking commonly named methods.
def self.print(arg)
puts "this print is broken"
end
# regular methods
# activated on include
def a?(letter)
letter.downcase == "a"
end
# When included, this module will break Kernel.print
def print(arg)
AlphabetTesters.print(arg)
end
# Refer to module constants using the
# :: syntax.
# AlphabetTesters::PI
PI = 3.14
end
/*
comments
*/
// This is a single-line comment. Everything is ignored until the end of the line
// console.log("I'm in a JS file!");
var some_var;
/*
This is a multi-line comment.
I can keep typing words until
I reach the final star-slash.
*/
var some_other_var;
/*
control flow
*/
// var input;
// if (input === undefined) {
// console.log("It's not defined.");
// } else {
// console.log(input);
// }
// var input;
// if (input) {
// console.log(input);
// } else {
// console.log("It's not defined.");
// }
// there is no unless keyword
// var input;
// if (!input) {
// console.log("It's falsy");
// }
// c-style 1-line if/then (ternary statement)
// var test = false;
// var output = test ? "Passed the test" : "Failed the test";
// console.log(output);
// else [space!] if
// var value = 1;
// if (value === 0) {
// console.log("zero");
// } else if (value === 1) { // not elsif, elseif, elif
// console.log("one");
// } else {
// console.log("or something...");
// }
// switch/case statement
// var value = 0;
// switch (value) { // Ruby case
// case 0: // Ruby when
// console.log("zero");
// // break; fall-through
// case 1:
// console.log("zero or one when there's fall through");
// break;
// default:
// console.log("or something...");
// break;
// }
// while loops
// var x = 0;
// while (x < 10) {
// console.log(x);
// x++;
// }
// js doesn't have until
// until (x > 10)
// while (!(x > 10))
// infinite loop - don't do this
// while (true) {
// console.log("infinite loop");
// }
// infinite loop - don't do this
// while (true) {
// break;
// }
// console.log("broken free");
// Ruby next === JavaScript continue
// Ruby redo === doesn't exist in JS
// break example
// var a = 0;
// while (true) {
// console.log(a);
// if (a >= 10) {
// break;
// } else {
// a++;
// }
// }
// for loop
// for (var i = 0; i <= 10; i++) {
// console.log(i);
// }
// for loop over array
// var arr = ["a", "b", "c", "d"];
// for (var i = 0; i < arr.length; i++) {
// console.log(arr[i]);
// }
// for-loop with alternate expressions
// var arr = ["a", "b", "c", "d"];
// for (var i = 0, j = 0; i < arr.length; i += 2) {
// console.log(j);
// console.log(arr[i]);
// }
////
// Functions
////
// Similar to Ruby
// def function_name(arg1, arg2)
// puts "a function"
// puts arg1, arg2
// end
// function function_name(arg1, arg2) {
// console.log("a function");
// console.log(arg1, arg2);
// }
// function_name("Hi", "Hello");
// A named "anonymous function"
// A variable set to a "block of code"
// var another_function = function(arg) {
// console.log("anonymous function");
// console.log(arg);
// }
// This is hard to do in Ruby because of optional parentheses
// "".size # This is a Ruby method call
// "".size() # This is also a Ruby method call
//
// To get the "size" method object in Ruby, I need to do this
// size_method = "".method(:size)
//
// This is awkward because Ruby always assumes method invocation.
// This is much easier in JS.
// a JS function call (including parentheses)
// another_function("Hi");
// a JS function object (without parentheses)
// console.log(another_function);
// console.log(typeof another_function);
// console.log(function_name);
// console.log(typeof function_name);
// another_function = 1;
// function_name = 1;
// typeof is special
// typeof is not a function
// console.log(typeof typeof) //error
// explicit returns
// function one() {
// return 1;
// }
// console.log(one());
// unlimited (rest) arguments
// function addAll() {
// var sum = 0;
// for (var i = 0; i < arguments.length; i++) {
// sum += arguments[i];
// }
// return sum;
// }
// This function won't break with invalid number or arg errors
// function a() {}
// a(1,2,3,4,5);
////
// default arguments
////
// In Ruby
// def method_with_defaults(a = 1)
// puts a
// end
// function funcWithDefaults(a, b) {
// console.log(a, b);
// if (typeof(a) === "undefined") {
// a = 0;
// }
// if (typeof(b) === "undefined") {
// b = 0;
// }
// return a + b;
// }
// console.log(funcWithDefaults(1, 1));
// console.log(funcWithDefaults(-1, 1));
// console.log(funcWithDefaults(0));
// console.log(funcWithDefaults(0,1,2,3,4,5));
////
// JavaScript "blocks"
////
// function evenOrOdd(num) {
// if (num % 2 === 0) {
// return "even";
// } else {
// return "odd";
// }
// }
function map(arr, func) {
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push( func(arr[i]) );
}
return result;
}
// console.log(map([1,2,3], evenOrOdd));
console.log(map([1,2,3], function(num) {
if (num % 2 === 0) {
return "even";
} else {
return "odd";
}
}));
// in Ruby, this is
// [1,2,3].map { |num|
// if num.even?
// return "even"
// else
// return "odd"
// end
// }
////
// Sameness
////
<!doctype html>
<html>
<head>
<title>JavaScript</title>
<!-- The type attribute is optional in HTML5 -->
<!-- The type is semi-required in HTML4 -->
<!-- script>
console.log("This is embedded JS. Don't use this.");
</script -->
<script src="hellojs.js"></script>
</head>
<body>
</body>
</html>
####
puts "Cheetahs Sometimes Do Wyn"
####
CHEETAH_GIRLS = [
"Raven-Symone",
"Adrienne Bailon",
"Sabrina Bryan",
"Kiely Williams"
]
auditions = [
"Raven-Symone",
"Usher",
"Wiz Khalifa",
"Adrienne Bailon",
"Hulk Hogan",
"Sabrina Bryan",
"Diego Lugo",
"Kiely Williams",
"Justin Timberlake"
]
panel = []
# Translate the question text from English into Ruby
auditions.each do |auditioner|
panel.push(auditioner) if CHEETAH_GIRLS.include? auditioner
end
p panel
# Use set intersection magic
panel2 = CHEETAH_GIRLS & auditions
p panel2
####
puts "The Cats in the Hats"
####
def is_divisible_by?(top, bottom)
(top % bottom).zero?
end
# Initialize a 100 item array with trues
cats = Array.new(100, true)
p cats
# count from 0 to 99
100.times do |pass|
pass = pass + 1 # ... uh, I mean 1 to 100
cats.map!.with_index do |has_hat, index|
# start counting cats at 1 instead of 0
index = index + 1
is_divisible_by?(index, pass) ? !has_hat : has_hat
end
#p cats
end
p cats
####
puts "stock picker"
####
stocks = [10,4,5,10,1]
# iterate over every pair of buy and sell days
stocks.size.times do |buy_day|
stocks.size.times do |sell_day|
# if sell_day comes before buy_day
# skip it, we haven't invented time travel yet
# end
# if this is the best profit ever seen
# save it
# else
# skip it
# end
p "#{buy_day}, #{sell_day}"
end
end
####
puts "Object-oriented tip calculator"
####
class TipCalculator
attr_accessor :bill, :tip_percent, :num_people
def grand_total
@bill + (@bill * (@tip_percent/100.0))
end
def bill_split
grand_total / @num_people
end
end
def test
tip_calc = TipCalculator.new
tip_calc.bill = 100
p tip_calc.bill == 100
tip_calc.tip_percent = 20
p tip_calc.tip_percent == 20
tip_calc.num_people = 4
p tip_calc.num_people == 4
p tip_calc.grand_total == (100 * (1 + 0.2))
p tip_calc.bill_split == (120/4)
puts "The bill split is $#{tip_calc.bill_split}"
end
test
####
## Putting on the Ritz
####
puts "Putting on the Ritz"
class MyClass
def to_s
"My own custom to_s method"
end
def inspect
"My own custom inspect method"
end
end
mc = MyClass.new
puts mc
p mc
####
puts "Quad Core & Quad v2"
####
class Quadrilateral
# a quadrilateral is a 4-sided shape
# so all it knows about is it's 4 sides
def initialize(s1, s2, s3, s4)
@side1 = s1
@side2 = s2
@side3 = s3
@side4 = s4
end
# 4 sides is all you need to calculate a perimeter
def perimeter
@side1 + @side2 + @side3 + @side4
end
end
# a cross-cutting concern that applies
# to unrelated objects
module Equilateral
def side_length
# this concern, when included, will inherit
# access to instance vars
@side1
end
end
class Rectangle < Quadrilateral
# All you need to define a Rectable is a
# length & width
def initialize(length, width)
super(length, width, length, width)
end
# areas only make sense on Rect & Squa
def area
@side1 * @side2
end
end
class Square < Rectangle
include Equilateral
# squares only need 1 length
def initialize(length)
# pass the length as the width and height
super(length, length)
end
end
class Trapezoid < Quadrilateral
# inherits initialize, perimeter,
# and sides1-4
end
class Rhombus < Trapezoid
include Equilateral
def initialize(length)
super(length, length, length, length)
end
end
def test
squa = Square.new(1)
puts squa.is_a? Quadrilateral
puts squa.is_a? Rectangle
puts squa.perimeter == 4
puts squa.area == 1
puts squa.side_length == 1
rect = Rectangle.new(1,2)
puts rect.is_a? Quadrilateral
puts not(rect.is_a? Trapezoid)
puts rect.perimeter == 6
puts rect.area == 2
rhom = Rhombus.new(1)
puts rhom.is_a? Quadrilateral
puts rhom.is_a? Trapezoid
puts rhom.side_length == 1
trap = Trapezoid.new(1,2,3,4)
puts trap.is_a? Quadrilateral
end
test
####
puts "Caesar Cipher"
####
def caesar_cipher(str, shift_factor)
# characters that don't shift
exclusions = "! ".codepoints
codepoints = str.codepoints
new_codepoints = []
codepoints.each do |cp|
if exclusions.include? cp
new_codepoints << cp
else
new_cp = cp + shift_factor
if 65 <= cp and cp <= 90
# uppercase
new_cp -= 26 if new_cp > 90
else
# lowercase
new_cp -= 26 if new_cp > 122
end
new_codepoints << new_cp
end
end
new_str = ""
new_codepoints.each do |cp|
new_str << cp.chr
end
# p new_str
new_str
end
def test
puts caesar_cipher("What a string!", 5) == "Bmfy f xywnsl!"
puts caesar_cipher("Z", 10) == "J"
puts caesar_cipher("z", 10) == "j"
end
test

Model Callbacks

creating

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_create
  • around_create
  • after_create
  • after_save

updating

  • before_validation
  • after_validation
  • before_save
  • around_save
  • before_update
  • around_update
  • after_update
  • after_save

destroying

  • before_destroy
  • around_destroy
  • after_destroy

everywhere

  • after_initialize
  • after_find

Examples of:

  • after_save with a touch
  • before_save with a if
class Project < ActiveRecord:Base
  has_many :tasks
end

class Task < ActiveRecord:Base
  # touch
  belongs_to :project, touch: true

  # touch: long version
  # after_save :touch_project
  # after_destroy :touch_project

  # def touch_project
  #   self.project.touch
  # end

  # before save with if
  belongs_to :owner, class_name: 'User'
  before_save :check_owner,
    if: Proc.new { |task| task.state.started? }

  def check_owner
    # before refactored to use if
    #if self.state.started?
    not self.owner.nil? # false will halt the callback chain, blocking the save
    # else
    #   true
    # end
  end
end

Caching + Model Callbacks = Counter Cache

class Order < ActiveRecord:Base
  belongs_to :customer, counter_cache: true

  # counter_cache long version
  #
  # after_save :add_order
  # def add_order
  #   self.customer.orders_count.increment!
  # end
  #
  # after_destroy :remove_order
  # def remove_order
  #   self.customer.orders_count.decrement!
  # end
end

class Customer < ActiveRecord:Base
  # assume you have an attribute @orders_count
  has_many :orders
end
# in the view, when you do this
<div><%= @customer.orders.size %></div>

# you no longer see this in the logs
# SELECT COUNT(*) FROM orders WHERE customer_id = ?

# because counter_cache guarantees that this is strue
@customer.orders_count === @customer.orders.count

But you'll need to add a new column in the migration.

rails g migration AddOrdersCountToCustomer orders_count:integer

Model Callbacks for Cache Expiration

<%= cache('all_products') do %>
  <%= @products %>
<% end %>

Expire the all_products cache whenever (after) any product changes (saves or destroys).

class Product < ActiveRecord:Base
  after_save :expire_products_cache
  after_destroy :expire_products_cache

  def expire_products_cache
    expire_fragement('all_products')
  end
end

Pipelines

ERB (Dynamic Asset) pipeline

index.html.erb => |ERB| => index.html

Static Asset pipeline

minification

user.css => |minification| => user.min.css

concatenation

users.css + products.css => |concatenation| => application.css

zipping

application.min.css => |gzip| => application.min.css.gz

full asset pipeline

users.scss + products.scss => |sass| => user.css + products.css => |concatenation| => application.css => |minification| => application.min.css => |gzip| => application.min.css.gz

# 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}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment