An ongoing compilation of resources I find useful.

notes as we go!

This first iteration is focused on Ruby language, and it’s frameworks.

The plain text markup language AsciiDoc is used to journal and arrange all technical content that is deemed useful. We then use a Ruby based Text Processor for compiling and parsing everything into a document model. It’s a really great tool that allows us to output different formats according to our needs. This process of working is great as it mechanizes the writing, editing, and compiling process so that we can focus more on the content.

These notes are a work in progress. Proceed with caution and forgiveness.

How to read this document

We follow some convetions:

Contributing to this document will be easier if we follow some style conventions.

Single Sentence Per Line

Most documents go through several versions (always more than you expected) before they are finally finished. Accordingly, you should do whatever possible to make the job of changing them easy.

First, when you do the purely mechanical operations of typing, type so subsequent editing will be easy. Start each sentence on a new line. Make lines short, and break lines at natural places, such as after commas and semicolons, rather than randomly. Since most people change documents by rewriting phrases and adding, deleting and rearranging sentences, these precautions simplify any editing you have to do later.

— Brian W. Kernighan
1974

Write somethihg for test In the case of this document, we will prefer "One Sentence, One Line".

Admonitions

This is used for notes, noteworthy items.
This is used for tips, usually short or guiding material
This is used for important information, things that may be more critical than a tip
This is used to provide caution, something may not be apparent, or may cause other problems.
This is to note great care should be given to this information, as it may cause something irrevocable to happen, or otherwise cause problems.

end section

Commands

These come first, as they tend to be the most useful for me.

This section is predominantly about moving around in the terminal.

I like to use Tmux as a terminal multiplexer, allowing us to open multiple panes within our terminal. We can use a fork of Tmux Tmate which provides an instant pairing solution, we do so by sharing the ssh key of a session created for us when doing tmux. What is great is that we can import all of our personalised Tmate keybindings into the session by doing tmux -f ~/.tmate.conf (the tmate configuration file is at the root of our local, and is where we put any of our personalized keybindings).

There are all sorts of really great things we can do through the terminal, and I have taken to using it for pretty much everything because it’s so efficient (except CSS styling).

A great resource and general good read regarding the command line: The Art of Command Line.

Otherwise, I use this page to store commands that I have found useful. So it’s a: Work in progress…​

Vim Commands

For basic commands like moving around (h,j,k,l) see vim tutor. These commands are slightly more esoteric.

Command Action

:help [word]

then hit CTRL-D to see matching help entries for "word".

:helpgrep [word]

Does the same as above

:help

To see list of all possible files relating to vim instructions

Navigating Around

Commands to move around in the vim environment.

Table 1. Window or Pane control
Command Action

:split

Opens a new pane above/below

:control w + up arrow

Switch between these panes

:control w + _

Make other pane exponentially smaller

:control w + =

Make panes of equal proportion

:control w + -

Adjust incrementally proportion of pane

:control w + +

Make current selected pane bigger

<Ctrl>+<w> then press <v>

Open a new VIM window next to the existing one

Editing

Table 2. Useful Vim Keys for editing
Command Action

ysw

"you surround word"

ds[character]

Deletes character surrounding word/sentence (e.g. "quotes" becomes quotes)

ys[n chars]l

Surrounds the letter under cursor n times right

ys[n chars]h

Surrounds the letter under cursor n times left

:set spell

Spell check (it highlights suspect words)

:set nospell

Turn off spell checker

z=

If you’re sure its the first one

1z=

If you’re sure its the first one

~

Capitalize a letter

.

Do the last command (i.e. Capitalize a letter)

/[a word]

Highlights searched word

ctrl n

to select how many times it appears, hit enter, then change the word

:noh

Remove highlight on word or character until next search

:set nohlsearch

Turn off highlighting completely

gqq

Formatting lines: The Power of G, or formmating long lines.

shift J

Joins the line beneath to current line on.

Tmux commands

  • Commands to move around Tmux environment.

Table 3. Window and Pane Management
Command Action

ctrl -b + c

Create new window

ctrl -b + n

Move to next or previous window

ctrl -b + z

Zoom into one pane, and again to zoom out

ctrl -b + %

Open new vertical pane

ctrl -b + "

Open new horinzontal pane

ctrl -b + space

Reorganise panes

  • To open a tmate session using tmux configuration do:

Tmate session with Tmux configurations
tmate -f ~/.tmux.conf

Shell Commands

git log [filename] ⇒ if interested in the log history of a specific file E.G. git log lib/user.rb

Regenerate on change to generate "book" using asciidoctor

find . -name \*.adoc | entr -c asciidoctor book.adoc ⇒ it runs continuous updates on a file to the html loaded version (can see in browser after refresh (control R))

Command-Line Art

The Art of Command Line is great place to look for tips and tricks on having fun in the command-line.

Commands used whilst playing around with 'figlet' and 'cowsay'

  • brew search cowsay

You set up the cowsay with brew install cowsay, then pipe something to cowsay and the cow says it…

> cowsay 'Hello'
   _______
  < Hello >
   -------
          \   ^__^
           \  (oo)\_______
              (__)\       )\/\
                  ||----w |
                  ||     ||

For example, you can do fortune | cowsay and it will give the fortune via cow.

Banner with: brew install banner figlet Different font files with figlet Hippo instead of cow

How to read output from a command into VIM
:r !echo "Zeinzu" | figlet -f isometric1.flf
Where to find the fonts
cd /opt/homebrew/Cellar/figlet/2.2.5/share/figlet/fonts
How to run the test suite in background
ls **/*.rb **/*.erb | entr -r -c rspec

It says: list every file here that has a suffix of .rb or . everything here that has a suffix of .erb then pipe to it -r and -c with the next command of rspec

This is an example, can also use jest, or capyabara, or any other test suite It continuously listens for something to happen

Ag -l TODO Ag TODO will show you exact lines that they’re in

If we mark the TODO within the file immediately then we can actually use it with AG immediately,

Table 4. Use the last argument given in a command
Command Action

cd !$

Inserts the last argument of the last command executed

Practical example for the last command diven
ls /projects/KOTP/publish_book/0_publish_ruby/
cd !$ (1)
1 This will take us directly to the path (taken as an argument) with the cd command

end section

Git Version Control

Articles to read:

Git is a free and open source version control system. Github is the centralised hub that allows the remote storage of all things Git related. It is a type of Centralized Version Control System(CVCS), this is the standard for version control.

If Git is a version control system that for tracking change.

There are alternatives to Github, like sourcehut, Gitlab; but one could also build a personalized server to host versions remotely for team members to use. Github is nice for various reasons. It has a GUI interface, and there are social aspects to it that allows users to share their code with a larger audience, but teams working on specific things can use it to collaborate effectively.

We use Git to "save" the state of code if we are at a cross roads in the process, this allows us to return to that state later on should we make an error.

We’ll start with a cheat-sheet of the commands used most frequently, and note as we go…​

Setting up & Initializing a new Repository

From the local machine we initiliaze a new empty repository. This acts as a sort of wrapper that allows us to place pins at various points in the code. We do so with the following command:

git init

It is important to know that the git init command will set a wrapper for the current directory from within which it is set, as well as anychild repositories beneath it. Consider this:

> Parent directory   <------- git init
> |
> |
> |------Child directory <------- this directory will now also be
                                  encapsulated by the wrapper

When we want to copy all the code from a remote repository into one that we have created locally, we use cloning. The name is pretty explanatory, it creates a clone (double) version of the code in the remote repository and copies it to our machine. To do this, we must have the url link of that code featured on a remote repo (or hosted location) so that we can tell Git where to clone it from, otherwise it cannot know. We use the following command:

git clone [url]

Once we clone the code to our local we can now do what we please with it. However, if we would now like to host it on a remote repo or a CVCS then we must attach a new remote to it, as naturally the attached remote will be the one that it was originally cloned from.

