This is the curriculum for the 2018 Foxtrot Web Developer Bootcamp.

Object Oriented Programming in Ruby

We've been talking throughout this class about how everything in Ruby is an object, but it can be difficult to visualize what that means when it comes to building a program. Let's take a moment with your Car challenge to take apart how Ruby's object-oriented behavior actually impacts you as a programmer.

Classes are made up of Nouns and Verbs

Take a look at your Vehicle class. It probably looks like a list of class attributes, followed by a list of methods. The class attributes -- things like @wheels and @model_year -- are what make your object what it is: the class Vehicle (also a noun). The methods you've written are things you can do with Vehicle and its attributes: for example, print the Vehicle information or create a new vehicle. All Ruby classes have this structure.

Classes have Relationships

In your Car challenge, you have at minimum a Vehicle class and a Car class. There can be as many Cars as you want to make, and they will share a lot of the same attributes as Vehicle. Any time you identify this kind of relationship forming, whether it's one to many or one to one, it's a strong indication that there should be inheritance between the two classes. This will allow you to share methods across related classes.

Objects are Interrelated

Creating a program in Ruby is a little bit like playing Jenga; all the bits and pieces rely on each other to stand up, and removing the wrong piece can wreak havoc. Because of their relationships, objects impact each other and you have to remember those relationships as you're writing your code. Take the example of the Vitamin class from this morning's presentation. Vitamin and Apple have a many to many relationship. There can be as many Apples with Vitamins as you want to create, and you can create them using the method you already wrote for creating items in the Fruit class, which is pretty cool. But what happens when you write a method in the Fruit class to grab all Fruit with Vitamins, and it grabs an instance of the Fruit class that doesn't inherit from Vitamin? You'll get an error. One solution to this is creating a separate array for storing items with Vitamins in Fruit, so the two types don't clash. Remembering how your objects impact each other is very important when writing applications in Ruby.

Inheritance

Class

Class can be an abstract concept when you first come across it. Let's try to clear up what class is and how it behaves. To start, "class definition" describes the structure and behavior of a set of objects.

Inheritance

Inheritance is a relationship between two classes. For instance, an Apple "is a" Fruit. Apple and Fruit have an inheritance relationship based on their shared features.
Fruit is the Superclass,"super"/bigger, because it encompasses more things (like other classes or Fruits) and is the broader class definition.

Apple would be a good example of a class further down the inheritance hierarchy. Classes further down from the superclass have more features specific to what they are -- like Apple has more specific features and characteristics than the broad umbrella Fruit -- making them more specialized.

Inheritance means that classes inherit the external interface of the superclasses. That is a slightly complicated way of saying that, in Ruby, classes can access and use their superclass' methods. The "private parts" of those methods -- the instance variables -- are not inherited (only the methods in a class can access them).

has-a, has-many

Aggregation/composition/contains, has-a (uses): one class uses another class
Has-a: singular; have-many: plural, implying a collection (for instance, array).
Classes are related through storage or operation, but not more/less.

Objects, meaning instances of a class, are realizations of a class that can be interacted with and store data. Objects are independent of each other. With a class definition, we have the blueprint for what a Fruit class or an Apple class is and can do. Objects allow us to interact with those classes directly in our code; it's the difference between reading about an apple and being handed one.

This style of programming is called object oriented, even if the programming to some extent focuses on classes.

Ruby Inheritance <

Since inheritance is a relation between two classes, to create the relation we use <. This allows the new class to get the features of the higher up class, but now you can add specific features. For example:

class Fruit

  def initialize  color
    @color = color
  end

  def color
    @color
  end

  def is_sweet
    true
  end
end

Now we will create a new class called Apple and have it inherit from Fruit.

And since we are inheriting from the Fruit class, which initializes an instance variable, and requires a parameter, we have to do the same with our Apple class. So we create an initialize method, and pass in the parameter as super. Using super will call the original method from Fruit and pass in the parameter.

class Apple < Fruit

  def initialize color
    super color + " Apple"
  end

  def spoils
    "Spoils in 7 days"
  end

end

apple_one = Apple.new("Red")
apple_one.color
=> "Red Apple"
apple_one.is_sweet
=> true
apple_one.spoils
=> "Spoils in 7 days"

Ruby Class

In Ruby, all values are objects. Anything you have any way of referring to is an object.

All objects in Ruby belong to a class. The class defines what it means to be that kind of thing, which is to say what the thing does.

Note that this class concept is not (directly) related to JavaScript's class. A Ruby class is most similar in JavaScript to a prototype, but Ruby classes are a bit different.

You can find out the class of something by calling its .class method.

> 1.class
=> Fixnum
> 1.0.class
=> Float
'hello'.class
=> String

The nice thing about Ruby's OOP capabilities is that they're deep and subtle and powerful and you can spend a lot of time getting really good at OOP and being able to do many amazing things. Yet for most purposes, you can stick to simple OOP features and get nearly anything done that you might want.

Making Your Own Class

Let's say you want to keep track of people. You would create a Person class (capitalized, and usually singular). This can be created in IRB.

class Person
  def set_given_name(name)
    @given_name = name
  end

  def get_given_name()
    @given_name
  end

end

Then you can create a new instance of the Person class. You create a new instance of a class with .new.

a_person = Person.new()
a_person.set_given_name('Bilbo')

Then you can call the methods that have stored the names:

a_person.get_given_name()
=> 'Bilbo'

Instance Variables

The variable starting with @ is an instance variable, meaning it belongs to the instance of Person.
And each instance of Person has it's own set of instance variables independent of other Persons.

You can't just access an instance variable from outside of an object, as you can in JavaScript.

