Skip to content

Instantly share code, notes, and snippets.

@clockworkpc
Last active May 16, 2018 08:59
Show Gist options
  • Save clockworkpc/0e05fd1e623d10e0c599db041a3f873e to your computer and use it in GitHub Desktop.
Save clockworkpc/0e05fd1e623d10e0c599db041a3f873e to your computer and use it in GitHub Desktop.
112 Things I Have Learned about Ruby Programming

1. This course is good gauge of one's progress in learning to code. I skimmed through most of the course, even the advanced topics, with ease.

2. #print does not include a carriage return.

3. Parallel variable assignment.

a, b, c = 10, 20, 30

4. Ruby used to have Fixnum and Bignum classes.

5. Boolean methods are called predicate methods.

6. #upto and #downto

5.downto(1) #=> 5,4,3,2,1
1.upto(5) #=> 1,2,3,4,5

7. #step

1.step(20, 5) { |n| puts n } #=> 1, 6, 11, 16

8. Multiline strings

words = <<HEREDOC
multi-
line
string
HEREDOC

9. Single quotes do not recognise escape characters or string interpolation

p 'hello\n #{world}' #=> hello\n #{world}

10. Capital letters have a smaller numeric value in Ruby

"A" < "a" #=> true

11. #concat, #prepend, and << in string concatenation are destructive

a = "hello"
a.concat(" world")
a #=> "hello world"
a << " today"
a #=> "hello world today"
"again".prepend("hello ")
a #=> "hello hello world today"

12. case methods

"hello world".capitalize
"hello world".upcase
"HELLO WORLD".downcase
"HeLlO wOrLd".swapcase

13. A non-existent string is nil, an empty string is not.

"Donald"[100, 4] #=> nil
"".empty? #=> true
"".nil? #=> false

14. 'false' and 'nil' are falsey, everything else is truthy.

15. a :symbol is essentially a light-weight string, instantiated without most of the String methods, which means it takes up less memory. Although in Ruby 2.5 there doesn't seem to be much difference between them.

"my_string".class.methods - :my_symbol.class.methods
### => [:try_convert, :new]

16. The ternary operator returns its findings. Still not confident using it..

p 1 == 1 ? true : false #=> true

17. Put optional parameters at the end of a method definition: they can't be skipeed try_convert

def make_phone_call(number, area_code=03)
  "#{area_code}-#{number}"
end

18. Case method: multiple options can be grouped on a single line

def rate_my_food(food)
  case food
  when "steak"
    "Delicious"
  when "Tacos", "Burritos", "Quesadillas"
    "Still pretty delicious"
  end
end

19. Case method: condition and return value can be returned on a single line

def calculate_school_grade(grade)
  case grade
  when 90..100 then "A"
  when 80..89 then "B"
  when 70..79 then "C"
  else
    "F"
  end
end

20. Double negation

!true #=> false
!!true #=> true

21. How to use 'unless' properly

password = 'dominoes'

unless password.include?('a')
  puts "It does not include the letter 'a'"
end

22. How to use 'until' properly

i = 0

until i > 9
  puts i
  i += 1
end

23. Inline modifiers

n = 5000
puts "Huge number" if n > 2500
puts "Huge number" unless n < 2500

24. Conditional operator ||=

greeting = "hello"
extraction = 100
letter = greeting[extraction]
letter ||= "Not found"
p letter
#=> "Not found"

25. Range (..) includes last item, (...) excludes

numbers = (90..150)
numbers.last
#=> 150
numbers = (90...150)
numbers.last
#=> 149

26. Ruby includes a few extra characters between A-Z and a-z

("A".."z").to_a.each { |character| character }
#=> ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "\`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

27. Shorthand syntax, but it only works on strings that do not include a whitespace character.

names = %w[Jack Jill John]

28. Array#new

x = Array.new(3)
#=> [nil, nil, nil]
y = Array.new(3, true)
#=> [true, true, true]
z = Array.new(3, [true, false])
#=> [[true, false], [true, false], [true, false]]

29. Array#fetch allows for a custom error message if no entry exists at a given index position

names = ["Tom", "Bob"]
names.fetch(100, "Custom error message")
#=> "Custom error message"

30. The difference between Array#empty? and Array#nil?

letters = ["a".."j"].to_a
letters[25].nil?
#=> true
empty_array = []
letters.empty?
#=> false
empty_array.empty?
#=> true

31. Array#shift and Array#unshift

ary = (1..5).to_a
ary.shift
#=> 1
ary
#=> [2, 3, 4, 5]
ary.shift(1)
#=> [2]
ary
#=> [3, 4, 5]
ary.unshift(20, 30, 40)
#=> [20, 30, 40, 3, 4, 5]

32. Spaceship operator returns nil if incomparable; between arrays, returns the first result of different elements, not total of elements.

5 <=> 5 #=> 0
5 <=> 10 #=> -1
5 <=> 3 #=> 1
5 <=> [1, 2, 3] #=> nil
[3, 4, 5] <=> [nil, 4, 5] #=> nil
[1, 2, 4] <=> [1, 2, 10] #=> -1

33. The main classes via #is_a?

