Study Guide 2023+

ruby

Warning: These notes are partial, ongoing, incomplete, and may contain typos/inaccuracies. (They are kept factually accurate, time permitting.)

They are being united from many disparate notes created in the past and the layout/organization will gradually improve with time!

Please view them on a computer as they are not optimized for mobile (although you can still view them on Mobile along with the Flashcards at your own risk)!

Topics and code examples are lazy-loaded and may require two-clicks from the TOC to correctly calculate the updated x,y coordinates (after rendering). Thanks!

Ruby: General Concepts

  1. nil is the nullish value keyword.
  2. Ruby is an inherently Synchronous language. There's nothing that corresponds to a native Promise.
  3. Modules are Mixins that can be included in a Class. This also provides Java-like Interface and Abstract Class reuse.
  4. All Function, Method, Procs, and Lambda-types are Closures.

Ruby: Closures

All Function, Method, Procs, and Lambda-types are Closures.

Examples

# More Closures
## Closures include any of procs, lambdas, methods, functions, blocks

#-----------------------------------#

# Procs (functions)
## Lambda proc with no variable name
-> (arg) {p arg +1 }.call(2)

## Proc new keyword with no assignment
Proc.new {| n | p n + 111}.call(2)
## Proc new keyword with assignment
proc1 = Proc.new {| n | n ** 2}
p proc1.call(4)

## Proc from block
def make_proc(&block)
   block
end
proc2 = make_proc {|x| x+7 }
p proc2.call(4)

#-----------------------------------#

# Lambdas
## With variable name
varName = -> (arg){p arg + 1}
varName.call(2)

#-----------------------------------#

# Blocks
## Can be a method
def example1
  yield
end
example1{p 2+ 4}

def example2(&block)
  block.call
end
example2{p 5+ 8}

## Helpful for arrays
[1,2,3,4].each do |x|
  p x
end
[1,2,3,4].each{|x| p x}

Lambda Proc with no variable name (Anonymous Function called with an actual parameter or argument):

-> (arg) {p arg +1 }.call(2)

Methods

# Methods
## Last line is automatically returned
## No explicit return type needed

def scoring(x, y)
  x + y
end

## It can be added however

def add(x, y)
  return x + y
end

puts(scoring(1,2))
puts(add(1,4))

## Parameterization - key arguments

def order_irrelevant_key_args(arg1:, arg2:, arg3:)
  arg1 + arg2 + arg3
end

p order_irrelevant_key_args(arg1:1, arg3: 2, arg2: 3)
p order_irrelevant_key_args(arg3: 2, arg2: 3, arg1:3,)

def order_irrelevant_optional_key_args(arg1:, arg2:1, arg3:2)
  arg1 + arg2 + arg3
end

p order_irrelevant_optional_key_args(arg1:1)
p order_irrelevant_optional_key_args(arg1:1, arg3:5)

## Parameterization - arguments

def order_matters(arg1, arg2, arg3)
  arg1 + arg2 + arg3
end

p order_matters(1,2,3)

def order_matters_optional(arg1 = 1, arg2 = 2, arg3)
  arg1 + arg2 + arg3
end

p order_matters_optional(3)

## Parameterization - optional (as arr)

## Logically, this order of args is required
## Standard args occur first (and ordering matters)
## It also looks for any key args
## The remainder are optional and specified by * (... in JS)
def optional(arg1, *opt, arg2:)
  total =  arg1 + arg2
  opt.each{|x| total = total + x }
  total
end

p optional(1,2,3,4, arg2: 5)

Note: official Ruby documentation refers to all Functions as Methods (and uses these interchangeably):

  1. https://docs.ruby-lang.org/en/2.0.0/syntax/methods_rdoc.html
  2. https://ruby-doc.com/docs/ProgrammingRuby/html/tut_methods.html

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024/tree/main/ruby/8-closures
  2. https://github.com/Thoughtscript/rrr_2024/tree/main/ruby/2-methods

Ruby: Hashes and Arrays

Hash

A Hash is a dict / array (since they are integer-indexed collections) / map equivalent.

Named Key - Value (e.g. - Objects in JS, dicts in Python).

# hash_example = {}
# hash_example = Hash.new
hash_example = Hash[]
hash_example['a'] = n
hash_example[:b] = 100

Array

Array - an Array proper (e.g. - Key by index Value).

Expands in size - ArrayList like (Java).

arr_example = Array.new() 
arr_example = []
arr_example.push(1)
arr_example.push(2)
arr_example.push(3)
p arr_example.first
p arr_example.last

Ruby Arrays are used to implement (or as backing Data Structures for) most other commonly encountered Data Structures (Set, Heap, Stack, Queue, etc.).