> a_person.given_name
NoMethodError: undefined method `given_name' for #<Person:0x007fec7287d9b0>
> p.@given_name
SyntaxError: (irb):60: syntax error, unexpected tIVAR, expecting '('

This is why we have written accessor methods - the get... and set... methods in my class. And remember that Ruby methods return the value of whatever they last did, which is how my get... methods work.

Simplifying the Methods

To keep things simple, I've written the accessor methods in a much more cumbersome way than you would normally do it in Ruby. First, it would clearly be nicer to be able to set a variable value using the same sort of syntax we would use in JavaScript:

a_person.given_name = 'Bilbo'

And in fact we can do that. Without restarting your irb, enter this:

class Person
  def given_name=(name)
    @given_name = name
  end

  def given_name
    @given_name
  end
end

This lets you write a_person.given_name = 'Baggins', which is interpreted as aPerson.given_name=("Baggins")

For example:

>  a_person.given_name = 'Bracegirdle'
>  a_person.given_name
=> 'Bracegirdle'

Initialization

Finally, you will often want to do something at the time an object is created. Perhaps it has a property that is an array, and you want to ensure that it always starts as an empty array.

If you give your class an initialize method, it will get executed immediately when an instance of the class is created. And if it takes any arguments, you'll be required to give those when creating an instance.

class with initialize:

class Jedi

  def initialize
    @title = "Knight"
  end

  def title
    @title
  end
end

Creating a new instance of the Jedi class, which immediately initializes an instance variable with an empty array, which can than have elements pushed to it:

> chosen_one = Jedi.new
=> #<Jedi:0x007fb532a24d30 @title="Knight">
> chosen_one.title
=> "Knight"

Another example with initialize for a one-to-many/has-many relationship:

class Fruit

  def initialize
    @vitamins = []
  end

  def vitamins
    @vitamins
  end
end

New instance of the class:

generic_fruit = Fruit.new
=> #<Fruit:0x007fb532acc120 @vitamins=[]>
generic_fruit.vitamins => []            # No vitamins
generic_fruit.vitamins << "C"
generic_fruit.vitamins << "D"
generic_fruit.vitamins => ["C", "D"]

One more Example, but this one is looking for an argument to be passed into it when it is initialized. So it gives an error of wrong number of arguments if one is not passed in.

class Animal

  def initialize att
    @attitude = att
  end

  def attitude
    @attitude
  end
end

New instance of the class, which gives error.

> kitten = Animal.new
ArgumentError: wrong number of arguments (0 for 1)
from (irb):77:in `initialize'
from (irb):81:in `new'

Correct instance of the class, with passing in 1 argument.

> kitten = Animal.new :sweet
=> #<Baz:0x007fb5331de210 @attitude=:sweet>
> kitten.attitude
=> :sweet

Object Oriented Ruby Car Challenge

Process

Before starting the implemention, copy each story into the editor as a comment, and use the function recipe to guide the implementation of each method

Stories

Story: As a programmer, I can make a vehicle.
Hint: Create a class called Vehicle, and create a variable called my_vehicle which contains an object of class Vehicle.

Story: As a programmer, I can make a car.
Hint: Create a class called Car, and create a variable called my_car which contains an object of class Car.

Story: As a programmer, I can tell how many wheels a car has; default is four.
Hint: initialize the car to have four wheels, then create a method to return the number of wheels.

Story: As a programmer, I can make a Tesla car.
Hint: Create an variable called my_tesla which is of class Tesla which inherits from class Car.

Story: As a programmer, I can make a Tata car.

Story: As a programmer, I can make a Toyota car.

Story: As a programmer, I can tell which model year a vehicle is from. Model years never change.
Hint: Make model year part of the initialization.

Story: As a programmer, I can turn on and off the lights on a given Vehicle.
Hint: Create method(s) to allow programmer to turn lights on and off. Which class are the methods in?

Story: As a programmer, I can determine if the lights are on or off. Lights start in the off position.

You should be able to test the car now:
vehicle = Vehicle.new(...)
vehicle.lights_on # should return false because they start false
vehicle.lights_on = true # this should change the lights to true
vehicle.lights_on # should now return true
vehicle.lights_on = false # this should change the lights to false
vehicle.lights_on? # should return false

Story: As a programmer, I can signal left and right. Turn signals starts off.

Story: As a programmer, I can determine the speed of a car. Speed starts at 0 km/h.

Story: As a programmer, I can speed my Teslas up by 10 per acceleration.

Story: As a programmer, I can slow my Teslas down by 7 per braking.

Story: As a programmer, I can speed my Tatas up by 2 per acceleration.

Story: As a programmer, I can slow my Tatas down by 1.25 per braking.

Story: As a programmer, I can speed my Toyotas up by 7 per acceleration.

Story: As a programmer, I can slow my Toyotas down by 5 per braking.

Story: As a programmer, I can call upon a car to tell me all it's information.
Hint: Implement to_s on one or more classes. You can call a super class's to_s with super.

Story: As a programmer, I can keep a collection of two of each kind of vehicle, all from different years.
Hint: Create two of each vehicles, all from different model years, and put them into an Array.

Story: As a programmer, I can sort my collection of cars based on model year.

Story: As a programmer, I can sort my collection of cars based on model.
Hint: Sort based on class name.

Story: As a programmer, I can sort my collection of cars based on model and then year.
Hint: Find out how the spaceship operator can help you with an array.

Today's Tentative Schedule

9:15am - Stand Up

9:30am - Introduction to Ruby: Classes & Inheritance followed by challenges

12:00 noon - Lunch

1:00pm - Continue challenges

4.30pm - Review

5:00pm - Class Ends