1.is_a?(Integer) == (1.class == Integer)
1.1.is_a?(Float) == (1.1.class == Float)
[1,2,3].is_a?(Array) == ([1,2,3].class == Array)
true.is_a?(TrueClass) == (true.class == TrueClass)
false.is_a?(FalseClass) == (false.class == FalseClass)
nil.is_a?(NilClass) == (nil.class == NilClass)

34. #ri will come in handy one day when there is no more internet (and I have backed up the latest version of Ruby before everything goes dark)

ri.String
ri.String.length

35. #for is like #each but doesn't create a new scope for local variables

numbers = [3,5,7]

for n in numbers
  p n
end

n #=> 7

36. Array#each_with_index

arr = (1..100).to_a
total = []
arr.each_with_index { |element, index| total << element * index }

37. #map == #collect

numbers = [1,2,3,4,5]
numbers.map {|n| n ** 2} == numbers.collect {|n| n ** 2}

38. #next allows a program not to be interrupted by an erroneous element

numbers.each do |n|
  unless n.is_a?(Integer)
    next
  else
    puts n
  end
end

39. A neat trick for case-insensitive sorting of an array

words = %w[Shiny happy People holding Hands]
words.sort { |word1, word2| word1.casecmp(word2) }
#=> ["Hands", "happy", "holding", "People", "Shiny"]

40. Array#partition

foods = ["Steak", "Tofu", "Vegetables", "Steak Burger", "Tuna", "Kale"]
split = foods.partition { |food| food.include?("Steak") }
#=> [["Steak", "Steak Burger"], ["Tofu", "Vegetables", "Tuna", "Kale"]]
good_food, bad_food = split
good_food
#=> ["Steak", "Steak Burger"]
bad_food
#=> ["Tofu", "Vegetables", "Tuna", "Kale"]

41. String#split

"hello world".split == "hello world".split(" ")

42. String#each_char

str.each_char { |char| p char }
#=> simply iterates over characters in string

43. String#chars

str.split("") == str.chars
#=> Array of characters, including spaces

44. splat arguments

def sum(*numbers)
  sum = 0
  numbers.each { |n| sum += n }
  sum
end

p sum(3,4,5,6,7,8,9)

45. Array#find == Array#detect

lottery = (1..100).to_a.sample(6)
lottery.find { |n| n.odd? }
#=> first odd number
lottery.reverse.find { |n| n.odd? }

46. Array#inject == Array#reduce. In both block methods, each iteration hands over the return value of the previous iteration, unlike #each , which focuses exclusively on the value of the current iteration.

result_of_inject = [10, 20, 30, 40].inject(0) do |previous_value, current_value|
  puts "previous value: #{previous_value}"
  puts "current value: #{current_value}"
  previous_value + current_value
end
#=> 100

result_of_reduce = [10, 20, 30, 40].inject(0) do |previous_value, current_value|
  puts "previous value: #{previous_value}"
  puts "current value: #{current_value}"
  previous_value + current_value
end
#=> 100

result_of_inject == result_of_reduce
#=> true

47. Array#inject can be reduced to a single-line Proc

single_line_inject = [10, 20, 30, 40].inject(0) {|previous, current| previous + current}
#=> 100

single_line_inject_to_proc = [10, 20, 30, 40].inject(:+)
#=> 100

single_line_inject == single_line_inject_to_proc
#=> true

48. Array#zip

names = %w[Bo Moe Jon]
registrations = [true, false, false]

names.zip(registrations)
#=> [["Bo", true], ["Moe", false], ["Jon", false]]

49. Array#sample returns one element as a string, but if supplied any integer (even 1), it will return an array of strings.

flavours = %w[chocolate vanilla mint strawberry apple cinnamon]
flavours.sample
#=> "apple"

flavours.sample(1)
#=> ["mint"]

flavours.sample(2)
#=> ["apple", "mint"]

50. Array#multiply

[1,2,3] * 3
#=> [1, 2, 3, 1, 2, 3, 1, 2, 3]

51. Union of arrays

[1,2,3] | [3,4,5]  == [1,2,3].concat([3,4,5]).uniq
#=> true

52. Array intersection

[1, 1, 2, 3, 4, 5] & [1, 4, 5, 8, 9]
#=> [1, 4, 5]

53. Hash#fetch allows for a custom return value if key does not exist instead of 'nil'.

menu = {
  burger: 3.99,
  taco: 5.69,
  chips: 0.5
}

menu.fetch(:burger) == menu[:burger]
#=> true

menu[:pizza]
#=> nil

menu.fetch(:pizza, "custom error message")
#=> "custom error message"

54. Hash#length returns the number of main key-value pairs

shopping_list = {
  cheese: 1,
  tomatoes: 6,
  chicken: 1,
  lettuce: 1
}

vegan_shopping_list = {}

shopping_list.length
#=> 4

(vegan_shopping_list.length == 0) == vegan_shopping_list.empty?
#=> true

55. Hash#each returns key-value pairs, Hash#each_key and Hash#each_value iterate over the keys and values respectively.

salaries = {
  director: 100_000,
  producer: 200_000,
  ceo: 300_000
}


