Lab Goals and Culture
- After finishing your courses in
Programming Principles, Programming 1, Programming with C++ and Data structures and Algorithm
, You applied for aninternship
at asoftware startup company
calledNSBT Futures
and you got hired as anengineering intern
after successfully passing the interview process. NSBT Futures
is small company with a small team that working together from all the software development cycle and may have person A working onSystem Analysis => System Design => System Implementation => System Testing => System Deployment => System Maintainance
.- The
Engineering Manager
calledPromise
, realized you tookNBIT208 or DIFT206: System Analysis and System Design
course and this your unique background convinced him to add you to not onlySystem Analysis and Design Team
but also theCore Product Software Engineering Team
andDevOps and Site Reliabity Engineer Team
. - The
The Core Product Software Engineering Team
mostly uses a language and framework calledruby and ruby on rails
which you are not familiar with and you are force to learn it on the job. which is not a big deal because you know programming already
- Student will learn to appreciate system analysis or requirement engineering and system design in system software development with ruby and it's object oriented programming paradiagm and how to collobrate with other team members like product owner or product manager or software architect or ux designer etc
Setup
- Installing Ruby through RVM(Ruby Version Manager)
- Ruby Installer for window users
- I'm sorry to break your heart window users. please if software engineering is career you want to go into in future, kindly configure your windows to support WSL or switch to mac or linux operating systems.
- Installing Git
- Sign Up on Github
- Text Editor but for web engineering we love vscode
- Ruby documentation to explore more
- Rspec Testing Framework Documentation to explore more
1. Objects, Classes, and Instances
Starting this Lab I assume you have rvm
installed and have ruby
installed and pry
installed which is optional. if you are using mac or linux you must have done the following on your terminal.
$ rvm install 3.2.2
$ rvm use 3.2.2
$ gem install pry
- gem is a ruby package manager like npm for javascript
pry
is a ruby interpreter better than default irb interpreter- you can still use the default irb but i prefer pry
- ruby 3.2.2 is the current stable verion but you can install previous version as well
- ruby 2.7. and beyond is no longer maintained, so i don't recommend using it unless you are are working on a project built ontop of this version
Learning Goals
- Describe the difference between a
class
and aninstance of that class
- Define a
class
- Store state in instance variables defined in
initialize
- Provide access to state using
attr_readers
andattr_accessors
- Use methods to provide behaviors to instances of a class
- Create a new instance of a class and call methods on that instance
Vocabulary
- Class
- Object
- Instance
- State
- Attribute
- Instance Variable
- Behavior
- Method
Warm Up
- In your notebook brainstorm a type of object and specific instances of that object. Then brainstorm 3 different attributes for those objects and 3 different behaviors of those objects.
For example:
- Type of object: Student
- Specific instances: Level 100 Student, Level 200 Student, Level 300 Student
- Attributes: Level, Age, Name, Email, ID, GPA
- Behaviors: register courses, change courses, check gpa
Classes in Ruby
Overview
In programming, a Class is something that models:
- State
- Behavior
State is what something is. Behavior is what something does. In the warm up, our Class was Student
. We modeled the state of a Student by defining the attributes Age
, ID
, email
, Name
and GPA
. We modeled the behavior of a Student by defining the methods register courses
, change courses
, check gpa
.
An Instance or Object is a concrete representation of a Class. In the previous activity, Level 100 Student
is a specific Instance of the Student Class
. We can also say that Level 100 Student
is a Student Object
. Do not get confused by the terms Instance and Object. They mean the exact same thing. Think of playing with your own shadow, you full human body is the Class(main Object) but you can cast(clone) instances(objects) of your shadow at any where.
Think of a Class like a blueprint for a house and an Instance as an actual house. The blueprint is a just an idea of how the house should be built, and the house is the realization of that blueprint.
Syntax
- The syntax for defining a class is as follows:
class NameOfClass
end
So, for example, if we wanted to create a Student class, we could do the following:
class Student
end
Notice the use of
UpperCamelCase
for the class name.Generally we will want to put more information in our classes to make them useful to us, but those two lines even with no other information will create a class.
Example - Class and Instance Syntax
- Let’s follow an example with a Employee class. I will create a directory in the
NBIT208-DIFT208-2S23
directory calledobjects_classes_and_instances
. Within that directory, I’ll create anemployee.rb
file, and put the following information into that file. (You can do what ever you like if you know what you are doing).
cd NBIT208-DIFT208-2S23
mkdir objects_classes_and_instances
cd objects_classes_and_instances
touch employee.rb
class Employee
end
- Notice that
class
is lowercase whileNameOfClass
is UpperCamelCased - In the same
objects_instances_and_classes
directory, let’s create amain.rb
file and put the code below into that
touch main.rb
require './employee'
paul = Employee.new
patrick = Employee.new
require 'pry'; binding.pry
- We can run the
main.rb
file from the command line if we are inside of ourobjects_classes_and_instances
directory by typing the following:ruby main.rb
on the terminal.
pwd
ruby main.rb
- When we run this file, our terminal should open up a pry session when it reads the line:
binding.pry
. Inside of that pry session, we’ll typepaul
and hit return to see what the variablepaul
is holding. Then, we’ll typepatrick
to see what that variable is holding.
Reflection
- How are those two things the same?
- How are they different?
Attributes in Ruby Classes
- Above we created an Enployee class and then also created specific instances of the Employee class that we held in the variables paul and patrick. Generally the objects we create will come from the same template, but each will be a unique object.
Take a look at these Employee.
Each one is different in important ways. For example, each one has its own:
- Name
- ID
- Age
We can model these attributes in code by using instance variables. Generally we define these instance variables in a special method called initialize
that is run every time a new instance of a class is created. Thus called constructor in other language like Java.
Initialize
When we run Employee.new
in Ruby, what actually happens? We can see from the last example that different Employee objects (or instances) are created. Other than that, nothing happens. If we want some specific code to run when we first create a new Employee, we need to tell Ruby what should happen when a new Employee instance (or object) is created. We do this with the initialize
method.
class Employee
def def initialize
# any code here will run each time a new instance is created
end
end
- This method is run once and only once during an Object’s lifetime, when we call new.
- Other than that,
initialize
is like any other method where we can put Ruby code
class Employee
def def initialize
puts "An Employee class implementation"
end
end
Modeling State with Attributes
- The instances of the classes we have defined so far are basically useless. Aside from their
object_id
, there is nothing unique about these instances. - Remember, a class models State and Behavior. Let’s give our Employee some state.
Example - Attributes
- Let’s add some attributes to the Employee class.
- The @ symbol before a variable name indicates that it is an
Attribute or Instance Variable
. These two terms mean the exact same thing.
class Employee
def def initialize(id, name, age)
@id = id
@name = name
@age = age
end
end
Because
attributes
are something we want to persist throughout an object’s lifetime, we typically define them inside theinitialize
method because we want them to exist as soon as the object is created.We have now created a method class that will allow us to create many different instances of Employee, each one slightly different from the last.
How do we do that in practice? Let’s update the
main.rb
file so that it includes the following:
require './employee'
paul = Employee.new(1, "Paul", 34)
patrick = Employee.new(2, "Patrick", 26)
eric = Employee.new(3, "Eric", 45)
require 'pry'; binding.pry
When we include the arguments to
.new
, Ruby will pass those arguments to the initialize method for us. Note that the arguments that we pass to new are order dependent. So, in the first example when we pass1
as the first argument, we are saying that theid
of the Employee we are creating is1
. When we pass in2
the second time we call new we are saying that the Employee that we created must have an id2
.What we have just done is a very common pattern. We gave our initialize method some arguments and we saved those arguments to instance variables. While this is a strong pattern, it is not a rule. For instance, you may want to set a variable in your initialize that has a default value that isn’t set using an argument.
class Employee
def def initialize(id, name, age)
@id = id
@name = name
@age = age
@retirement_age = 70
end
end
Practice
Create an
objects_classes_and_instances
directory, then touch aperson.rb
file and arun_person.rb
file. Define a Person class in it and create instances of that class in your runner file.Give your Person class some attributes that are set using arguments to initialize and some attributes that have default values. Make some instances of your Person class, and run you runner file.
Accessing Attributes
That’s all well and good, but what can we do with all these attributes that we’ve created They’re no good to us if we can’t use them.
Generally, the way that we access information stored in a class is by sending it messages or calling methods on that class. We do that using . syntax.
Let’s run our
main
file again and check to see what this returns
require './employee'
paul = Employee.new(1, "Paul", 34)
patrick = Employee.new(2, "Patrick", 26)
eric = Employee.new(3, "Eric", 45)
require 'pry'; binding.pry
paul.id
We should get an error that says something about the method
.id
not existing (a no method error). The syntax here is correct, but we haven’t told ourEmployee
class how to respond when it receives the message id.We can do that with methods like the ones we’ve seen before, but attributes stored as instance variables are special. We can tell our class to provide access to them using
attribute readers
. Let’s do that now.
Example - Accessing Attributes
- Let’s update our Employee class to include the lines below.
class Employee
def initialize(id, name, age)
@id = id
@name = name
@age = age
@retirement_age = 70
end
def id
@id
end
def name
@name
end
def age
@age
end
def retire_age
@retirement_age
end
end
- Let’s run our runner file again and see if you can now call
paul.id
. - type
paul.id
paul.name
paul.age
require './employee'
paul = Employee.new(1, "Paul", 34)
patrick = Employee.new(2, "Patrick", 26)
eric = Employee.new(3, "Eric", 45)
require 'pry'; binding.pry
paul.id
- Now, I should be able to call
paul.id
and get back whatever was stored in the instance variable. But wow, this class is suddenly lengthy, harder to read, and has a lot of similar work happening.A method called id returns @id, name returns @name, etc
. There’s a cleaner way to do the same thing:
class Employee
attr_reader :id, :name, :age, :retirement_age
def initialize(id, name, age)
@id = id
@name = name
@age = age
@retirement_age = 70
end
end
Let’s run our runner file again and see if you can still call
paul.id
and the other attributes.An important thing to remember is that although there is a special syntax for creating attr_readers, they are still just methods. Remember the error we got earlier was a no method error for name.
Practice
- Create
attr_readers
for the attributes in yourPerson class
. - Practice explaining to your what is happening under the hood with the
attr_readers
Other Methods
- We can also create other methods that will allow us to send other messages to our
Employee class
. For example, let’s say we wanted to add aincrease salary
to our Employee class. Let’s do that by creating a method calledincrease_salary
that will add to the previous salary.
class Employee
attr_reader :id, :name, :age, :retirement_age
def initialize(id, name, age)
@id = id
@name = name
@age = age
@retirement_age = 70
@salary = 500
end
def increase_salary(amount)
@salary + amount
end
end
Let’s update our main
file so that you:
- Create a new instance of Employee.
- Print the salary of that Employee.
- Add an increase in salary
- Print the new salary of the Employe.
class Employee
attr_reader :id, :name, :age, :retirement_age, :salary
def initialize(id, name, age)
@id = id
@name = name
@age = age
@retirement_age = 70
@salary = 500
end
def increase_salary(amount)
@salary += amount
end
end
Practice
- Create a
have_birthday
method for your Person class. This should increase the age of that person by 1. - Update your
run_person
file in a similar fashion to steps 1-4 for your Person class.
Object Interaction
- When we build more complex programs, we typically have many classes, and the instances of those classes interact in some way.
Example - Object Interaction
- Employee can be working at different Location, so we can have a location class.
class Location
attr_reader :country, :state, :city
def initialize(country, state, city)
@country = country
@state = state
@city = city
end
end
Practice
Create a Book Class
Create a
book class
. Make sure that your book class withtitle
,author
, andgenre
attributes.Once you’ve created your class, create a
run_book
file that creates three separate instances of book and saves them to variables.Check in with your partner that you’re in a similar place. Discuss an differences you have in your code.
Create a Library Class
Create a Library class. Add attributes as you wish, but the be sure to include a
@collection
instance variable that starts as anempty array
.Check in with your partner that you’re in a similar place. Discuss an differences you have in your code.
If you have time:
Add a
add_book
method that takes an instance of book and adds it to your collection.Add a
titles
method that iterates over your collection of books and returns only their titles.Add an
authors
method that iterates over your collection of books and returns the authors for each book. Can you make it so that it does not return any duplicate authors?Pretty print: add a method that prints a table of books and authors that the library has.
This will require some string manipulation to get a table to print with columns that line up
.Update your
run_book
file to create a new library, add some books to the library, and print information about their collections.
2. Intro to Testing
Learning Goals
- Understand why we use tests
- Define the stages of a test
- Define a RSpec test
- Use a variety of assertion methods
Vocabulary
- Gem
- Test
- Assertion
Warm Up
- Think back of your practical solution in (1); how did you know if your program was working?
- What are some potential drawbacks to this approach?
Test Etiquette
File Structure
- Spec files live in their own
spec directory
- Implementation code files live in a sibling
lib directory
- Spec files should reflect the class they’re testing with
_spec
appended to the file name, e.g.spec/name_of_class_spec.rb
- In your test, you’ll now require
./lib/name_of_class.rb
- Run your spec files from the
root of the project directory
, e.g.rspec spec
- If you want to run a specfic spec file you can append the location of that file to the
rspec spec
command. Sorspec spec spec/name_of_file_spec.rb
.
├── lib
| └── name_of_class.rb
└── spec
└── name_of_class_spec.rb
RSpec Setup
- RSpec is a framework used for automated testing. It helps us to do
Test Driven Development TDD
gem install rspec
- Require rspec - the easy and explicit way to run all your tests
RSpec Convention
- At the top of every spec file:
describe NameOfClass
- describe
‘#name_of_method’
- It is good practice to have another describe block for the name of method. That way we can group all assertions dealing with this method in this describe block.
- We need to have an assertion at the end of every test
- A lot of times we are going to compare if two values are equal to each other
- We do that by writing
expect(actual).to eq(expected)
where actual is the result of the method call or object querying, and expected is the value we expect it to be.
- RSpec Expectations Documentation
S.E.A.T
Each test that we create needs 4 components to be a properly built test.
- Setup - The setup of a test is all of the lines of code that need to be executed in order to verify some behavior. Because each test is run individually, we often see the same setup being created multiple times.
- Execution - The execution is the actual running of the method we are testing. This sometimes happens on the same line as the assertion, and sometimes happens prior to the assertion.
- Assertion - The verification of the behavior we are expecting. This is really the main focus of the test; without the assertion, we have no test.
- Teardown - After we complete a test, we want to delete all of our setup, and clear the scope for our next test. In RSpec this is done automatically!
cd objects_classes_and_instances
mkdir testing
cd testing
mkdir lib spec
cp ../person.rb lib
cd spec
touch person_spec.rb
cd ..
rspec spec
rspec spec spec/person_spec.rb
require 'rspec'
require './lib/person'
describe Person do
describe '#initialize' do
it 'is an instance of person' do
person = Person.new("Paul", "Offei", 44, "male")
expect(person).to be_a Person
end
it 'has a first name' do
person = Person.new("Paul", "Offei", 44, "male")
expect(person.first_name).to eq 'Paul'
end
it 'has a last name' do
person = Person.new("Paul", "Offei", 44, "male")
expect(person.last_name).to eq 'Offei'
end
it 'has age' do
person = Person.new("Paul", "Offei", 44, "male")
expect(person.age).to eq 44
end
it 'has a gender' do
person = Person.new("Paul", "Offei", 44, "male")
expect(person.gender).to eq 'male'
end
it 'has status by default' do
person = Person.new("Paul", "Offei", 44, "male")
expect(person.status).to eq 'single'
end
it 'can have different name as well' do
person = Person.new("Esi", "Osei", 24, "female", "married")
expect(person.first_name).to eq 'Esi'
expect(person.last_name).to eq 'Osei'
expect(person.age).to eq 24
expect(person.gender).to eq 'female'
expect(person.status).to eq 'married'
end
end
describe '#have birthday' do
it 'add plus one to the person age' do
person = Person.new("Paul", "Offei", 44, "male")
person.have_birthday
expect(person.age).to eq 45
end
end
end
Practice
- Apply the same techniques on
book.rb employee.rb library.rb location.rb class
3. Flow Control
Conditional Statement in Ruby
Is a Boolean statement, which is a Declarative statement that is either true or false but not both
- true
- false
[1] pry(main)> statement1 = true
=> true
[2] pry(main)> statement2 = false
=> false
[3] pry(main)> statement3 = true
=> true
[4] pry(main)> statement4 = false
=> false
Comparason Operator in Ruby
Their result always return true or false
- == (Equal to:)
= (greater than or equal to:)
- <= (less than or equal to)
(greater than)
- < (less than)
- != (not equal to)
[5] pry(main)> 2 == 2
=> true
[6] pry(main)> 3 != 5
=> true
[7] pry(main)> 7 > 5
=> true
[8] pry(main)> 99 < 34
=> false
[9] pry(main)> 44 >= 55
=> false
[10] pry(main)> 44 <= 66
=> true
Relation Operators in Ruby
- && (and) - only true when all of its statement are true
- || (or) - only false when all of its statement are false
- ! (not) - inverse of itself
[13] pry(main)> statement1 && statement2
=> false
[14] pry(main)> statement1 && statement3
=> true
[15] pry(main)> statement1 || statement1
=> true
[16] pry(main)> statement2 || statement4
=> false
[17] pry(main)> !statement1
=> false
[18] pry(main)> !statement2
=> true
Branching or Decision Making or Selection
- if
- if - else
- if - elsif .... - else
[19] pry(main)> if statement1
[19] pry(main)* puts "I Love System Analysis and Design"
[19] pry(main)* end
I Love System Analysis and Design
=> nil
[20] pry(main)> if statement2
[20] pry(main)* puts "I Love NSBT"
[20] pry(main)* else
[20] pry(main)* puts "Yeah NSBT is the best"
[20] pry(main)* end
Yeah NSBT is the best
=> nil
[21] pry(main)> if statement2
[21] pry(main)* puts "yes!"
[21] pry(main)* elsif statement4
[21] pry(main)* puts "yes yes!!"
[21] pry(main)* elsif statement1 && statement2
[21] pry(main)* puts "yes yes yes!!!!"
[21] pry(main)* elsif statement3 || statement4 && statement1
[21] pry(main)* puts "yeah yeah!!!!!"
[21] pry(main)* else
[21] pry(main)* puts "Hey hey!!"
[21] pry(main)* end
yeah yeah!!!!!
=> nil
Repeatition in Ruby with
- Times
[22] pry(main)> 5.times do
[22] pry(main)* puts "I Love you"
[22] pry(main)* end
I Love you
I Love you
I Love you
I Love you
I Love you
=> 5
[23] pry(main)> 3.times do |any_name|
[23] pry(main)* puts any_name
[23] pry(main)* end
0
1
2
=> 3
- while
[24] pry(main)> initialization = 0
=> 0
[25] pry(main)> while initialization < 5
[25] pry(main)* puts "I Like You"
[25] pry(main)* initialization += 1
[25] pry(main)* end
I Like You
I Like You
I Like You
I Like You
I Like You
=> nil
- until (is the onverse of while)
[31] pry(main)> age = 5
=> 5
[32] pry(main)> until age > 10
[32] pry(main)* puts "I'm still a baby"
[32] pry(main)* age = age + 1
[32] pry(main)* end
I'm still a baby
I'm still a baby
I'm still a baby
I'm still a baby
I'm still a baby
I'm still a baby
=> nil
- loop
[35] pry(main)> age = 10
=> 10
[36] pry(main)> loop do
[36] pry(main)* if age != 18
[36] pry(main)* puts "you are not eligible to vote"
[36] pry(main)* end
[36] pry(main)* if age % 2 == 0
[36] pry(main)* age += 2
[36] pry(main)* end
[36] pry(main)* if age == 18
[36] pry(main)* puts "woow I can vote"
[36] pry(main)* break
[36] pry(main)* end
[36] pry(main)* end
you are not eligible to vote
you are not eligible to vote
you are not eligible to vote
you are not eligible to vote
woow I can vote
=> nil
4. Scope
Scope is what you have access to and where you have access to it. Think of scope as a gateman and a house owner. House owner has her own room and the gateman to has his own room closer to the gate. both room are caled scope but the house owner can have access to the gateman room but the gateman don't have access to her room.
- Local Variable: name fish_and_chips x_axis thx1138 _x _26
- Instance Variable: @name @point1 @X @ @plan9
- Class Variable: @@total @@symtab @@N @@x_pos @@SINGLE
- Global Variable: $debug $CUSTOMER $_ $plan9 $Global
- Class Name: String ActiveRecord MyClass
- Constant Name: FEET_PER_MILE DEBUG
Global Scope
Example 1
x = 10
puts x
puts y
Example 2
x = 10
puts x
puts y
y = 20
Example 3
x = 10
def say_hello
puts "Hello World!"
end
puts x
Example 4
def print_variable
x = 4
puts x
end
x = 2
print_variable
Example 5
def print_variable
x = 4
end
x = 2
print_variable
puts x
Method Scope
Example 6
def print_variable
x = 10
puts x
end
print_variable
puts x
Example 7
def print_variable
x = 4
puts x
end
print_variable
Example 8
def print_variable
x = 4
puts x
end
x = 2
print_variable
Example 9
def print_variable
x = 4
end
x = 2
print_variable
puts x
Example 10
def print_variable(x)
puts x
end
print_variable(4)
Example 11
def print_variable(x)
puts x
end
x = 4
print_variable(x)
Example 12
def print_variable(x)
puts x
end
print_variable(2)
puts x
Example 13
def print_variable(x)
x = 4
puts x
end
print_variable(2)
puts x
Block Scope
Example 14
numbers = [1,2,3]
total = 0
numbers.each do |number|
total += number
end
p total
Example 15
numbers = [1,2,3]
total = 0
numbers.each do |number|
pizza = "yummy!"
total += number
end
p pizza
Example 15
numbers = [1,2,3]
total = 0
numbers.each do |number|
total += number
end
p number
Example 17
numbers = [1,2,3]
number = 0
numbers.each do |number|
puts number
end
Example 18
numbers = [1,2,3]
numbers.each do |number|
number = 0
puts number
end
Example 19
numbers = [1,2,3]
def number
0
end
numbers.each do |number|
puts number
end
Class Scope
- refer to examples in (1)