Ruby 's Object Oriented Programming
This workshop is important because:
- Ruby is designed for Object Oriented Programming with classes.
- Class-based OOP is much more common across languages than JavaScript's prototype-based system - even JavaScript is adopting classes.
- To understand more complex Ruby code (like Rails) one must be comfortable with classes.
After this workshop, developers will be able to:
- Define the OOP terms “class,” “instance,” and “inheritance.”
- Create your own classes and instances.
- Define attributes and methods for instances or for the class as a whole.
- Explain and implement the class-based inheritance pattern.
Before this workshop, developers should already be able to:
- Describe how JavaScript handled Object Oriented Programming.
- List two benefits of OOP.
- Write methods in Ruby.
Hashes are simple key value stores. They look a lot like JavaScript's objects.
Hash Example:
our_hash = {name: "Napoleon", fav_food: "steak", skills: ["archery", "combat", "egg farming"]}
# => {:name=>"Napoleon", :fav_food=>"steak", :skills=>["archery", "combat", "egg farming"]}
The colon notation always results in your keys being symbols, which is usually what we want. The hash rocket notation gives you more control over the types of your keys.
Ruby also has Object
s to organize methods and data. In fact, everything in Ruby is a BasicObject
. However, we almost never use plain BasicObjects
or Object
s because there are more sophisticated, specialized classes such a String
, Integer
, and Hash
.
Class Inheritance Tree:
Example:
How can we prove that the hash we just created is a BasicObject
?
# there's a built-in method for that:
our_hash.is_a? BasicObject
# => true
// constructor function sets up the type
function Car(color, model, year){
this.color = color;
this.model = model;
this.year = year;
this.wheels = 4;
}
// instance methods usually defined on prototype
Car.prototype.repaint = function(newColor){
this.color = newColor;
}
// bring in Animal class from a different 'Animals' module
import Animal from 'animals'
// define Dog class
class Dog extends Animal {
// constructor for initial behavior and state
constructor (dogName="Good Dog") {
this.name = dogName;
this.sound = 'woof';
}
// instance method for the class
speak () {
console.log(`${this.name} says ${this.sound}!`);
}
}
// an instance of Dog
let morocco = new Dog('Morocco');
morocco.speak();
// export
export Dog
Ruby uses classes for object-oriented programming. Classes are data types used to create more data. They are similar to the object types we manipulated with constructors and prototypes in JavaScript.
Classes are more common among programming languages than prototypes, so we'll go into more depth about OOP with Ruby than we did with JavaScript. This will help give us extra context for JavaScript classes from ES6, like we saw with React.
- Challenge: Create a
Monster
class in Ruby and an instance ofMonster
.
Hint: you'll have to use the Ruby reserved word
class
and thenew
method.
- Challenge Update the
Monster
class so that we see "Rawr!" when a monster is first initialized.
What should we do if we want to set attributes on the monster, such as its habitat
?
Since each monster will probably have a different habitat, this is a good candidate for an instance variable. Remember Ruby classes mark instance variables with @
.
- Challenge: Add a
habitat
instance variable and any instance methods needed to your Monster class to enable this code...
rabbit = Monster.new
# Rawr
rabbit.habitat = "Cave of Caerbannog"
rabbit.habitat
# => "Cave of Caerbannog"
Hint: Use the methods
attr_accessor
,attr_reader
, and/orattr_writer
- Challenge: Add a
threat_level
instance variable to the Monster class. Allow the user to specify a threat level when the monster is created.
dalek = Monster.new(:high)
dalek.threat_level
=> :high
Hint: use
initialize
- Challenge: Allow the user to create an instance of
Monster
without specifying a threat level. The default threat level for a new monster should be:medium
.
teletubby = Monster.new
teletubby.threat_level
=> :medium
- Challenge: Create a
habitat?
instance method forMonster
that tests whether the monster's habitat matches an argument that is passed in.
yeti = Monster.new
# Rawr!
yeti.habitat = "tundra"
yeti.habitat?("swamp")
# => false
yeti.habitat?("tundra")
# => true
Hint: use
def
to define a new method inside the class
What if I wanted a running count of all the Monsters ever created? Let's keep track with a class variable and print a message each time a new monster spawns.
- Challenge: Add a class variables to enable this code...
predator = Monster.new(:high)
# Rawr!
# 2 monsters now roam the world!
alien = Monster.new(:high)
# Rawr!
# 3 monsters now roam the world!
Hint: Create a class variable with
@@
- Challenge: Create a class method to get the current value of the monster count.
Monster.count
# => 3
Hint: Use the reserved word
self
Note: Class variables SHOULD BE AVOIDED due to their weird behavior with inheritance!
- Challenge: Add a check so that the allowed
threat_level
values at creation are:low
,:medium
,:high
, or:midnight
. If another value is passed in as the initial threat_level,raise
a runtime error.
rubber_ducky = Monster.new(:friendly)
# /stretch.rb:31:in `initialize': cannot create monster - invalid threat level friendly (RuntimeError)
- Optional Constant Part 1: Create a class constant called
THREAT_LEVELS
that is an array containing all the allowed values ofthreat_level
.
Hint: Access the class constant with
Monster::THREAT_LEVELS
.
Hint: Use
freeze
to make sure the value ofTHREAT_LEVELS
isn't changed later (necessary for array constants).
- Challenge: Create a
fight
class method forMonster
that takes in two monster instances and compares theirthreat_level
s. Thefight
method should return the monster that has the higher threat level. If they're tied, let the second monster win.
- Optional Constant Part 2: Refactor
fight
to useindex
with theTHREAT_LEVELS
array. You should be able to makefight
code shorter and simpler.
- Optional Constant Part 3: Include the
Comparable
mixin in yourMonster
class and create a custom<=>
method to compare monsters based on their threat levels. Refactorfight
to use this comparison.
- Compassion Challenge: Give your
Monster
class aname
instance variable with a getter and a setter.
Hint: only modify
initialize
as a stretch (solution not provided). If you modifyinitialize
so it takes aname
argument, update your simple test code to give each monster instance a name. Wondering how you could make thename
argument optional likethreat_level
? Look up Ruby's "keyword arguments" syntax.
- What is a class?
- What is an attribute?
- What is a method?
- What is the difference between:
- an instance variable
- a class variable
- What is the difference between:
- an instance method
- a class method
- Why do we use classes?
- Looking ahead: What is inheritance?