git clone [not_my_url]
git remote -v


>[not_my_url](fetch)
>[not_my_url](push)

We look closer at how to do this later, for now just remember that that’s somebody else’s url attached, not yours.

Staging

So we have initialized an empty Git repository, meaning we now have a wrapper to track any changes that occur in our process. We make some changes to our code and would like to save the current state of where things are. We can do a few things:

git status
# to show modified files in the working directory, staged or not for the next commit

git add [file]
# we use this command to "stage" the state of a file to the next commit

git reset [file]
# we use this to unstage a file while retaining the changes in the working directory
# for instance: we have staged multiple files and would like to unstage just one in particular from the next commit (1)
git diff

git diff --staged
# we use this to visualize the difference between the current code state and what is staged but not yet committed

git commit -m "[some descriptive message]" (2)
# we use this to commit the staged content as a new commit "snapshot"
1 Why might we do this? Well, when we make our commit messages we like each commit to encapsulate one particular thing at a time, one incremental step at a time: we want to make multiple small steps/commits rather than one big one, because this means that the process is more maintainable should something go wrong: we make one small change at a time, therefore each commit should be a grouping of changes that make sense to be together. Consider the following example:
  • As a mechanic, someone brings us a car to fix

  • The dashboard is not working

  • We go through fixing each component of the electronics in the dashboard

  • Each time we fix something we start the car to see if works

  • We fix the windscreen wipers and the radio (without checking to start the car)

  • The car is no longer working

  • Because we did not check each incremental step we now do not know which fix caused the car to stop working.

  • To keep it simple when we write the commit message we should not have to use the word "and" to describe what we have changed, if we did then its probably too long.

2 Generally it is considered a better idea to use git commit -v (v stands for verbose) instead, as it allows us to stop and think about whether we are writing a meaningful commit message, whether the code is in a correct state ready for committing, and to peruse over the changes in the code. We stop and think for a moment not only because we are practitioners of zen, and meditation, but also because we want to consider whether returning to such a state would arouse confusion, or whether it is a safe place to return to. It’s a little like jumping across stepping stones if you are lucky enough to be chosen as a participant of Takeshi’s castle, you can stop at any stage - you just need to be sure that the stone you stop on is solid enough to hold your weight and that you can balance on it.
takeshi

Branches & merging

A useful way to think about git version control is to consider the wrapper as a tree of sorts. The great thing about this is that if we want to start working on a feature or part of the code without affecting the current state of our code, we can create a branch. The root branch of a git wrapper is generally called 'main', but you can set it to whatever you want - previously it was called 'master' branch, but to get away frmo the master/slave langauge it was changed to the former. The root branch of the wrapper on this very project is called 'unbroken' for instance, as ideally the root branch is the only one that cannot, and communicating that it must not, be broken/corrupt. This is particularly useful when working as part of a team, and different pairs/squads/people of that team are working on different parts of the code. To avoid getting in each others hair code is written on separate branches and then merged back onto the main in pull request - which is where those writers request the incorporation of their newly written code into the main.

git branch
# to check branches, * appears aside the branch we are currently on

git branch [name-of-new-branch]
git checkout -b [name-of-new-branch]
# to create a new branches we have these options

git checkout [name-of-branch]
# to switch to another branch and check it out into working directory

git merge [name-of-branch]
# to merge the specified branch's history into the current one (1)
1 Sometimes this can be a caveat, as changes are made to multiple branches at the same time (by different people, or by us - if we are jumping between two or more aspects of the code) then once we merge back onto the main we may have repeated commit messages, and when looking back through our commit messages things might look messy. To render them clean, we use git rebase instead, see below.

Inspecting & Comparing

git log
# to show all commits in the current branch's history (1)

git shortlog
# to view only the first line of each commit message
1 Using git log shows the first line of the commit message followed by any extraneous information we include within the verbose commit entry (git commit -v).

As a convention that we try to follow, the first line of a given commit message should not be greater than 50 characters long (4.75 is the average length of a word in English). When reading the commit messages we should be able to follow the narrative of how we came to be at any particular state in the code. the commit message, if verbose should provide the 'who? what? why? how?'.

git log branchB..branchA
# to show the commits on branchA that are not on branchB

git diff branchB..branchA
# to show the diff of what is in branchA that is not in branchB

git show [SHA key] (1)
# show any object in Git in human-readable format
1 This will only show the contents of the object in Git, meaning we will only be able to view the contents of the commit message and the diff (differences) from that state. In order to actually place ourselves at the very state of a particular message, we use: git checkout [SHA key], once we do this we will be able to move around from within the code at that very state.

Tracking Path Changes

git rm [file]

git mv [existing-path][new-path]

git log --stat -M

Ignoring Patterns

To prevent the staging or commit of certain files, we write a .gitignore file

logs/
*.notes
pattern*/
#Save a file with desired paterns as .gitignore with either direct string
matches or wildcard globs.

git config --gloabl core.excludesfile[file]
#system wide ignore pattern for all local repos

Sharing & Updating

So now we arrive at how to change the remote address to which we will push the updated code. This so that others can write to it themselves.

git remote add [alias, usually origin][url]
# add a git URL as an alias

git fetch [alias]
#fetches down all the branches from that Git remote (1)

git merge[alias]/[branch]
# merge a remote branch into your current branch to bring it up to date

git push [alias][branch]
# Transmit local branch commits to the remote repository branch

git pull
# fetch and merge any commits from the tracking remote branch
1 The difference between git pull vs git fetch :vic-says: You can use git fetch to know the changes done in the remote repo/branch since your last pull. This is useful to allow for checking before doing an actual pull, which could change files in your current branch and working copy (and potentially lose your changes, etc). If you do a fetch, you do not have to do an "actual pull", at least in the terms of doing git pull literally. Instead you can do a rebase or merge or reset using the local reference, since you just got done doing the fetch part of the pull command.

Rewriting History

Remember when we mentioned that merging made for sometimes messy commit history? This is the alternative which makes for much cleaner commit messages:

git rebase [branch]
# applies any commits of current branch ahead of specified one
# this will put the commit messages on top of the ones that already exist on another branch, but will allow us to not have duplicate messages in our commit history.

git rebase -i root
# we use when we really want to clean up our commit history (1)

git reset --hard [commit]
# clear staging area, rewrite working tree from specified commit
1 The '-i' flag stands for interactive, we enter into this mode to pick specifically which messages in the history we want to change. As we go through, we either:
  • leave commit message as be

  • modify the prefix word with 'edit' :when we want to change the contents of the commit message

  • modify the prefix word with 'squash' : when we want to "flatten"/erase that particular commit message; though be careful, doing so can cause the next commit object to break meaning that if you were to checkout the code at that particular state it may be missing some code or state required for it to function correctly.

Making Temporary Commits

We use these when we need to temporarily store written files that have not been committed yet, for instance: if we would like to checkout another branch but have not committed changes made yet.

git stash

git stash list

git stash pop

git stash drop

Reverting Commits

To revert only a specific file from a previous commit message, we can do this:

$ git checkout [revision_hash] [file_name]

This was taken from here:https://coderwall.com/p/dvdrzg/retrieve-single-file-from-old-commit-on-git


end section

Ruby

Introdution

Ruby is commonly known by its community as being "very expressive", perhaps that’s because it’s dynamically typed, or because objects (as we’ll momentarily see) can be coerced. We can change things at the language level. It might also be that, in Ruby, there are many many many ways to do anything and everything. {url-whys-chapter-2}[Some, window=_blank] say that "Vitamin R. Goes straight to the head.".

Ruby was invented by Yukihiro Matsumoto. Kon’nichi wa, Mr Matsumoto. The idea was/is to have a language that is not only "really expressive", but also really simple. That doesn’t mean simplistic, however, since we can do all manner of complex things, like this WebAssembly interpreter.