salaries == salaries.each { |key_value_pair| key_value_pair }
#=> true

roles = salaries.each_key { |role| role }
# :director
# :producter
# :ceo

salaries.each_value { |salary| salary }
# 100000
# 200000
# 300000

56. Hash#keys and Hash#values return array of keys and values respectively.

salaries = {
  director: 100_000,
  producer: 200_000,
  ceo: 300_000
}

salaries.keys
#=> [:director, :producer, :ceo]

salaries.values
#=> [100000, 200000, 300000]

57. Hash#new allows for a default value if key not found

fruit_prices = Hash.new("No price found")
fruit_prices[:unlisted_fruit]
#=> "No price found"

58. Hash#sort_by values returns a nested array of the key-value pairs in the alphanumeric order of the values, which can be converted back to a hash with Array#to_h.

vips = {
  director: "Thomas Jones",
  producer: "Michael Jackson",
  ceo: "Andrew Jackson"
}

vips.sort == vips.sort_by { |key, value| key }
#=> true

vips.sort_by {|key, value| value }
#=> [[:ceo, "Andrew Jackson"], [:producer, "Michael Jackson"], [:director, "Thomas Jones"]]

vips.sort_by {|key, value| value }.to_h
#=> => {:ceo=>"Andrew Jackson", :producter=>"Michael Jackson", :director=>"Thomas Jones"}

59. Hash#delete deletes the key-value pair and returns its value.

vips = {
  director: "Thomas Jones",
  producer: "Michael Jackson",
  ceo: "Andrew Jackson"
}

vips.delete(:ceo)
#=> "Andrew Jackson"

vips
#=> {:director=>"Thomas Jones", :producer=>"Michael Jackson"}

60. Hash#select and Hash#reject return a Hash of entries: select returns those that satisfy its condition; reject, that fail to satisfy it.

salaries = {
  director: 100_000,
  producer: 200_000,
  ceo: 300_000
}

salaries.select { |key, value| value > 150_000 }
#=> {:producer=>200000, :ceo=>300000}

salaries.reject { |key, value| value < 150_000 }
#=> {:director=>100000}

61. Remember String#to_sym when looking for keys

salaries = {
  director: 100_000,
  producer: 200_000,
  ceo: 300_000
}

salaries.include?("director")
#=> false

salaries.include?(:director)
#=> true

salaries.include?("director".to_sym)
#=> true

62. Do not include 'return' when passing a block to 'yield'

def pass_control
  p "inside method"
  yield
  p "back in method"
end

pass_control {return "in the block now"}

# "inside method"
# LocalJumpError: unexpected return

pass_control {p "in the block now"}

# "inside method"
# "in the block now"
# "back in method"
# => "back in method"

63. Proc in Ruby

a = (1..5).to_a
b = (6..10).to_a
c = (11..15).to_a

cubes = Proc.new { |n| n ** 3 }

a.map { |n| n ** 3 } == a.map(&cubes)
#=> true

b.map { |n| n ** 3 } == b.map(&cubes)
#=> true

c.map { |n| n ** 3 } == c.map(&cubes)
#=> true

a_cubes, b_cubes, c_cubes = [a, b, c].map { |array| array.map(&cubes)}
#=> [[1, 8, 27, 64, 125], [216, 343, 512, 729, 1000], [1331, 1728, 2197, 2744, 3375]]

a_cubes
# => [1, 8, 27, 64, 125]

b_cubes
# => [216, 343, 512, 729, 1000]

c_cubes
# => [1331, 1728, 2197, 2744, 3375]

64. #block_given? is a handy control

def pass_control_without_control_on_condition
  p "Inside the method"
  yield
  p "Back inside the method"
end

pass_control_without_control_on_condition

# "Inside the method"
# LocalJumpError: no block given (yield)

def pass_control_on_condition
  p "Inside the method"
  yield if block_given?
  p "Back inside the method"
end

pass_control_on_condition { p "Inside the block now!" }

# "Inside the method"
# "Inside the block now!"
# "Back inside the method"
#=> "Back inside the method"

65. #yield with arguments takes care of routine operations within a method or function, thus freeing up the developer to concentrate on other, more cognitively demanding operations.

def speak_the_truth(name)
  yield name if block_given?
end

speak_the_truth("Alex") { |name| puts "#{name} has been studying for many hours."}
# Alex has been studying for many hours.

def number_evalution(num1, num2, num3)
  p "Main method"
  yield(num1, num2, num3) if block_given?
  p "Main method again"
end

p number_evalution(5,10,15) do |num1, num2, num3|
  p num1 ** 2
  p num2 ** 3
  p num3 ** 4
end

# "Main method"
# 25
# 1000
# 50625
# "Main method again"

def custom_each(ary)
  i = 0
  while i < ary.length
    yield ary[i]
    i += 1
  end
end

names = %w[John Jack Jill Joe]
numbers = (1..5).to_a

custom_each(names) { |name| p "name length: #{name.length}" }
custom_each(numbers) { |number| p "number squared: #{number ** 2}"}

