Ruby Training
Introduction
This is to summarised what I learn on the ruby Training
Returning if true
Quite liked this. On do the return if true
def remaining_minutes_in_oven(actual_minutes_in_oven)
return EXPECTED_MINUTES_IN_OVEN - actual_minutes_in_oven if actual_minutes_in_oven < EXPECTED_MINUTES_IN_OVEN
raise 'Please implement the Lasagna#remaining_minutes_in_oven method'
end
Symbols
Symbols are just an identifier
class User
STATES = [:active, :inactive, :banned]
...
Provide you pass the same thing, they are the same thing e.g.
User.new(:active)
Predicate Method
So in ruby you have a predicate method of a method that returns true or false which is, by convention, not enforced, a method with a ?. The example below returns true if status equal true.
class User
STATES = [:active, :inactive, :banned]
def initialize(status)
raise "Invalid state" unless STATES.include?(status)
@status = status
end
def active?
@status == :active
end
end
This became a bit more obvious the usage with the example with ranges
module Chess
RANKS = 1..8
FILES = 'A'..'H'
def self.valid_square?(rank, file)
RANKS.include?(rank) && FILES.include?(file)
end
end
Interpolation
This is how to interpolation works
my_var1 = "HELLO"
my_var2 = "FRED WAS HERE #{my_var1}"
puts my_var2
LINQ in Ruby?
So this is what was given
class BirdCount
def self.last_week
[0, 2, 5, 3, 7, 8, 4]
end
def initialize(birds_per_day)
@birds_per_day = birds_per_day
end
def yesterday
@birds_per_day[-2]
end
def total
@birds_per_day.sum
end
def busy_days
@birds_per_day.count { |day| day >= 5 }
end
def day_without_birds?
@birds_per_day.any? { |day| day == 0 }
end
end
Which looks canna complicated but really it just like this in C#
public class BirdCount
{
public int[] BirdsPerDay { get; }
public BirdCount(int[] birdsPerDay)
{
BirdsPerDay = birdsPerDay;
}
}
var birds = new BirdCount(new[] { 0, 2, 5, 3, 7, 8, 4 });
birds.Where(day => day >= 5).Count();
Other functions include
fibonacci = [0, 1, 1, 2, 3, 5, 8, 13]
fibonacci.count { |number| number == 1 } #=> 2
fibonacci.any? { |number| number == 6 } #=> false
fibonacci.select { |number| number.odd? } #=> [1, 1, 3, 5, 13]
fibonacci.all? { |number| number < 20 } #=> true
fibonacci.map { |number| number * 2 } #=> [0, 2, 2, 4, 6, 10, 16, 26]
Mapping over arrays
This was quite hard for me. I think just not used to syntax. Once I see the answer it is obvious. compact removes the nil values in an array.
# So an example of the data is
shoes = { price: 30.00, name: "Shoes", quantity_by_size: { s: 1, xl: 4 } }
coat = { price: 65.00, name: "Coat", quantity_by_size: { s: 2 } }
handkerchief = { price: 19.99, name: "Handkerchief", quantity_by_size: { m: 3, l: 2 } }
items = [shoes, coat, handkerchief]
class BoutiqueInventory
def initialize(items)
@items = items
end
def item_names
@items.map { |item| item[:name] }.flatten.sort
end
def cheap
@items.map { |item| item[:price] < 30.00 ? item : nil }.compact
end
def out_of_stock
@items.map { |item| item[:quantity_by_size].empty? ? item : nil }.compact
end
def stock_for_item(name)
@items.map { |item| item[:name] == name ? item[:quantity_by_size] : nil }.compact.first || {}
end
def total_stock
@items.map { |item| item[:quantity_by_size].values.sum }.sum
end
private
attr_reader :items
end
Openstruct
With this we create an object from a hashes. E.g.
attributes = { name: "Jeremy Walker", age: 21, location: "Nomadic" }
person = OpenStruct.new(attributes)
# You can now do
person.name
#=> Jeremy Walker
person.location
#=> Nomadic
So for out example above we can do
# So an example of the data is
shoes = { price: 30.00, name: "Shoes", quantity_by_size: { s: 1, xl: 4 } }
coat = { price: 65.00, name: "Coat", quantity_by_size: { s: 2 } }
handkerchief = { price: 19.99, name: "Handkerchief", quantity_by_size: { m: 3, l: 2 } }
items = [shoes, coat, handkerchief]
class BoutiqueInventory
def initialize(items)
# @items = items
@items = items.map { |item| OpenStruct.new(item) }
end
def item_names
# @items.map { |item| item[:name] }.flatten.sort
# First Bash
# items.map { |item| item.name }.sort
items.map(&:name).sort
end
def total_stock
# @items.map { |item| item[:quantity_by_size].values.sum }.sum
# First Bash
# items.sum do |item|
# item.quantity_by_size.values.sum
# end
items.map(&:quantity_by_size).map(&:values).map(&:sum).sum
end
private
attr_reader :items
end
Decomposing Arrays and Hashes
I think it probably is just arrays as hashes will need to be converted to arrays
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Instead use to_a
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]
Once a array, we can decompose stuff any way we choose using splats to capture remaining. Did actually quite like this.
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
Composing Arrays and Hashes
Composing can be done with single splat (*) for arrays
>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits
>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
And a double splat (**)for hashes
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}
Splats with Arguments
Maybe the only sensible thing to say about this is like Gary Eats ("let's give a go"). If they can fit it in they will. It suggests
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
def my_method(a, b, *arguments,c, symbol_bash:, **d)
p a
p b
p arguments
p c
p symbol_bash
p d
end
my_method(1, 2, 3, 4, 5, 6, symbol_bash: 100, a: 1, b: 2)
Ruby String functions
translate characters (tr)
Just substitute a for b
"metal-oxide".tr("-", " ")
global substitute with regex
So will never learn regex just to difficult for me. Know how to solve so not an issue.
- [...] means set
- ^ inside bracket means not like maths
- a-z means what it says a,b,...z
- A-Z means what it says A,B,...Z
- \s means whitespace, space, tab, newline, carriage return
So substitute anything not in set with ""
phrase.gsub(/[^a-zA-Z\s]/, "")
Exceptions
So quick reminder of mainly rescue and exceptions
class SimpleCalculator
ALLOWED_OPERATIONS = ['+', '/', '*'].freeze
class UnsupportedOperation < StandardError; end
def self.calculate(first_operand, second_operand, operation)
unless ALLOWED_OPERATIONS.include?(operation)
raise UnsupportedOperation, "Operation must be one of #{ALLOWED_OPERATIONS.join(', ')}"
end
raise ArgumentError, 'Operands must be numeric' unless first_operand.is_a?(Numeric) && second_operand.is_a?(Numeric)
case operation
when '+'
"#{first_operand} + #{second_operand} = #{first_operand + second_operand}"
when '/'
begin
"#{first_operand} / #{second_operand} = #{first_operand / second_operand}"
rescue ZeroDivisionError
'Division by zero is not allowed.'
end
when '*'
"#{first_operand} * #{second_operand} = #{first_operand * second_operand}"
end
end
end