The simplicity of Ruby contributes to the idiomatic nature of it, meaning it reads slightly like natural language - it’s design is such that even a non-programmer can understand more or less what is happening. To get a really nice primer in Ruby through fun and inspiring reading see Why’s Poignant Guide. For something more serious see _The Well Grounded Rubyist.

Ruby in Practice

Syntax

Good Guides

Variables and Statements

Since a Ruby program is your own Universe, we get to choose what things are called. This is called naming. Naming is a big part of programming, because we need to give sensible and meaningful names, so other users of this world (other programmers for instance) can manipulate the world without beoming confused by what things are. A lot of wasted programming time can be traced down to choosing a bad name for a new object in a program world.

One way to give an object a name is to make a label for it, and attach the object to that lable. These labels are called variables, and the process are called variables, and the process of attachement is called assignment.

We call and expression like 1 + 2 a statement (one = 1 is a statement also).

Variables
local
@instance
@@class
CONSTANT

Messages and Interfaces

Our REPL gives us a window through which we can interact with the program world. How does that interaction happen?

Messages

We interact with the program world by sening it messages. In response to our messages, objects return something. When we’re using the REPL, the program world treats us, the programmer, as if we were just another object in the object world.

Dot syntax

The only way to send messages to objects in Ruby is with the . (dot): it means 'send this object a message', for instance: 2.integer? or 5.positive? means "send the object referenced (by the numbers 1 and 5 respectively) amessages asking it to answer if it’s an integer or not."

So 1 + 2 is actually translated to 1.+(2)

Arguments

When a messaged object requires something else to answer the message, we call that something else the argument, for instance: 1.+ ⇒ "add what?"/"what should I add?" that missing element in the message is what we call the argument.

Chaining messages

It’s ok to send lots of messages to objects, one after the other:

four.+(five).+(seven).-(one)

the queing up of messages is known as "chaining".

Flow of Control

The order in which an object executes instructions is called the control flow. One way of controlling this order is using a conditionl to control which instructions get executed and which don’t.

Another way is doing so manually, with parantheses: an object will evaluate statements in parentheses before any other statement in an instruction.

Using while and break to make games

Most games are built around loops, with some 'break' condition being 'the end of the game':

Using accumulators

An object might want to keep track of some value during a while loop. The object can’t just define a variable inside the loop:

while true do
  points_scored = 0

  if magnus_carlsen.win_game
    points_scored = points_scored + 1
  end
end
while loop conditions

An object can execute procedures in a while loop forever, if we tell it to run while true.

return

return says to an object:

Stop from executing this procedure. Return this value as the 'return value' from the procedure. It’s typically used inside a method, to say "this is what should come back from calling this method". However, we can use it to manage the control flow because instructions after a return will never be executed:

if true
  puts 1
  return 0

  puts 2
end

Other objects

what is an object

Resources:

everything is an object in ruby

Everything in Ruby is an object?

{url-ruby-everything-is-object-blog_p1}[Blog discussion on objects in Ruby( part 1 ), window=_blank]

{url-ruby-everything-is-object-blog_p2}[Blog discussion on objects in Ruby( part 2 ), window=_blank]

In Ruby, some objects are made for us. For instance, numbers are ready and waiting for us whenever we set up the Ruby world. We can just assign these pre-existent numbers to variables if we like.

We are familiar by now with a few kinds of objects:

  • integers

  • floats

  • true

  • false

what other objects exist in this world? what are they for? how do we make new objects?

nil

nil is an object that represents nothing. It can’t do much much: it just says : "there is absence of anything here"

Strings

Arrays

Objects contain things: integers conatin the value of the integer; strings contain a bit of text. Whatever an object 'contains' we call that object’s state. There are objects that can store more than one thing, we call it an Array.

The Array class creates instances that can store many other objects inside themselves. They can have objects added to them, and removed from them. they grow and they shrink, depending on how many objects they contain.

Any object in Ruby that can change its state during the course of the program we call mutable, so an Array is mutable.

Strings can be altered and manipulated, so they are mutable. Integers can’t be changed, 1 will alays represent 1: so integers are immutable.

Modifying Arrays
Adding to an Array
another_array = []
another_array.push("A short string")

another_array
# ["A short string"]
Removing elements from an Array
array = ["a", "b", "c"]

array.delete_at(1)

array
# ["a", "c"]
Removing the last element from an Array
array = [1, 2, 3]

array.pop

array
# [1, 2]
Reading Arrays
Reading a single element from array by sending the array the [] method with required index
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# To get the first element
array[0]
# 1

# To get the second element
array[1]
# 2
Access different elements of the array above using [].
array = [1, 2, 3, 4, 5, 6]

array.slice(0, 1)(1)
# [1]

array.slice(3,2)
# [4, 5]

<1>This method takes two arguments, the first argument is the starting index, the last argument is the length of the slice.

Making arrays from strings
string = String.new("this is a string")
# "this is a string"(1)

<1>This is an example of syntactic sugar. If we create a new string with string="this is a string", then behind the scenes it does the above.

Getting an element of the string
string = "Hello there"
string[0]
# "H"
Turning a string into an array
string = "Hello World!"

string.split
# ["Hello", "World!"]
Removing an element from a string
string = "Hello World!"

string.split("l")
# ["He", "", "o Wor", "d!"]
Building an array with each element
string = "Hello World!"

string.split("")
# ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d", "!"]
Arrays of arrays

Arrays can contain arrays.

groups = [
  ["Mary", "Sam"],
  ["Peter", "Kay"]
]

groups[0][0]
# "Mary"
Combing arrays
array_1 = ["What's", "the", "last", "word", "in", "this"]
array_2 = ["sentence?"]

array_1 + array_2
# ["What's", "the", "last", "word", "in", "this", "sentence?"]
Finding out how many elements
array = [1, 2, 3, 4, 5]

array.length
# 5
Checking if elements are in arrays
words = ["Hello", "there"]

words.include?("there")
# true

Hashes

Arrays and hashes are similar, they are both collections, and contain lists of elements.

Differences

  • Elements of an array are identified only by their location within array (index)

  • Elements of hash are identified by a key

For arrays we can only use integers as keys (i.e. 0, 1, 2, and 999) For hashes, however, we more commonly use string as keys instead:

favourite_things = { "sport" => "tennis", "food" => "chunky bacon" }

More commonly than this, we use symbols. Symbols are a special and very widespread object in Ruby. They are similar to strings, except they’re immutable – they can’t be changed once they’ve been set. To write a symbol, add a semicolon : before its name :my_symbol. Since we rarely want to change the keys in a hash, symbols are a perfect choice.

We can actually use any object as a key…​

Example
day = "Monday"

schedule = {
  "Monday" => "Do ruby exercises",
  "Tuesday" => "Do more ruby exercises",
  "Wednesday" => "Do some more ruby exercises",
  "Thursday" => "Do the same thing as on Monday",
  "Friday" => "Do the same thing as on Tuesday"
  }

# To do a lookup

schedule[Monday]

#> "Do ruby exercises"
Removing things from within hash
favourite_things = { :films => ["Hackers", "Titanic", "The Matrix", "CATS"] }
favourite_things[:films].delete_at(0)
favourite_things[:films].push("Now You See Me 2").push("Citizen Kane")

favourite_things
{"films"=>["Titanic", "The Matrix", "CATS", "Now You See Me 2", "Citizen Kane"]}
Grouping things in hashes

do exercise…​

Iterating over hashes
my_favourite_things = { :sport => "tennis", :music => "classical" }

my_favourite_things.each do |key, value|
  # Do something with key and value
end
# We get a result.

Methods

Most Ruby procedures are wrapped up in methods, which are defined 'inside' objects. When we call a method on an object:

1.positive?
true

That object executes a procedure inside itself:

# Inside 1
if self > 0
  return true
else
  return false
end

# It then returns the outcome of the procedure: true
  • Methods are reusable procedures.

Defining methods

We define a methodm using def

def method_name
  # Whatever the procedure does goes in here