# "name length: 4"
# "name length: 4"
# "name length: 4"
# "name length: 3"
# "number squared: 1"
# "number squared: 4"
# "number squared: 9"
# "number squared: 16"
# "number squared: 25"

66. Instead of a simple block, a Proc can be passed to a method. Also the Proc can be called with Proc#call.

def greeter
  p "Inside the method"
  yield
  p "Back inside the method"
end
phrase = Proc.new { p "Inside the proc" }

greeter(&phrase)

phrase.call

67. A neat shorthand to call a method as a Proc.

p ["1", "2", "3"].map { |n| n.to_i } == ["1", "2", "3"].map(&:to_i)
#=> true

68. A Proc can be passed as a method argument

true_statement = Proc.new { |name| puts "#{name} is writing a useful review of a Ruby course."}

def talk_about(name, &my_proc)
  puts "Let me tell you about #{name}:"
  my_proc.call(name)
end

talk_about("Alex", &true_statement)

69. lambda returns an error if the wrong number of arguments is supplied, Proc simply returns nil. Otherwise, they seem to be identical

squares_proc = Proc.new { |n| n** 2 }
squares_lambda = lambda { |n| n** 2 }

p [1, 2, 3].map(&squares_proc) == [1, 2, 3].map(&squares_lambda)
#=> true
p squares_proc.call(5) == squares_lambda.call(5)
#=> true
some_proc = Proc.new { |name, age| puts "#{name}, #{age}" }
some_lambda = lambda { |name, age| puts "#{name}, #{age}" }

p some_proc.call("John")
# John,
# nil

p some_lambda.call("John")
# -:9:in block in <main>': wrong number of arguments (given 1, expected 2) (ArgumentError)
# 	from -:12:in <main>'

70. 'return' prompts lambda to yield control to a method; Proc, to break from the method.

def diet_with_proc
  status = Proc.new { return "I gave in!" }
  status.call
  "Still dieting!"
end

def diet_with_lambda
  status = lambda { return "I gave in!" }
  status.call
  "Still dieting!"
end

p diet_with_proc
#=> "I gave in!"

p diet_with_lambda
#=> "I'm still dieting!"

71. Segmenting repeatable operations into lambdas allows for DRYer code.

to_euros = lambda { |dollars| dollars * 0.95 }
to_pesos = lambda { |dollars| dollars * 20.70 }
to_rupees = lambda { |dollars| dollars * 68.13 }

def convert(dollars, currency_lambda)
  currency_lambda.call(dollars) if dollars.is_a?(Numeric)
end

p convert(1000, to_euros)
p convert(1000, to_pesos)
p convert(1000, to_rupees)

p [1000, 2000, 3000].map(&to_rupees)

72. Time#new without arguments gives the same string as Time#now

p Time.new
# 2018-05-14 10:00:02 +1000
p Time.now
# 2018-05-14 10:00:02 +1000

p Time.new.class
#=> Time

p Time.now.class
#=> Time

p Time.new.object_id
#=> 47049250547520
p Time.now.object_id
#=> 47049250547420

p Time.new == Time.now
# false

p Time.new(2015, 5, 18, 23, 30, 12)
# 2015-05-18 23:30:12 +1000

p Time.now(2015, 5, 18, 23, 30, 12)
# -:8:in now': wrong number of arguments (given 6, expected 0) (ArgumentError)
# 	from -:8:in <main>'

73. Handy boolean methods for Time objects

p birthday = Time.new(1984, 05, 20)

p birthday.monday?
#=> false
p birthday.sunday?
#=> true
p birthday.dst?
#=> false

74. Time is added and subtracted in seconds

p start_of_year = Time.new(2019, 1, 1)
# 2019-01-01 00:00:00 +1100

p days_later_45 = start_of_year + (60 * 60 * 24 * 45)
# 2019-02-15 00:00:00 +1100

76. A smaller Time value is an earlier date, and vice versa.

p Time.now
# 2018-05-14 10:17:42 +1000

p tomorrow = Time.now + (60 * 60 * 24)
# 2018-05-15 10:17:42 +1000

75. Convert Time to other objects

p Time.now.to_s
# "2018-05-14 11:32:23 +1000"

p Time.now.ctime
# "Mon May 14 11:32:23 2018"

p Time.now.to_a
# [23, 32, 11, 14, 5, 2018, 1, 134, false, "AEST"]

76. Time#parse requires a string formatted "yyyy-mm-dd"; #Time.strptime can use strftime switches to parse a string.

require 'time'
p Time.parse("2016-01-1")
# 2016-01-01 00:00:00 +1100

p Time.strptime("03-04-2000", "%d-%m-%Y")
# 2000-04-03 00:00:00 +1000

77. A refresher on File#open, File#write, File#rename, File#delete

File.open("first.txt").each { |line| puts line }

File.open("first.txt", "w") do |file|
  file.puts "puts includes a carriage return"
  file.write "write does not include a carriage return"
endp ARGV.class
p ARGV
p ARGV[0].class

File.rename("old.txt", "new.txt")

File.delete("file_to_delete.txt") if File.exists?("file_to_delete")

78. ARGV returns an array of strings, even if non-strings are entered.