Unlike many other languages, in Ruby there are only the two primary container-type Data Structures (Array and Hash). Data Structures such as Set aren't inherent to the core language itself and are added through standalone, core, libraries (in a manner somewhat akin to the Java EE and Collections APIs, JavaScripts Web APIs, etc.).

In Ruby, Array length and size() accomplish the same:

  1. size() (the method) is an alias for the field length.
  2. Added for uniformity since many languages use length (Java, JavaScript) while C++ uses size().
  3. Refer to the Documentation.
  1. https://docs.ruby-lang.org/en/3.4/Hash.html
  2. https://docs.ruby-lang.org/en/3.4/Array.html
  3. https://docs.ruby-lang.org/en/3.4/Set.html

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/10-hashes/main.rb
  2. https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/7-arrays/main.rb

Ruby: Interceptors

  1. before_action - provides the same functionality as a Java Interceptors, executes a function prior to other actions being performed within an HTTP handler.

Ruby: Object Oriented Design

Inheritance

class Animal
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def speak
    "Hello!"
  end
end

class GoodDog < Animal
  def initialize(name, color)
  super(name)
    @color = color
  end

  def speak
    super + " from GoodDog class"
  end
end

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/6-ood/main.rb

Ruby: Exception Handling

  1. Use fail instead of raise in circumstances where an Exception will not be handled and rethrown as something more specific.
  2. Use raise otherwise.
  3. rescue is the relevant "catch" keyword.
  4. When defining customized Exceptions inherit from StdErr.

Examples

begin
  p hash_example['a']['b']
rescue Exception # Should generally prefer to check for StdErr
  p "hash_example['a']['b'] throws an exception" 
end
begin
  p hash_example['a']['b']
rescue Exception # Should generally prefer to check for StdErr
  p "hash_example['a']['b'] throws an exception" 
ensure
  p "I'm a finally block"
end
  1. https://bulldogjob.com/readme/ruby-gotchas-that-will-come-back-to-haunt-you

Ruby: Techniques

Strings and Symbols

# Ruby symbols and strings

## Comparison - these are not the same
a = 'hello'
b = :hello
p a == b

## Hash - different keys
x = Hash[]
x['a'] = 1
x[:a] = 2
p x['a'] == x[:a]
p x

## Via try
y = Hash[]
y['b'] = { 'c' => 100 }
p y
p y['b']['c']
p y[:b]&.try(:c)

## Convert
p :a.to_s
p 'a'.to_sym

https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/17-sym_str/main.rb

Remember:

  1. Ruby Strings don’t work like Java's String Pool. The same String content can have two Pointers.
  2. Ruby Symbols that share the same content will have the same Address in memory.
  3. Strings are mutable and Symbols aren't.
  4. It's often easier and better to use Symbols (performance, overhead, immutability-wise) to access, enumerate, or work with Hashes.

https://stackoverflow.com/questions/255078/whats-the-difference-between-a-string-and-a-symbol-in-ruby

Equality and Comparisons

For all intents and purposes one should generally use == ("Generic Equality"):

# At the Object level, == returns true only if obj and other are the same object.
1 == 1.0     #=> true
1.eql? 1.0   #=> false

As opposed to eql? (Hash Equality) which checks by Hash key and/or subtype (as in the case of Numeric comparisons above) or any of: ===, equal?. Generally, these should be avoided due to the specifics of their implementations and occasionally lengthy research into their side-effects.

If required use the other existing comparison operators or Methods:

b.is_a? A         
b.kind_of? A      
b.instance_of? A  

## check identity by object_id
x = 10;
y = 25;
z = x;

puts x.object_id
puts y.object_id
puts z.object_id == x.object_id

Consult: https://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and and https://ruby-doc.org/3.2.2/Object.html#method-i-eql-3F

Helpful Loop Opertations

each_with_index:

a.each_with_index { |item, index|
  p %Q(#{item} at index: #{index})
}
  1. https://docs.google.com/presentation/d/1cqdp89_kolr4q1YAQaB-6i5GXip8MHyve8MvQ_1r6_s/edit#slide=id.g2048949e_0_38
  2. https://www.toptal.com/ruby/interview-questions
  3. https://www.turing.com/interview-questions/ruby
  4. https://stackoverflow.com/questions/255078/whats-the-difference-between-a-string-and-a-symbol-in-ruby
  5. https://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and
  6. https://ruby-doc.org/3.2.2/Object.html#method-i-eql-3F

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024
  2. https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/17-sym_str/main.rb

Ruby: Safe Navigation Operators

Consider the example of checking the index of an M x N Array. Specifically, where some row r may be Out of Bounds.

Or, alternatively, checking if a nested field exists on an Object.

Three ways of checking that include are given as follows.

The Overly Verbose Way

if account && account.owner && account.owner.address
    # ...
    # => false
    # => nil
end

ActiveRecord Try

if account.try(:owner).try(:address)
    # ...
    # => false
    # => nil
end

Safe Navigation Operator

if  account&.owner&.address
    # ...
    # => nil
    # => undefined method `address' for false:FalseClass`
end

With Hashes

Given:

hash_example = Hash[]
hash_example['a'] = n
hash_example[:b] = 100

To check for nested a > :b:

hash_example['a']&.try(:b)
hash_example['a']&.try(:[], :b)

Array#dig and Hash#dig

Consider:

address = params[:account].try(:[], :owner).try(:[], :address)
# or
address = params[:account].fetch(:owner) { {} }.fetch(:address)
address = params.dig(:account, :owner, :address)
  1. https://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/16-safe_navigation/main.rb

Ruby: Truthy Evaluations

In Ruby, only false and nil are evaluated to false.

0, [], etc. all evaluate to true. (Unlike Javascript where 0 evaluates to false.)

  1. https://bulldogjob.com/readme/ruby-gotchas-that-will-come-back-to-haunt-you

Ruby: Visibility and Access

  1. @@ - Class Variable, defines a field that's synchronized between all instances of the Class. For example, counting the number of instantiated copies of a Class that have been created since Application start.
  2. @ - fields must be set and got using getters and setters, same as self.
  3. attr_accessor - fields can be accessed directly with dot notation.
  4. private - a keyword that can be used to indicate that every Closure definition in the scope below be given the private access visibility modifier.

Example

class ExampleClass
  attr_accessor :field_one
  
  def set_and_get_field_two(arg)
    ## Does not have to be declared as attr_accessor
    ## But cannot be directly accessed in public
    @field_two = arg
    p @field_two
  end

  def get_field_two()
    p @field_two
  end

  def get_field_one
    ## These are the same
    p @field_one
    p self.field_one

    example_private(arg1: @field_one)
  end

  # Everything below is given the 'private' access modifier
  private

  def example_private(arg1:)
    p arg1
  end
end

## Call 
### Constructor
e = ExampleClass.new
### Set attr_accessor field
e.field_one = 2
### Getter for @
e.get_field_one

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024/blob/main/ruby/12-access/main.rb

Ruby: Common Rails Commands

Start

rails db:create
rails db:migrate
rake db:seed
rails server --binding=127.0.0.1

By default, the Ruby on Rails serve will serve from: http://localhost:3000/

Migrations

bin/rails generate migration ExampleMigration
rails db:migrate
# rake db:migrate

Reset DB

# run migration and seeding
rails db:setup 
# rails db:create
# rails db:migrate
# rake db:seed

rails db:reset

Create Model and Table

rails g model Dinosaur name:text
rails g model BabyDino name:text

Create Controller

rails g controller Dinosaurs

Code samples:

  1. https://github.com/Thoughtscript/rrr_2024/tree/main/ruby

Ruby: Active Record

Active Record FK Example

Schema

ActiveRecord::Schema[7.1].define(version: 2024_07_05_224153) do
    # These are extensions that must be enabled in order to support this database
    enable_extension "plpgsql"
  
    create_table "examples", force: :cascade do |t|
      t.text "name"
      t.datetime "created_at", null: false
      t.datetime "updated_at", null: false
    end
  
    create_table "jsonexample", id: false, force: :cascade do |t|
      t.integer "id"
      t.json "json_col"
      t.json "json_array_col"
      t.jsonb "jsonb_col"
      t.jsonb "jsonb_array_col"
    end
  
    create_table "sub_examples", force: :cascade do |t|
      t.bigint "example_id"
      t.text "name"
      t.datetime "created_at", null: false
      t.datetime "updated_at", null: false
      t.index ["example_id"], name: "index_sub_examples_on_example_id"
    end
  
  end

From: https://github.com/Thoughtscript/rrr_2024/blob/main/rails/web/db/schema.rb

Models

class SubExample < ApplicationRecord
    attribute :name, :string

    # Does not need to be explicitly set
    # attribute :id, :integer
    # self.primary_key = :id

    # Does not need to be explicitly set
    # attribute :example_id, :integer
    belongs_to :example, class_name: "Example", inverse_of: :sub_examples

    validates :name, presence: true
end

From: https://github.com/Thoughtscript/rrr_2022/blob/main/_ruby/web/app/models/baby_dino.rb

class Example < ApplicationRecord
    # Remember that Rails ActiveRecord uses attributes here!
    # Distinct from DTO's.  
    attribute :name, :string

    # Does not need to be explicitly set
    # #attribute :id, :integer
    # self.primary_key = :id

    has_many :sub_examples, inverse_of: :example

    validates :name, presence: true

    def msg
      "Test Message!"
    end
end

From: https://github.com/Thoughtscript/rrr_2024/blob/main/rails/web/app/models/example.rb