end

Just like variables, we define a name for the method, so that we can call it later.

  • Returning values from methods

  • We have in Ruby the implicit return

def hello
  return "Hello World!"
end

# is identical to

def hello
  "Hello World!"
end
  • Empty methods procedures As nil is a Ruby object used to represent the 'absence of anything'. When we define an empty method – one with no procedure inside – Ruby will secretly add a line containing nil to it.

def do_nothing
end

# is identical to

def do_nothing
  nil
end
  • Methods often take parameters, we can provide it with 'arguments' to use during the procedure.

films_i_like = ["Eternal Sunshine", "Matrix"]

# push takes one argument
films_i_like.push("Rust and Bone")

# ["Eternal Sunshine", "Matrix", "Rust and Bone"]

Note on best practice Methods should do one thing, and do it well. The word and in a method name is a major clue that something’s been refactored poorly. This principle is called the Single Responsibility Principle.

Taking a procedure (lots of lines of code) and grouping it into a named method is an example of abstraction. Abstraction results in code that is easier to work with – using the average[1] method is definitely easier than writing all those lines of code each time – but we lose understanding of what’s happening inside the code.

  1. See Makers on Teachable (chapter 9) here.

Some notes on helpful rules of thumb for picking the right abstraction when writing methods Can you name your method in a simple way, without using the word 'and'? Does it do one thing, and nothing more? Can you name your method after what it returns, instead of what it does? For instance, average(test_scores) is a better name than averages_scores(test_scores). For another example, score(hand) is a better method name than scores_cards(hand).

If you can answer 'yes' to both 1 and 2, your method is more likely to be a good one.

Classes

Insert notes from Makers Teachable (chapter 10): mastery-curriculum.makers.tech/chapter10/

Then the rest, below…​

See this article.

Introduction to Ruby language (prose, and slightly less technical)

A Quick (and Hopefully Painless) Ride Through Ruby (with Cartoon Foxes)

"Read the following aloud to yourself

['toast', 'cheese', 'wine'].each { |food| print food.capitalize }

While Ruby may sometimes read like English, it sometimes reads as a shorter English. Fully translated into English, you might read the above as: With the words ‘toast’, ‘cheese’, and ‘wine’: take each food and print it capitalized.

— Why the Lucky Stiff
Why's Poignant Guide to Ruby

Ruby is a language of careful balance. Its creator, Yukihiro "Matz" Matsumoto, blended parts of his favourite languages ( Perl, Smalltalk, Eiffel, Ada, and Lisp ) to form a new language that balanced functional programming with imperative programming. He has often said that he is "trying to make Ruby natural, not simple," in a way that mirrors life. Building on this, he adds: Ruby is simple in appearance, but is very complex inside, just like our human body. — extract from the ruby-lang.org website

Test Driven Development (TDD)

What is it?

Part-first:

First of all, why write tests?

The reason is two-fold:

  1. It allows other programmers after us to see precisely what each component of the code does (or is supposed to do) without spending too long pulling out the hair.

  2. If ever there is an issue, a bug, the error message should tell us specifically where to look. So it reduces the scope of where we should be investigating.

Part-second:

Now the main reason for following TDD:

It also allows us to be lazy programmers, and only write as much as is necessary to pass the tests incrementally, and advance the functionality of the code one small step at a time.

See Kent Beck’s book on TDD for an in-depth look, or consider watching this video. this Robert C Martin video: The Transformation Priority Premise.

Red Green Refactor

Like a traffic light, the process of TDD goes through three stages:

Assuming we have written a test stating what we expect the result of some actionable component should be;

1.RED: We run the test, and watch it fail.

2.GREEN: We write the code necessary to pass the test.

BONUS: we intentionally break the test to check that it is actually working correctly, then we change it back.

3.REFACTOR: We refactor the code.

A short example of this process

Notice that in this case we could theoretically pass every name given in as a conditional, and just go on forever. We wrote one extra test case at the end but we might have stopped at three. We then refactored after that.

You can copy the code from the recording, and paste it in your terminal.

Testing Frameworks

We consider testing frameworks in light of being able to verify that our code does what it is intended to do. We have different ways of testing:

  1. Unit testing: individual software components are tested, an inidividual component could either be an individual function or a procedure.

  2. Integration testing: testing the interface between two or more software units or modules. The aim is to expose faults in the interaction between integrated units.Generally we do this after unit testing has been performed.

  3. End to End testing: verifying code’s deployed behaviour from a user perspective, by automating a user simulationthat interacts with your system as a black box (we will look at Capybara for this in the web section below)

For a detailed explanation of the differences between these Twilio has a great article on this here.

Unit testing with RSpec

What is it?

RSpec is a kind of programming tool called a test framework, it’s written for use with the programming language Ruby. We can use it to test that a particular component (sych as a method) does what is supposed to do by matching (or not) an expected output with the actual output. We can also use it to build our test-driving practice.

The documentation can be found here.

Setting up an RSpec project:
# This assumes you have Ruby & RVM installed. If you don't, visit:
# https://rvm.io/ to install RVM.

# First, create a directory for your project
; mkdir your-project-directory
; cd your-project-directory

# Then, we're going to get you the latest Ruby
; rvm get stable
; rvm use ruby --latest --install --default

# Next, install bundler, which manages dependencies like RSpec
; gem install bundler

# Create a bundler project
; bundle init

# And add RSpec to your dependencies
; bundle add rspec

# Generate an RSpec template
; rspec --init

# Create a folder for your implementation code
; mkdir lib

# And create a repository for your changes
; git init .
; git add .
; git commit -m "Project setup"

# Then go and create a repository on github.com
# On the next screen after you have created the repository,
# follow the "Push an existing repository from the command line" section
# To push your project to your github repository
# It will look something like this...
; git remote add origin YOUR_REMOTE_ADDRESS
; git branch -M main
; git push -u origin main

Web Frameworks

Sinatra

What is Sinatra?

Sinatra is a Domain Specific language (DSL) for creating web applications in Ruby with minimal effort.

Official docs here.
The basic syntax is:
# inside app.rb

require 'sinatra'

get '/frank-says' do
  'Put this in your pipe $ smoke it!'(1)
end
1 We would then run 'ruby app.rb', visit the localhost and see the above printed out.
How to get started.

How to Create a Ruby api with Sinatra

The aim of this exercise is to create a very basic api that we will then interact with using endpoints (basic CRUD functionality).

Main resource found here.

Creating the project
mkdir booklist

Because this app will be really simple, we will only use this file to store all of BookList source code inside.

cd booklist && touch server.rb
gem install sinatra
# server.rb
require 'sinatra'

get '/' do
  'Welcome to BookList!'
end

we run the server with: ruby server.rb

Adding Mongoid and the Book model
brew update && brew install mongodb

However that command does not work, so we use the following instead:

brew services stop mongodb-community@6.0

We reinstall mongosh and we get this message after putting in the following command: db.enableFreeMonitoring()

{
  state: 'enabled',
  message: 'To see your monitoring data, navigate to the unique URL below. Anyone you share the URL with will also be able to view this page. You can disable monitoring at any time by running db.disableFreeMonitoring().',
  url: 'https://cloud.mongodb.com/freemonitoring/cluster/6OHUFACQGG7AITWYGY5242AQWNX5Y7QQ',
  userReminder: '',
  ok: 1
}

The reason we are using mongodb is because we need it create the api, but if we were interacting with a thrid party api we would need this.

To keep this README simple, you should refer to the actual tutorial to check if everything is in tune and inline with the tutorial

the equivalent of rails console in Sinatra (apparently, according to this tutorial) is irb though that isn’t quite true, since irb is Ruby’s REPL

  • We found that according to the tutorial, it is possible to add things to the mongodb databse using the irb

By doing the following:

Book.create(title:'Foundation', author:'Isaac Admiov', isbn:'o5532293354')
Book.create(title:'Dune', author:'Frank Herbert', isbn:'0441172717')