$ ruby argv_demo.rb 3 4 5
p ARGV.class
# Array
p ARGV
# ["3", "4", "5"]
p ARGV[0].class
# String

79. File#require and File#require_relative make the file's contents available to the program; File#load both returns and executes its contents. However, my simple example does not bear this out.

def hello_world_method_in_source_file
  puts "hello world"
end

load "hello_world.rb"
#=> true

require_relative("hello_world")
#=> true

require("./hello_world")
#=> true

80. Simple regexp returns index position of the first match only, and is case sensitive. (OBVIOUSLY)

phrase = "The Ruby programming language is amazing!"

p phrase =~ /T/
# 0
p phrase =~ /R/
# 4
p phrase =~ /r/
# 10

81. String#scan returns all matches as an array of strings, which can be counted for actually useful information.

phrase = "The Ruby programming language is amazing!"

p phrase.scan(/e/)
# ["e", "e"]

p phrase.scan(/[eRr]/)
# ["e", "R", "r", "r", "e"]

p phrase.scan(/e/).length

82. String#sub and String#gsub

str = "I want to find the sub-string 'find' by the sub-string replace."

p str.sub("find", "replace")
# "I want to replace the sub-string 'find' by the sub-string replace."

p str.gsub("find", "replace")
# "I want to replace the sub-string 'replace' by the sub-string replace."

p str.gsub(/\s/, "")
# "Iwanttofindthesub-string'find'bythesub-stringreplace."

83. Superclasses (Not really useful, but good to know)

p 5.class.superclass
# Numeric

p 5.class.superclass.superclass
# Object

p 5.class.superclass.superclass.superclass
# BasicObject

p 5.class.superclass.superclass.superclass.superclass
# nil

84. #methods returns an array of method names that apply to the object.

p "Hello World".methods
# [:include?, :%, :*, :+, :count, :partition, :to_c, :sum, :next, :casecmp, :casecmp?, :insert, :bytesize, :match, :match?, :succ!, :<=>, :next!, :index, :rindex, :upto, :==, :===, :chr, :=~, :byteslice, :[], :[]=, :scrub!, :getbyte, :replace, :clear, :scrub, :empty?, :eql?, :-@, :downcase, :upcase, :dump, :setbyte, :swapcase, :+@, :capitalize, :capitalize!, :undump, :downcase!, :oct, :swapcase!, :lines, :bytes, :split, :codepoints, :freeze, :inspect, :reverse!, :grapheme_clusters, :reverse, :hex, :scan, :upcase!, :crypt, :ord, :chars, :prepend, :length, :size, :start_with?, :succ, :sub, :intern, :chop, :center, :<<, :concat, :strip, :lstrip, :end_with?, :delete_prefix, :to_str, :to_sym, :gsub!, :rstrip, :gsub, :delete_suffix, :to_s, :to_i, :rjust, :chomp!, :strip!, :lstrip!, :sub!, :chomp, :chop!, :ljust, :tr_s, :delete, :rstrip!, :delete_prefix!, :delete_suffix!, :tr, :squeeze!, :each_line, :to_f, :tr!, :tr_s!, :delete!, :slice, :slice!, :each_byte, :squeeze, :each_codepoint, :each_grapheme_cluster, :valid_encoding?, :ascii_only?, :rpartition, :encoding, :hash, :b, :unicode_normalize!, :unicode_normalized?, :to_r, :force_encoding, :each_char, :encode, :encode!, :unpack, :unpack1, :unicode_normalize, :<=, :>=, :between?, :<, :>, :clamp, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :instance_variable_get, :public_methods, :instance_variables, :public_method, :singleton_method, :method, :define_singleton_method, :public_send, :extend, :to_enum, :enum_for, :pp, :!~, :respond_to?, :object_id, :send, :display, :nil?, :class, :singleton_class, :clone, :dup, :yield_self, :itself, :tainted?, :taint, :untrust, :untaint, :trust, :untrusted?, :methods, :frozen?, :singleton_methods, :protected_methods, :private_methods, :!, :equal?, :instance_eval, :instance_exec, :!=, :__id__, :__send__]

85. If a class is not instantiated, an instance variable is not instantiated either. (Obvious, I know, but easy for a novice to miss)

class Gadget
  def initialize
    @username = "User-#{rand(1..100)}"
    @password = (("A".."z").to_a | (0..9).to_a).sample(8).join
    @product_id = "#{("a".."z").to_a.sample(3)}-#{rand(1..100)}"
  end

  attr_accessor :username

  def show_username
    puts "Username: #{@username}"
  end
end

phone = Gadget.new
phone.show_username
# Username: User-8

dead_phone = Gadget
dead_phone.show_username
# -:20:in <main>': undefined method show_username' for Gadget:Class (NoMethodError)

86. An instance method has access to @instance_variables and the methods that come bundled in with them.

class Gadget
  def initialize
    @username = "User-#{rand(1..100)}"
    @password = (("A".."z").to_a | (0..9).to_a).sample(8).join
    @product_id = "#{("a".."z").to_a.sample(3)}-#{rand(1..100)}"
  end

  attr_accessor :username

  def show_username
    puts "Username: #{@username}"
  end