Directly inside the irb, It outputs the information for us.

  • We then created a Gemfil to keep our dependencies in one place

Adding a Gemfile

touch Gemfile

We populate it with the necessary information:

# Gemfile
source 'https://rubygems.org'
gem 'sinatra'
gem 'mongoid'
# Required to use some advanced features of# Sinatra, like namespaces
gem 'sinatra-contrib'

We add one more gem sinatra-contrib to use the namespace feature later on. Finally, we run bundle install to get everything in place. From now on, we will use bundle exec server.rb to start our web API.

We could also have created a config.ru file and run the application with Rack but to keep this tutorial short, simply using the bundle command is easier.

Adding a Namespace

The reason this is important if we want to be able to version it and add a v2 later, for example.

The best practice here is usually to define the API version using HTTP headers. However, for tutorials, it’s much simpler to have it in the URL because we can easily test some endpoints using a browser.

Two resources on namespaces in Ruby: - geeks for geeks - the role of namespace in ruby on rails

When we try to run the new site with the new endpoint, we get the following error:

% bundle exec ruby server.rb
/Users/benjamin/.rvm/gems/ruby-3.0.0/gems/rack-2.2.4/lib/rack/handler.rb:45:in `pick': Couldn't find handler for: thin, falcon, puma, HTTP, webrick. (LoadError)
        from /Users/benjamin/.rvm/gems/ruby-3.0.0/gems/sinatra-3.0.2/lib/sinatra/base.rb:1504:in `run!'
        from /Users/benjamin/.rvm/gems/ruby-3.0.0/gems/sinatra-3.0.2/lib/sinatra/main.rb:47:in `block in <module:Sinatra>'

The solution proposes adding these things to the gemfile:

gem install thin
gem install puma
gem install reel
gem install http
gem install webrick

We will put them in the Gemfile instead and then run bundle install, to see if this fixes the problem.

After doing so, we run the command bundle install but get the following error:

An error occurred while installing eventmachine (1.2.7), and Bundler cannot continue.

In Gemfile:
  thin was resolved to 1.8.1, which depends on
    eventmachine

We try various things, but since there does not seem to be any requirement that we should be using this, we decide to remove 'thin' from the Gemfile. We then run the command and the api works.

We get this message in the terminal

% bundle exec ruby server.rb
== Sinatra (v3.0.1) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Puma version: 6.0.0 (ruby 3.0.0-p0) ("Sunflower")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 22031
* Listening on http://127.0.0.1:4567
* Listening on http://[::1]:4567
Use Ctrl-C to stop
::1 - - [18/Oct/2022:14:59:07 +0200] "GET /api/v1/books HTTP/1.1" 200 338 0.0307
::1 - - [18/Oct/2022:14:59:08 +0200] "GET /favicon.ico HTTP/1.1" 404 469 0.0009

And the api returns, exactly what we asked for:

[                                    (1)
  {
    "_id": {
      "$oid": "634d7dfc236f4434d041f47f"
    },
    "author": "Isaac Asimov",
    "isbn": "0553293354",
    "title": "Foundation"
  },
  {
    "_id": {
      "$oid": "634d7e02236f4434d041f480"
    },
    "author": "Frank Herbert",
    "isbn": "0441172717",
    "title": "Dune"
  },
  {
    "_id": {
      "$oid": "634d7e0a236f4434d041f481"
    },
    "author": "Dan Simmons",
    "isbn": "0553283685",
    "title": "Hyperion (Hyperion Cantos)"
  }
]
1 It is returned inside an array. As an array which is a collecion of json objects. This is in parsed version, though there is the also the browser verion to return it Raw.
Filtering

We want people to be able to filter by name, isbn, and author. How do we do that? We are going to use a combination of scopes and just use URL parameters to let the client filter the books. We are going to have to add some code. We are using regex to return the titles that match:

/^#{title}/

where ^ indicates the beginning of the text. It can also be turned to a case insensitive search if we use /^#{title}/i

This means that we can search for a book without giving its entire name, for instance: localhost:4567/api/v1/books?title=Foun localhost:4567/api/v1/books?isbn=[insert isbn number here]`

The search is working for the specific search queries, but the problem is that we don’t necessarily want to send all the book attributes to the client. To fix this, we are hoing to create a small Ruby class that will serialize books into JSON documents.

Creating s Book JSON serializer

Our serializer is going to be a PORO (Plain-Old Ruby Obect) with one method called as_json that will be called whenever to_json is called on an instance.

We go from having this:

[
  {
    "_id":    {
      "$oid":   "634d7e0a236f4434d041f481"
    },
    "author": "Dan Simmons",
    "isbn":   "0553283685",
    "title":  "Hyperion (Hyperion Cantos)"
  }
]

To this:

[
  {
    "id": "634d7e0a236f4434d041f481",
    "title": "Hyperion (Hyperion Cantos)",
    "author": "Dan Simmons",
    "isbn": "0553283685"
  }
]
The Show Endpoint: GET /books/abc

Now lets add the show endpoint to access the details of one specific book. If the books i not found, we want to tell the client using the HTTP status 404.

We can check that the show from the api is correct by doing: bundle exec ruby server.rb and then heading over to the website.

The Create Endpoint: POST /books

Being able to add books is an important feature for BookList. We will follow best practices for our create endpoint and return 201 to the client while using the Location header to tell the client where it can find the newly created book, if it wants to

We are going to need to create the base url, so let’s add a helper to do this. We also need a helper to parse the request body and return an error to the client if the parsing fails.

Some of the curl commands we use to check/test:

invalid json
curl -i -X POST -H "Content-Type: application/json" -d'{"title":"The Power Of Habit"' http://localhost:4567/api/v1/books

without all required parameters
curl -i -X POST -H "Content-Type: application/json" -d'{"title":"The Power Of Habit"}' http://localhost:4567/api/v1/books

with valid parameters
curl -i -X POST -H "Content-Type: application/json" -d'{"title":"The Power Of Habit", "author":"Charles Duhigg", "isbn":"081298160X"}' http://localhost:4567/api/v1/books
The Update Endpoint: PATCH /books/abc

It’s a mix between the hosw and the create endpoint.

We first retrieve the book, then update the attributes before returning the updated book.

We update the server.rb file with a new patch method inside our namespace

We use the curl command to verify the result we get from that To test the endpoint, to run those we will need to change the id used by an ide we have in our database

Some of the curl commands we use to check/test:

invalid json
curl -i -X PATCH -H "Content-Type: application/json" -d ' {"title":"Foundation"' http://localhost:4567/api/v1/books/5710b7b6fef9afa7a8e5db81

Invalid Title
curl -i -X PATCH -H "Content-Type: application/json" -d '{"title":""}' http://localhost:4567/api/v1/books/5710b7b6fef9afa7a8e5db81

Valid Title
curl -i -X PATCH -H "Content-Type: application/json" -d '{"title":"Foundation, Asimov"}' http://localhost:4567/api/v1/books/5710b7b6fef9afa7a8e5db81
The Delete Endpoint:DELETE/books/abc

We’re now 0n the last endpoint that we need. Here, we will retrieve a book, delete it if it exists and return 204 No Content to the client.

We make the curl request to delete the book from BookList

curl - i -X DELETE -H "Content-Type: application/json" http://localhost:4567/api/v1/books/5710b7b6fef9afa7a8e5db81
Simplifying our endpoints

We can see alot of repeated code in our endpoints. We can change this by using some helpers and refactoring a bit of the code of each endpoint.

Demo of the process

First part:

Second part:

Creating and Authenticating Users in a Sinatra app

Learn to authenticate users with the database and sessions

Storing users in the database

Usually, user account will be stored in a database. User accounts can also be considered a resource: we can create a new user account (signing up), read its information (account page), update it, etc.

Something to keep in mind when storing user account information, especially when it contains sensitive data (like a password), is encryption. A password should never be stored in plain text in a database, as even databases can be subjected to security breaches and attacks.

You can use the bcrypt gem in Ruby to achieve this. Make sure you store the encrypted password when inserting the user account in the databse, and check against the correct encrypted value when authenticating the user.

lib/user_repository.rb
class UserRepository
  def create(new_user)
    encrypted_password = BCrypt::Password.create(new_user.password)

    sql = '
      INSERT INTO users (email, password)
        VALUES($1, $2);
    '
    sql_params = [
      new_user.email,
      encrypted_password
    ]
  end

  def sign_in(email, submitted_password)
    user = find_by_email(email)

    return nil if user.nil?  (1)

    encrypted_submitted_password = BCrypt::Password.create(submitted_password)

    if user.password == encrypted_submitted_password
      # login success
    else
      # wrong password
    end
  end

  def find_by_email(email)
    # ...
  end
end
1 We should be saying return nil unless user
On Sessions

By design, web server can’t remember information about specific client = it only receives requests, and sends back a response.

In order to keep track about a specific client, we can use a feature of Sinatra called the session. By storing information in the sessoinm we can make it available in any subsequent requests from the same client.

Authentication is a very good example on how sessoins can be useful - here is a typical HTTP flow of a user signing in:

  1. The user (through web browser) signs in on a website using their credentials.

  2. The web browser send the HTTP request with the credentials to the server.

  3. The serve checks the credentials, usually by looking in a ddatabase.

  4. If credentials are valid, the server stores information about the signed-in user in the session and sends back the response.

  5. During any other request sent by the same client (so, by the same user), the session will contain the user information stored previously. For any other client, the sessoin will not contain this information.

So the session is a way to "remember" information about a specific user, even though the client they are using is sending many different requests to the server.

Using sessoins in Sinatra
lib/application.rb
class Application < Sinatra::Base
  configure :development do
    register Sinatra::Reloader
  end

  # This route simply returns the login page
  get '/login' do
    return erb(:login)
  end

  # This route receives login information (email and password)
  # as body parameters, and find the user in the database
  # using the email. If the password matches, it returns
  # a success page.
  post '/login' do
    email = params[:email]
    password = params[:password]

    user = UserRepository.find_by_email(email)

    # This is a simplified way of
    # checking the password. In a real
    # project, you should encrypt the password
    # stored in the database.
    if user.password == password
      # Set the user ID in session
      session[:user_id] = user.id

      return erb(:login_success)
    else
      return erb(:login_error)
    end
  end

  # This route is an example
  # of a "authenticated-only" route.
  # It can be accessed only if a user is
  # signed-in (if we have user information in session).
  get '/account_page' do
    if session[:user_id] == nil
      # No user id in the session
      # so the user is not logged in.
      return redirect('/login')
    else
      # The user is logged in, display
      # their account page.
      return erb(:account)
    end
  end
end

Rails

Hanami

Web testing

Capybara

REPL

Debugging

Object

What is self in Ruby and how to use it

See this great article.

Arrays

Methods

Access control

Ruby theory

For an indepth section on theory see the following chapter.

API (general)

SOLID principles in Ruby

See this great article. See here for an explanation by example.

Dependency injection in Ruby

Useful books 99 Bottles of OOP - Sandi Metz The Well-Grounded Rubyist - David A. Black Practical Object-Oriented Design in Ruby - Sandi Metz Eloquent Ruby - Russ Olsen Design Patterns in Ruby - Russ Olsen Refactoring: Ruby Edition - Jay Fields, Shane Harvie, Martin Fowler, Kent Beck Refactoring in Ruby - William C. Rake, Kevin Rutherford


end section

Theory

To keep things relatively clear, I will try to stick to Ruby language for this survey of basic theory in programming. I’ll talk about other languages vaguely, however, as I find that sometimes it’s easier for me to consider certain concepts in other languages - it’s sometimes the case that I haven’t explicitly encountered such and such a concept in Ruby. But I’m often reminded by my mentor that that doesn’t mean it doesn’t exist in Ruby. Ruby is commonly known by the community as being "very expressive", perhaps that’s because it’s dynamically typed, or because objects (as we’ll momentarily see) can be coerced. We can change things at the language level. It might also be that, in Ruby, there are many many many ways to do anything and everything.

Ruby was invented by a lovely Japanese man - Yukihiro Matsumoto. The idea was/is to have a language that is not only "really expressive", but also really simple. That doesn’t mean simplistic, however, since we can do all manner or complex things, like this WebAssembly interpreter.

The simplicity of Ruby contributes to the idiomatic nature of it, meaning it reads slightly like natural language - it’s design is such that even a non-programmer can understand more or less what is happening. To get a really nice primer in Ruby through fun and inspiring reading see Why’s Poignant Guide. For something more serious see _The Well Grounded Rubyist

Language paradigms

There are two big familias of programming paradigms:

  1. Declarative Programming Paradigm

    • Logic Programming Paradigm

    • Functional Programming

    • Databbase Processing

  2. Imperative Programming Paradigm

    • Procedural programming Paradigm

    • Object Oriented Programming

    • Parallel Processing Approach

Ruby falls into the Object Oriented Programming Paradigm, since everything is an object - well, almost everything…​ actually not quite, but let’s pretened for now.

What is an object?

An instance of a class. To some it’s also the root class in ruby (Object). Clases themselves descend from the Object root class.

Things that an object might have: - identity (object identifier) - State - Behaviour

In the real world: - A Dog, has state (colourm breed, name, hungry) and behaviour (barking, fetching, wagging tail) - A bike has state (current gear, current pedal cadence, current speed), behaviours (changing gear, changing pedal cadence, applying brakes)

What is OOP?

OOP is defined as a programming paradigm that relies on the concept of classes and objects.

Here’s a brief overview of what you can achieve with OOP: you can use it to structure a software program into simple, reusable code blocks (in this case usually called classes), which you then use to create individual instances of sss objects.

*The four pillars of OOP:* 1. Abstraction 2. Encapsulation 3. Polymorphism 4. Inheritance

Abstraction

What is abstraction?

To abstract something away means to hide away the implementation details inside something – sometimes a prototype, sometimes a function. So when you call the function you don’t have to understand exactly what it is doing.

Encapsulation

The definition of encapsulation is "the action of enclosing something in or as if in a capsule". Removing access to parts of your code and making things private is exactly what Encapsulation is all about (often times, people refer to it as data hiding).

What is encapsulation?

Encapsulation in OOPs may also mean restricting direct access to certain components of an object so that users can’t access the state values for all variables of a particular object. Therefore, encapsulation can be used to hide data members and functions associated with an instantiated class or object. Encapsulation is analogous to a capsule where the mixture of medicines inside the pill represents the data and methods while the hard outer shell could be though of as the class.

The concept of binding fileds (object state) and methods (behaviour) together as a single unit.

Programming languages such as Java use encapsulation in the form of classes. A class allows programmers to create objects with variables (data) and behaviours (methods or functions). Since a class consists of data and methods packed into a single unit, it is an example of encapsulation.

How is encapsulation different from abstraction?
Key Difference
  • Abstraction shows only useful data by providing the most necessary details whereas Encapsulation wraps code and data for necessary information.

  • Abstraction is focused mainly on what should be done while Encapsulation is focused on how it should be done.

  • Abstraction hides complexity by giving you a more abstract picture while Encapsulation hides internal working so that you can change it later.

  • Abstraction helps you partition the program into many independent portions whereas Encapsulation is easy to change with new requirements.

  • Comparing Encapsulation vs Abstraction, Abstraction solves problem at design level while Encapsulation solves problem at implementation level.

  • Abstraction hides the irrelevant details found in the code whereas Encapsulation helps developers to organize the entire code easily.

Polymorphism

What is polymorphism?

Polymorphism means "the condition of occurring in several different forms." That’s exactly what the fourth and final pillar is concerned with – types in the same inheritance chains being able to do different things.