end

phone = Gadget.new

p phone.methods - Object.methods
# [:username, :show_username, :username=]

87. An instance method overrides any other method of the same name within the scope of the instantiated object.

class Gadget
  def initialize
    @username = "User-#{rand(1..100)}"
    @password = (("A".."z").to_a | (0..9).to_a).sample(8).join
    @product_id = "#{("a".."z").to_a.sample(3)}-#{rand(1..100)}"
  end

  def to_s
    puts "this overrides #to_s"
  end

end

phone = Gadget.new

p phone.to_s
# this overrides #to_s

88. #self refers to the instantiated object, not to the class.

class Gadget
  def initialize
    @username = "User-#{rand(1..100)}"
    @password = (("A".."z").to_a | (0..9).to_a).sample(8).join
    @product_id = "#{("a".."z").to_a.sample(3)}-#{rand(1..100)}"
  end

  def my_instance_method
    puts "class of instantiated Gadget: #{self.class}"
    puts "object_id of instantiated Gadget: #{self.object_id}"
    puts "inspection: #{self.inspect}"
  end
end

phone = Gadget.new
phone.my_instance_method

89. Shorthand getter and setter methods ('attr')

class Gadget
  attr_reader :production_number
  # read-only
  attr_writer :password
  # write-only
  attr_accessor :username
  # read-write

  def initialize
    @production_number = "#{("a".."z").to_a.sample(3).join}-#{rand(1..100)}"
    @password = (("A".."z").to_a | (0..9).to_a).sample(8).join
    @username = "User-#{rand(1..100)}"
  end

  def my_instance_method
    puts "class of instantiated Gadget: #{self.class}"
    puts "object_id of instantiated Gadget: #{self.object_id}"
    puts "inspection: #{self.inspect}"
  end
end

phone = Gadget.new

p phone.production_number
# "qpc-16"

p phone.username
# "User-68"

p phone.username = "user1984"
# "user1984"

p phone.password = "password123"
# "password123"

p phone.password
# -:28:in <main>': undefined method password' for #<Gadget:0x00005592cac77650> (NoMethodError)
# Did you mean?  password=

90. Modules are like classes, but cannot be instantiated; they are used to store bundles of related operations for access without instantiating a new object. Also, "::" is called a scope resolution operator.

module LengthConversions

  WEBSITE = "https://www.udemy.com/learn-to-code-with-ruby-lang/learn/v4/t/lecture/6482622?start=0"

  def self.miles_to_feet(miles)
    miles * 5820
  end

  def self.miles_to_inches(miles)
    feet = miles_to_feet(miles)
    feet * 12
  end
end

p LengthConversions::WEBSITE
# "https://www.udemy.com/learn-to-code-with-ruby-lang/"

p LengthConversions.miles_to_feet(100)
# 582000

p LengthConversions.miles_to_inches(100)
# 6984000

91. Mixin is a module that is added to a class, thereby making its variables and parameters available to said class.

class OlympicMedal
  include Comparable

  MEDAL_VALUES = { "Gold" => 3, "Silver" => 2, "Bronze" => 1}
  attr_reader :type

  def initialize(type, weight)
    @type = type
    @weight = weight
  end

  def <=> (other)
    if MEDAL_VALUES[type] < MEDAL_VALUES[other.type]
      -1
    elsif MEDAL_VALUES[type] == MEDAL_VALUES[other]
      0
    else
      1
    end
  end
end

bronze = OlympicMedal.new("Bronze", 5)
silver = OlympicMedal.new("Silver", 10)
gold = OlympicMedal.new("Gold", 3)

p bronze > silver
# false

p bronze < silver
# true

p gold >= silver
# true

p gold <= bronze
# false

p silver > bronze
# true

p silver != bronze
# true

p silver == gold
# false

92. include mixes a module into a class and allows class methods to override module methods of the same name.

module Purchaseable
  def purchase(item)
    "#{item} has been purchased!"
  end
end

class Store
  include Purchaseable

  def purchase(item)
    "from the store #{item} has been purchased"
  end
end

milkbar = Store.new
p milkbar.purchase("Tasty cheese")

93. prepend mixes a module into a class, but does not allow class methods to override module methods of the same name.

module Purchaseable
  def purchase(item)
    "#{item} has been purchased!"
  end
end

class Store
  prepend Purchaseable

  def purchase(item)
    "from the store #{item} has been purchased"
  end
end

milkbar = Store.new
p milkbar.purchase("Tasty cheese")

94. extend mixes a module into a class, but adds the module's methods on the class itself, not as class methods.

module Announcer
  def who_am_i
    "The name of the class is #{self}"
  end
end

class Extender
  extend Announcer
end

class Includer
  include Announcer
end

class Prepender
  prepend Announcer
end

p Extender.new.who_am_i
# NoMethoError
p Extender.who_am_i
# "The name of the class is Extender"
p Includer.new.who_am_i
# "The name of the class is #<Includer:0x000055f27daf9bf8>"
p Includer.who_am_i
# -:20:in <main>': undefined method who_am_i' for Includer:Class (NoMethodError)
p Prepender.new.who_am_i
# "The name of the class is #<Prepender:0x000055fb74c598b8>"
p Prepender.who_am_i
# -:20:in <main>': undefined method who_am_i' for Prepender:Class (NoMethodError)