The real power of polymorphism is sharing behaviours, and allowing custom overrides.

And finally…​

Inheritance

What is inheritance?.

Inheritance lets one object acquire the properties and methods of another object. In JavaScript this is done by Prototypal Inheritance.

Reusability is the main benefit here. We know sometimes that multiple places need to do the same thing, and they need to do everything the same except for one small part. This is a problem inheritance can solve.

This is where we can think about Liskov Substitution principle.

What do we mean by prototypical inheritance?

In short, it is the assignement of the behaviours of one object to another, in the case we’re we do not define the behaviours or datasets belonging to a new object we create. It is the extension of the behaviours of one object that we call the prototype to other objects.

In programming, we might want to take something and extend it.

For instance, we have a user object with its properties and methods, and want to make admin and guest as slighly modified variants of it. We’d like to reuse what we have in user, not copy/reimplement its methods, just build a new object on top of it.

Prototypal inheritance is a language feature that helps in that.

In Ruby classes and modules are two object types that allow us to do this to extend "meaning" or "structure" beyond onto other programmable things. We also have other tactics, such as the extend keyword. (Perhaps later we will have an example.)

What is a Class?

Details

If we need to build a new data type, we need to write a class for it. This class serves as a blueprint or template for your new data type.

It’s important because it lets us know what things can belong within this data type, and perhaps more importantly what behaviours or routine behaviours belong on that class. A class groups data and member functions/methods that work on those data members. Thus a class groups data and functions related to each other. We call it data encapsulation in technical language.

What is a Module?

A Module is a collection of methods, constants, and class variable. Modules are defined as a class, but with the module keyword not with class keyword.

Basically a library of sorts, where we can store data types-like with a class, except we dont instantiate an instance of a module.

Modules are about providing methods that you can use across multiple classes - think about them as "librairies" (as you would see in a Rails app). Classes are about objects; modules are about functions.

You might also use a module when you have shared methods across multiple apps (again, the library model is good here). Answer taken from

Important points about Modules:

  • You cannot inherit modules or you can’t create a subclass of a module.

  • Objects cannot be created from a module.

  • Modules are used as namespaces and as mixins.

  • All the classes are modules, but all the modules are not classes.

  • The class can use namespaces, but they cannot use mixins like modules.

  • The name of a module must start with a capital letter.

Syntax:
module Module_name
  # statements to be executed
end
Example:
# Ruby program to illustrate the module
# Creating a module with name Gfg

module Gfg

  C = 10;

  # Prefix with name of Modul
  # module method
  def Gfg.portal
    puts "Welcome to GFG Portal!"
  end

  # Prefix with the name of Module
  # module method
  def Gfg.tutorial
    puts "Ruby Tutorial!"
  end

  # Prefix with the name of Module
  # module method
  def Gfg.topic
    puts "Topic - Module"
  end
end

# displaying the value of
# module constant

puts Gfg::C

# calling the methods of the module
Gfg.portal
Gfg.tutorial
Gfg.topic
Output
10
Welcome to GFG Portal!
Ruby Tutorial!
Topic - Module

Note: - To define module method the user has to prefix the name of the module with the method name while defining the method. The benefit of defining module method is that use can call this method by simply using the name of module and dot operator as shown in above example. - A user can access the value of a module constant by using the double colon operator(::) as show in the above example. - If the user will define a method with def keyword only inside a module i.e. def method_name then it will consider an an instance method. A user cannot access instance method directly with the use of the dot operator as he cannot make the instance of the module. - To access the instance method defined inside the module, the user has to include the module inside a class and then use the class instance to access that method. Below example illustrates this concept clearly. - The user can use the module inside the class by usingg include keyword. In this case, the module works like a namespace.

Example
# Ruby program to illustrate how
# to use module inside a class

# Creating a module with name Gfg
module Gfg

  # module method
  def portal
    puts "Welcome to GFG Portal!"
  end

  # module method
  def tutorial
    puts "Ruby Tutorial!"
  End2Endd

  # module method
  def topic
    puts "Topic - Module"
  end

end


# Create class
class GeeksforGeeks

  # Include module in class
  # by usingg 'include' keyword
  include Gfg

  # Method of the class
  def add
    x = 30 + 20
    puts x
  end

end

# Creating objects of class
obj_class = GeeksforGeeks.new

# calling module methods
# with the help of GeeksforGeeksorGeeks
# class object
obj_class.portal
obj_class.tutorial
obj_class.topic

# Calling class method
obj_class.add

Use of Modules: A module is a way to categorize the methods and constants so that user can reuse them Suppose he wants to write two methods and also wants to use these methods in multiple programs. So, in this case they can write these methods in a module, so that they can easily call this module in any program with the help of require keyword without re-writing code.

What is the difference between a class and a module?

Modules serve as a mechanism for namespaces.

module ANamespace
  class AClass
    def initialize
      puts "Another object, coming right up!"
  end
end

ANamespace::AClass.new
 #=> Another object, coming right up!

Also, modules provide as a mechanism for multiple inheritance via mix-ins and cannot be instantiated like classes can.

module AMixIn
 def who_am_i?
   puts "An existntialist, that's who."
 end
end


# String is already the parent class
class DeepString < String
  # extends adds instance methods from AMixIn as class methods
  extend AMixIn
end

DeepString.who_am_i?
#=> An existentialist, that's who.

AMixIn.new
#=> NoMethodError: undefined method 'new' for AMixIn:Module
class module

instantiation

can be instantiated

can not be instantiated

usage

object creation

mixin facility. provide a namespace.

superclass

module

object

methods

class methods and instance methods

module methods and instance methods

inheritance

inherits behaviour and can be base for inheritance

No inheritance

inclusion

cannot be included

can be included in classes and modules by using the include command (includes all instance methods as instance methods in a class/module)

extension

can not extend with extend command (only with inheritance)

module can extend instance by using extend command (extends given instance with singleton methods from module)

Another explanation.

A Module is a collection of methods, constants, and class variable. Modules are defined as a class, but with the module keyword not with class keyword.

Important points about Modules:

  • You cannot inherit modules or you can’t create a subclass of a module.

  • Objects cannot be created from a module.

  • Modules are used as namespaces and as mixins.

  • All the classes are modules, but all the modules are not classes.

  • The class can use namespaces, but they cannot use mixins like modules.

  • The name of a module must start with a capital letter.

Syntax:
module Module_name
  # statements to be executed
end
Example:
# Ruby program to illustrate the module
# Creating a module with name Gfg

module Gfg

  C = 10;

  # Prefix with name of Modul
  # module method
  def Gfg.portal
    puts "Welcome to GFG Portal!"
  end

  # Prefix with the name of Module
  # module method
  def Gfg.tutorial
    puts "Ruby Tutorial!"
  end

  # Prefix with the name of Module
  # module method
  def Gfg.topic
    puts "Topic - Module"
  end
end

# displaying the value of
# module constant

puts Gfg::C

# calling the methods of the module
Gfg.portal
Gfg.tutorial
Gfg.topic
Output
10
Welcome to GFG Portal!
Ruby Tutorial!
Topic - Module

Note: - To define a module method the user has to prefix the name of the module with the method name while defining the method. The benefit of defining a module method is that the user can call this method by simply using the name of module and dot operator as shown in above example. - A user can access the value of a module constant by using the double colon operator(::) as show in the above example. - If the user will define a method with def keyword only inside a module i.e. def method_name then it will consider an an instance method. A user cannot access instance method directly with the use of the dot operator as he cannot make the instance of the module. - To access the instance method defined inside the module, the user has to include the module inside a class and then use the class instance to access that method. Below example illustrates this concept clearly. - The user can use the module inside the class by usingg include keyword. In this case, the module works like a namespace.

Example
# Ruby program to illustrate how
# to use module inside a class

# Creating a module with name Gfg
module Gfg

  # module method
  def portal
    puts "Welcome to GFG Portal!"
  end

  # module method
  def tutorial
    puts "Ruby Tutorial!"
  End2Endd

  # module method
  def topic
    puts "Topic - Module"
  end

end


# Create class
class GeeksforGeeks

  # Include module in class
  # by usingg 'include' keyword
  include Gfg

  # Method of the class
  def add
    x = 30 + 20
    puts x
  end

end

# Creating objects of class
obj_class = GeeksforGeeks.new

# calling module methods
# with the help of GeeksforGeeksorGeeks
# class object
obj_class.portal
obj_class.tutorial
obj_class.topic

# Calling class method
obj_class.add

Use of Modules: A module is a way to categorize the methods and constants so that user can reuse them Suppose he wants to write two methods and also wants to use these methods in multiple programs. So, in this case they can write these methods in a module, so that they can easily call this module in any program with the help of require keyword without re-writing code.

Strong-typed vs Dynamically-typed

Ruby is dynamically typed since type checking is done at runtime, unlike languages like C# and Java, that both have type-safety. Having type-safety means we have to declare what type of values a variable can have, what type of data it can hold (i.e. whether it is string or integer, for instance). The reason for this is that if we have a method that is alleged to do some mathematical calculation on a given argument, then we need to know it’s safe to do so, we need to know it’s possible. We can’t, for instance, have a method (a routine operation) say:

def method(argument)
  # take whatever the `argument` is and multiply it by 3
  # take the result of that and add to it the number 47
end

If we pass as an argument to this method the word (of type string) "duck", then we will receive an error message at run-time because we cannot operate numerically on string, we can only do so on an integer, or in Ruby float To avoid this sort of thing we therefore have type safety.

The type-saftey catch for JavaScript is known as TypeScript. The benefit of type safety is that any errors are caught before compiling, because these scripting languages require compilation ("building") before the code can be executed. Unlike these, Ruby compiles at run time however, and has something called duck-typing.

What is duck typing and how does it pertain to Ruby?

That an object may be acted upon even if it isn’t expected type as long as it looks and behaves like the expected object. This is a characteristic of Ruby because the lack of type checking of parameters makes this an effective programming technique. If it sounds like a duck, and walks like a duck, it must be a duck; object types are inferred and not declared

How can duck typing be useful? Let’s think about "coercion" in programming:

What does it mean to coerce an object? Why would you do it?

To coerce an object means to force it into an expected type. One migt do this in order to try and force an unknown object type into the expected type or format required by the operation. This is a common practice ivolved in duck typing.

SOLID principles

  1. S - Single Responsibility Principle (SRP): "one and only one responsibility."

  2. O - Open/Close Principle: "open for extension but closed for modification."

  3. L - Liskov’s Substitution Principle: "parent classes should be easily substituted with their child classes without blowing up the application."

  4. I - Interface Segregation Principle: "many client specific interfaces are better than one general interface."

  5. D - Dependency Inversion Principle: "classes should depend on abstraction, but not on concretion."

[insert oldPhonePad challenge at this commit message: commit f3db652a682affb447974ed40308e65413a00120 (HEAD → unbroken, main/unbroken) Author: benjamin <b.james.neustadt@gmail.com> Date: Thu Oct 13 09:49:50 2022 +0200]

SOLID principles in Ruby
Letters = {
  "1"    => '&',
  "11"   => "'",
  "111"  => '(',
  "0"    => ' ',
  "2"    => 'A',
  "22"   => 'B',
  "222"  => 'C',
  "3"    => 'D',
  "33"   => 'E',
  "333"  => 'F',
  "4"    => 'G',
  "44"   => 'H',
  "444"  => 'I',
  "5"    => 'J',
  "55"   => 'K',
  "555"  => 'L',
  "6"    => 'M',
  "66"   => 'N',
  "666"  => 'O',
  "7"    => 'P',
  "77"   => 'Q',
  "777"  => 'R',
  "7777" => 'S',
  "8"    => 'T',
  "88"   => 'U',
  "888"  => 'V',
  "9"    => 'W',
  "99"   => 'X',
  "999"  => 'Y',
  "9999" => 'Z',
  }

def oldphone(str)
  result = str.scan(/0+|1+|2+|3+|4+|5+|6+|7+|8+|9+|\*/)
  while result.include? '*'
    result.delete_at(result.index('*') - 1)
    result.delete_at(result.index('*'))
  end
  translation = result.map {|digit_input| Letters[digit_input]}
  translation.join
end

This was the state of our solution; all the logic is held inside one method. ALthough it passes, and solves the task at hand, it is not well structured, as is does not respect the single responsibility principle.

After we change it, this is what we come up with:

require_relative 'transform.rb' (1)

def oldphone(str)
  Transform.new(str).to_s       (2)
end
1 We call in the 'transfrom.rb' file so that we can create an instance of that class, upon using the oldphonepad method.
2 The method is entirely sanitized, holding as little as necessary to make it function; here it is clearer that it calls another function behind the scenes.
module RegularExpressions
  module Dictionary
  RegularExpression = {
    repeating_digits: /0+|1+|2+|3+|4+|5+|6+|7+|8+|9+|\*/   (1)
  }
  end
end

class Transform
  include RegularExpressions::Dictionary                   (2)

  Keypad_Morpheme = {                                      (3)
    "1"    => '&',
    "11"   => "'",
    "111"  => '(',
    "0"    => ' ',
    "2"    => 'A',
    "22"   => 'B',
    "222"  => 'C',
    "3"    => 'D',
    "33"   => 'E',
    "333"  => 'F',
    "4"    => 'G',
    "44"   => 'H',
    "444"  => 'I',
    "5"    => 'J',
    "55"   => 'K',
    "555"  => 'L',
    "6"    => 'M',
    "66"   => 'N',
    "666"  => 'O',
    "7"    => 'P',
    "77"   => 'Q',
    "777"  => 'R',
    "7777" => 'S',
    "8"    => 'T',
    "88"   => 'U',
    "888"  => 'V',
    "9"    => 'W',
    "99"   => 'X',
    "999"  => 'Y',
    "9999" => 'Z',
  }

  RE = RegularExpression                                   (4)
  private_constant :RE

  private

  attr_reader :message

  def initialize(encoded_message)
    @message = encoded_message.scan(RE[:repeating_digits])
    deletions
  end

  def deletions
    while message.include? '*'
      message.delete_at(message.index('*') - 1)
      message.delete_at(message.index('*'))
    end
  end

  def translate
    message.each_with_object('') do |encoding, memo|
      memo << Keypad_Morpheme[encoding]
    end
  end

  public

  def to_s
    translate
  end

end

# Library Guard
if $0 == __FILE__
  puts Transform.new('22 28')
end
1 module including the regular expression, what could be one of many
2 We bring in the module to the class
3 We bring in the constant to the class
4 We assign an abbreviation to the regular expression, only once we have called it previously.

All of the methods are separated at granular level, so that each method is only dealing with one portion of the program’s functionality.

Single Responsibility Principle

Open/Close Principle

Liskov’s Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

What is inversion control/dependency injection?*

Any nontrivial application is made up of two or more classes that collaborate with each other to perform some business logic. Traditionally, each object is responsible for obtaining its own references to the objects it collaborates with (its dependencies). When applying DI, the objects are given their dependencies at creation time by some external entity that coordinates each object in the system. In other words, dependencies are injected into objects.

Here is an example using Java:

Normally a class will depend on another class to do some work, such as above.
public class ClientA {
    ServiceB service;

    public void doSomething() {
        String info = service.getInfo();
    }
}
ClientA uses class ServiceB which is written below:
public class ServiceB {
    public String getInfo() {
        return "ServiceB’s Info";
    }
}
  • ClientA is said to be dependent on class ServiceB, which is thereby called a dependency of ClientA. This kind of dependency is trivial, though if the code gets bigger and more complex, hard.

To Be Continued…​


end section