95. Enumerable is the module that supplies iteration functionalities to String, Number, Array, Hash, and can be mixed in to a new class to the same effect.

class ConvenienceStore
  include Enumerable
  attr_reader :snacks

  def initialize
    @snacks = []
  end

  def add_snack(snack)
    snacks << snack
  end

  def each
    snacks.each { |snack| yield snack }
  end
end

milkbar = ConvenienceStore.new
milkbar.add_snack("TimTam")
milkbar.add_snack("Freddo")
milkbar.add_snack("Bisli")

p milkbar.snacks
# ["TimTam", "Freddo", "Bisli"]

p milkbar.map {|snack| snack.upcase}
# ["TIMTAM", "FREDDO", "BISLI"]

96. A private method is accessible to other methods within the instantiated object from the class that it is declared in, but not from an outside object, i.e. a private method cannot be called with an explicit receiver; only the current object can receive method.

class Gadget

  attr_writer :password
  attr_reader :production_number
  attr_accessor :username

  def initialize(username, password)
    @username = username
    @password = password
    @production_number = generate_production_number
  end

  def to_s
    "Gadget #{@production_number} has the username #{@username}.
    It is made from the #{self.class} class and it
    has the ID #{self.object_id}"
  end

  private

  def generate_production_number
    start_digits = rand(10000..99999)
    end_digits = rand(10000..99999)
    alphabet = ("A".."Z").to_a
    middle_digits = Time.now.year.to_s
    5.times { middle_digits << alphabet.sample }
    "#{start_digits}-#{middle_digits}-#{end_digits}"
  end
end

phone = Gadget.new("user", "password")

p phone.username
# "user"

p phone.production_number
# "64607-2018YHDOW-18212"

p phone.generate_production_number
# -:34:in <main>': private method generate_production_number' called for #<Gadget:0x000055d3408209b0> (NoMethodError)```

### 97. A *protected* method is available to other methods within the same instantiated object AND to other objects instantiated from the same class OR from objects instantiated from *descendents* of said class.

```ruby
class Vehicle
  def initialize(age, miles)
    base_value = 20000
    age_deduction = age * 1000
    miles_deduction = miles / 10.to_f
    @value = base_value - age_deduction - miles-age_deduction
  end

  def compare_with_other_vehicle(vehicle)
    self.value > vehicle.value ? "Your vehicle is better!" : "Your vehicle is worse!"
  end

  protected

  def value
    @value
  end
end

class Car < Vehicle
end

civic_vehicle = Vehicle.new(3, 3000)
fiat_vehicle = Vehicle.new(2, 2000)
civic_car = Car.new(3, 3000)
fiat_car = Car.new(2, 200)

p civic_vehicle.compare_with_other_vehicle(fiat_vehicle)
# "Your vehicle is worse!"

p fiat_vehicle.compare_with_other_vehicle(civic_vehicle)
# "Your vehicle is better!"

p civic_car.compare_with_other_vehicle(fiat_car)
# "Your vehicle is worse!"

p fiat_car.compare_with_other_vehicle(civic_car)
# "Your vehicle is better!"

p civic_vehicle.compare_with_other_vehicle(fiat_car)
# "Your vehicle is worse!"

p fiat_vehicle.compare_with_other_vehicle(civic_car)
# "Your vehicle is better!"

97. An instance method is preferable to an instance variable: it keeps instance variables safer and acts as a pseudo-variable. (Modification of an instance variable that does not create a new variable)

class Gadget

  attr_writer :password
  attr_reader :production_number
  attr_accessor :username

  def initialize(username, password)
    @username = username
    @password = password
    @production_number = generate_production_number
  end

  def to_s
    "Gadget #{self.production_number} has the username #{self.username}.
    It is made from the #{self.class} class and it
    has the ID #{self.object_id}"
  end

  private

  def generate_production_number
    start_digits = rand(10000..99999)
    end_digits = rand(10000..99999)
    alphabet = ("A".."Z").to_a
    middle_digits = Time.now.year.to_s
    5.times { middle_digits << alphabet.sample }
    "#{start_digits}-#{middle_digits}-#{end_digits}"
  end
end

phone = Gadget.new("user", "password")
puts phone.to_s```

# Gadget 22692-2018MBJMM-33662 has the username user.
#     It is made from the Gadget class and it
#     has the ID 47133824417920

### 98. *self* can be omitted from being called on an instance variable that is called by an instance method, except on a special Ruby keyword like *class*.

#### I'd prefer to make it explicit, but I guess leaving these sorts of things out is the Ruby way.

```ruby
def to_s
  "Gadget #{production_number} has the username #{username}.
  It is made from the #{self.class} class and it
  has the ID #{object_id}"
end

99. The most secure way to modify an instance variable is via an instance method (with the keyword self) that has access to otherwise inaccessible instance variables.

class Gadget
# initialize etc...
  def reset_device(username, password)
    self.username = username
    self.password = password
    self.apps = []
  end
end

100. Struct is a simple Class-like container. As Symbol is to String, Struct Class.

module AppStore
  App = Struct.new(:name, :developer, :version)

  APPS = {
    App.new(:Chat, :facebook, 2.0),
    App.new(:Twitter, :twitter, 5.8),
    App.new(:Weather, :yahoo, 10.15)
  }

  # *self* ensures that the method will look within the module, rather than outside it.
  def self.find_app(name)
    APPS.find { |app| app.name == name }
  end
end

# Within Gadget class
def install
  app = AppStore.find_app(name)
  # @apps is already initialized as an instance variable
  @apps << app unless @apps.include?(app)
end

101. A @@class_variable pertains to the Class as a whole rather than a specific Object instantiated therefrom.

102. A class method is declared with self and is inaccessible to an instantiated object.

103. An instance method is inaccessible to the Class per se.

class Bicycle
  @@maker = "Biketech"
  @@count = 0

  def self.description_class
    "I'm the blueprint for Bicycle Class"
  end

  def description_instance
    "I'm the instance method description"
  end

  def self.count
    @@count
  end

  def maker
    @@maker
  end

  def initialize
    @@count += 1
  end
end

bike = Bicycle.new
bike2 = Bicycle.new

p Bicycle.description_class
# "I'm the blueprint for Bicycle Class"

p Bicycle.description_instance
# -:30:in <main>': undefined method description_instance' for Bicycle:Class (NoMethodError)
# Did you mean?  description_class

p bike.description_class
# -:37:in <main>': undefined method description_class' for #<Bicycle:0x000055cfeb796150> (NoMethodError)
# Did you mean?  description_instance

p bike.description_instance
# "I'm the instance method description"

p bike.maker
# "Biketech"

p Bicycle.maker
# -:30:in <main>': undefined method maker' for Bicycle:Class (NoMethodError)

p Bicycle.count
# 2

104. instance_of? returns true only if the Object is instantiated directly from said Class.

p 1.is_a?(Numeric)
# true

p 1.is_a?(Integer)
# true

p 1.instance_of?(Numeric)
# false

105. super passes arguments to the instance method of the same name in the superclass

class Manager < Employee
  def initialize(name, age, rank)
    super(name, age)
    @rank = rank
  end
end

106. super fetches the return value of instance method of the same name from the superclass

class Employee
  attr_reader :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def introduce
    "Hi, I'm #{name} and I am #{age} years old."
  end
end

class Manager < Employee
  def initialize(name, age, rank)
    super(name, age)
    @rank = rank
  end

  def introduce
    super + "  I'm a manager!"
  end
end

dan = Manager.new("Dan", 42, "Manager")
p dan.introduce
# "Hi, I'm Dan and I am 42 years old.  I'm a manager!"

107. super() fetches the return value of the superclass instance method, without passing on any arguments thereto.

108. super(args) fetches the return value of the superclass instance method, and supplies only those args specified, without necessarily passing on all the arguments supplied by the subclass instance method.

class Car
  attr_reader :maker
  def initialize(maker)
    @maker = maker
  end

  def drive
    "Vroom! Vroom!"
  end
end

class Firetruck < Car
  def initialize(maker, siren)
    # Does not pass on siren variable to superclass.
    super(maker)
    @siren = siren
  end

  def drive(speed)
    # Does not pass to superclass method the argument supplied here.
    super() + "Beep! Beep! I'm driving at #{speed} miles per hour!"
  end
end

109 super can be used to call and pass on data to @@class_variables across subclasses.

class Widget < Product
  def initialize
    super # @@product_counter += 1
    @@widget_counter += 1
  end

  def self.counter
    @@widget_counter
  end
end

class Gadget < Product
  def initialize
    super # @@product_counter += 1
    @@gadget_counter += 1
  end

  def self.counter
    @@gadget_counter
  end
end

w = Widget.new
g = Gadget.new

Product.counter
# 2

Widget.counter
# 1

Gadget.counter
# 1

110. A singleton method is called exclusively and anonymously by a particular subclass.

111. #singleton_methods identifies the singleton methods of a class instance.

class Player
  def play_game
    rand(1..100) > 50 ? "Winner!" : "Loser!"
  end
end

bob = Player.new
boris = Player.new

def boris.play_game
  "Winner!!"
end

p bob.play_game
# "Winner!"

p boris.play_game
# "Winner!!"

p bob.singleton_methods
# []

p boris.singleton_methods
# [:play_game]

p boris.singleton_class
# #<Class:#<Player:0x00005649364fa558>>

112. Hash can be an initialize variable

class GnuslashlinuxDistros
  attr_accessor :favourite, :hated, :interested, :stallman_approved

  def initialize(distros)
    @favourite = distros[:favourite]
    @hated = distros[:hated]
    @interested = distros[:interested]
    @stallman_approved = distros[:stallman_approved]
  end
end

distro_list = { favourite: "Ubuntu", hated: "Linx Mint", interested: "Arch", stallman_approved: "gNewSense"}

my_distro_picks = GnuslashlinuxDistros.new(distro_list)
p my_distro_picks.favourite
# "Ubuntu"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment