Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save spacemonkeyvt/e555bd9ae209a061ef57b41bf48745e7 to your computer and use it in GitHub Desktop.
Save spacemonkeyvt/e555bd9ae209a061ef57b41bf48745e7 to your computer and use it in GitHub Desktop.

Revisions

  1. David VanDusen revised this gist Jan 14, 2016. 1 changed file with 4 additions and 3 deletions.
    7 changes: 4 additions & 3 deletions ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -593,9 +593,10 @@ instead of directly accessing the instance variables `@front_gear` and `@rear_ge
    follow because your class behaves in a predictable way to users (programmers using your class.) Some code that has an
    instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are
    linked to one another in a predetermined way. This keeps the class' internal code and external, or public, API
    consistent. By using existing instance methods inside your new instance methods, the code is more robust against future
    changes, too. The way instance variables are updated can change or the instance variables can be removed an replaced by
    computed values. If that happens, then you don't have to fix any code that was using the instance variables directly.
    consistent. By using existing instance methods inside your new instance methods, the code is more resilient against
    future changes, too. The way instance variables are updated can change or the instance variables can be removed an
    replaced by computed values. If that happens, then you don't have to fix any code that was using the instance variables
    directly.

    Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with
    an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object
  2. David VanDusen revised this gist Jan 14, 2016. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -592,10 +592,10 @@ Did you notice that inside the `#gear` method the `attr_reader` methods `#front_
    instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to
    follow because your class behaves in a predictable way to users (programmers using your class.) Some code that has an
    instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are
    linked to one another in a predetermined way. By using existing instance methods inside your new instance methods, the
    code is more robust against future changes, too. The way instance variables are updated can change or the instance
    variables can be removed an replaced by computed values. If that happens, then you don't have to fix any code that was
    using the instance variables directly.
    linked to one another in a predetermined way. This keeps the class' internal code and external, or public, API
    consistent. By using existing instance methods inside your new instance methods, the code is more robust against future
    changes, too. The way instance variables are updated can change or the instance variables can be removed an replaced by
    computed values. If that happens, then you don't have to fix any code that was using the instance variables directly.

    Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with
    an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object
  3. David VanDusen revised this gist Jan 14, 2016. 1 changed file with 43 additions and 43 deletions.
    86 changes: 43 additions & 43 deletions ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -416,49 +416,49 @@ the console.

    So far all the attributes of these classes been initialized to values specified as parameters to the `#initialize`
    method. Sometimes, each new instance of a class will have a starting value for an attribute. For example, each new
    `MountainBike` might start with its breaks released. It's easy to add these kinds of attributes. You just set them to
    `MountainBike` might start with its brakes released. It's easy to add these kinds of attributes. You just set them to
    their starting value inside the `#initialize` method.

    Let's add a `breaking` attribute to the `MountainBike` class that represents whether the breaks are being held, but
    Let's add a `braking` attribute to the `MountainBike` class that represents whether the brakes are being held, but
    let's give it a default value instead of taking its initial value as a parameter.

    ```ruby
    class MountainBike < Bicycle

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    end

    end
    ```

    Now every new `MountainBike` instance starts with the breaks released. Of course, we can't read whether an instance of
    `MountainBike` is breaking, so we should add an `attr_reader` for it.
    Now every new `MountainBike` instance starts with the brakes released. Of course, we can't read whether an instance of
    `MountainBike` is braking, so we should add an `attr_reader` for it.

    ```ruby
    class MountainBike < Bicycle

    attr_reader :breaking
    attr_reader :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    end

    end
    ```

    Now we can access the value representing whether the `MountainBike` is breaking. Let's try it out.
    Now we can access the value representing whether the `MountainBike` is braking. Let's try it out.

    ```ruby
    a_mountain_bike = MountainBike.new
    puts a_mountain_bike.terrain
    puts a_mountain_bike.propulsion
    puts a_mountain_bike.breaking
    puts a_mountain_bike.braking
    ```

    Ok, its breaks are off, but how do we change that? We did the right thing by defining the breaking attribute as read
    Ok, its brakes are off, but how do we change that? We did the right thing by defining the braking attribute as read
    only first. All attributes should start as `attr_reader`s until you know for a fact that you want to expose the ability
    to update them. Exposing a "writer" method as well is as easy as using the `attr_accessor` method instead of
    `attr_reader`. This will also expose a method for updating the value of the instance variable using the equals sign
    @@ -467,11 +467,11 @@ to update them. Exposing a "writer" method as well is as easy as using the `attr
    ```ruby
    class MountainBike < Bicycle

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    end

    end
    @@ -484,17 +484,17 @@ class MountainBike < Bicycle

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    end

    # Manually defined accessor methods. It's better to use `attr_accessor` for these.

    def breaking
    @breaking
    def braking
    @braking
    end

    def breaking=(breaking)
    @breaking = breaking
    def braking=(braking)
    @braking = braking
    end

    end
    @@ -505,8 +505,8 @@ special. You can then use the following syntax for updating the value of an attr

    ```ruby
    a_mountain_bike = MountainBike.new
    a_mountain_bike.breaking = true
    puts a_mountain_bike.breaking
    a_mountain_bike.braking = true
    puts a_mountain_bike.braking
    ```

    Now instances of `MountainBike` can break. As mentioned above, not all attributes should have "writer" methods.
    @@ -521,11 +521,11 @@ let's add front and rear gears to the `MountainBike` class.
    ```ruby
    class MountainBike < Bicycle

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -541,19 +541,19 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end

    end
    ```

    Because they are only readers, we can't just add them to the `attr_accessor` call for the breaking attribute, so we
    Because they are only readers, we can't just add them to the `attr_accessor` call for the braking attribute, so we
    add a separate line for the `attr_reader`s.

    The full gear that the bike is in can be calculated using the gears on the front and rear. Let's add an instance method
    @@ -564,11 +564,11 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -600,7 +600,7 @@ using the instance variables directly.
    Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with
    an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object
    instance, and you can access its methods using dot-notation just like you can outside the method. For example, if you
    wanted to implement a `#stop` instance method on `MountainBike` that turns on the breaks, it would have to be written
    wanted to implement a `#stop` instance method on `MountainBike` that turns on the brakes, it would have to be written
    the following way.


    @@ -609,11 +609,11 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -623,14 +623,14 @@ class MountainBike < Bicycle
    end

    def stop
    self.breaking = true
    self.braking = true
    end

    end
    ```

    Note the use of `self.` in the `#stop` method. If it was omitted, then Ruby would just think you are defining a new
    local variable named `breaking` instead of invoking the `#breaking=` instance method.
    local variable named `braking` instead of invoking the `#braking=` instance method.

    There is still no way to change the gear that the `MountainBike` is in. `MountainBike` objects should only be able to
    increase or decrease their gears by one at a time. Let's implement an instance method that increases the rear gear by
    @@ -641,11 +641,11 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -655,7 +655,7 @@ class MountainBike < Bicycle
    end

    def stop
    self.breaking = true
    self.braking = true
    end

    def increase_rear_gear
    @@ -695,11 +695,11 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -709,7 +709,7 @@ class MountainBike < Bicycle
    end

    def stop
    self.breaking = true
    self.braking = true
    end

    def increase_rear_gear
    @@ -738,11 +738,11 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -752,7 +752,7 @@ class MountainBike < Bicycle
    end

    def stop
    self.breaking = true
    self.braking = true
    end

    def increase_rear_gear
    @@ -779,11 +779,11 @@ class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking
    attr_accessor :braking

    def initialize
    super('off road')
    @breaking = false
    @braking = false
    @front_gear = 1
    @rear_gear = 1
    end
    @@ -793,7 +793,7 @@ class MountainBike < Bicycle
    end

    def stop
    self.breaking = true
    self.braking = true
    end

    def increase_rear_gear
  4. David VanDusen revised this gist Jan 13, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -213,7 +213,7 @@ and `#propulsion` instance methods work as expected because they are inherited f
    inside of `Boat#initialize`, passing the parameter values, and the code inside of `Vehicle#initialize` set those values
    to instance variables.

    ## Querying inheritance (polymorphism)
    ## Querying inheritance and polymorphism

    With these two classes, there is now a new, interesting dynamic to the way you can write a program. If you have some
    code that only works when it has an instance of `Vehicle`, it will work for instances of `Vehicle` _and_ instances of
  5. David VanDusen revised this gist Jan 13, 2016. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -213,7 +213,7 @@ and `#propulsion` instance methods work as expected because they are inherited f
    inside of `Boat#initialize`, passing the parameter values, and the code inside of `Vehicle#initialize` set those values
    to instance variables.

    ## Querying inheritance
    ## Querying inheritance (polymorphism)

    With these two classes, there is now a new, interesting dynamic to the way you can write a program. If you have some
    code that only works when it has an instance of `Vehicle`, it will work for instances of `Vehicle` _and_ instances of
    @@ -240,6 +240,9 @@ display_vehicle(steam_boat)
    In the above example, `steam_boat.is_a?(Vehicle)` returns `true`, because `#is_a?` returns `true` if the class object
    that is passed into it is the same class or any of the superclasses of the object.

    The ability to use `Boat` instances when `Vehicle` instances are needed is what's known as "polymorphism". A subclass
    can be used anywhere its parent classes are needed.

    ## New attributes

    Sometimes classes that inherit from other classes will have new properties that their superclasses don't have. For
  6. David VanDusen revised this gist Jan 13, 2016. 1 changed file with 6 additions and 5 deletions.
    11 changes: 6 additions & 5 deletions ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -587,11 +587,12 @@ puts a_mountain_bike.gear

    Did you notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used
    instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to
    follow because your class behaves in a predictable way to users. Some code that has an instance of `MountainBike` can
    access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a
    predictable way. By using existing instance methods inside your new instance methods, the code is more robust against
    future changes, too, because the way instance variables are reported can change or the instance variables can be removed
    an replaced by computed values and none of the other code has to be changed.
    follow because your class behaves in a predictable way to users (programmers using your class.) Some code that has an
    instance of `MountainBike` can access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are
    linked to one another in a predetermined way. By using existing instance methods inside your new instance methods, the
    code is more robust against future changes, too. The way instance variables are updated can change or the instance
    variables can be removed an replaced by computed values. If that happens, then you don't have to fix any code that was
    using the instance variables directly.

    Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with
    an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object
  7. David VanDusen revised this gist Jan 13, 2016. 1 changed file with 15 additions and 15 deletions.
    30 changes: 15 additions & 15 deletions ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -10,10 +10,10 @@ read the output. Try writing your own driver code to try things out new concepts

    ## A simple class

    In Ruby, all objects are instances of classes. There are a lot of built in classes, but you can define your own if none
    of the built in ones are sufficient for solving the problem you're trying to solve. This is often the case when you are
    working in a very specific problem domain. For example, if you want to program a traffic simulation, you probably need
    objects representing vehicles, which don't come pre-packaged with Ruby.
    In Ruby, all objects are instances of classes. There are a lot of built-in classes, but you can define your own if none
    of the built-in ones are sufficient for the problem you're trying to solve. This is often the case when you are working
    in a very specific problem domain. For example, if you want to program a traffic simulation, you probably need objects
    representing vehicles, which don't come pre-packaged with Ruby.

    Let's define a `Vehicle` class to see how that would look.

    @@ -195,8 +195,8 @@ travel on water, so we don't need to take a parameter for it.
    The next thing is the use of the `super` keyword. The way the `super` keyword works is just like any other method call.
    It calls the method of the same name as the one that the program is currently inside of, but in the superclass. In this
    case, the keyword `super` is inside of the `#initialize` method in the `Boat` class, therefore it will call the method
    of the same name &mdash; `#initialize` &mdash; in the superclass, which is `Vehicle`. The `#initialize` method of
    `Vehicle` takes two parameters, `terrain` and `propulsion`, so they both need to be passed.
    of the same name, `#initialize`, in the superclass, which is `Vehicle`. The `#initialize` method of `Vehicle` takes two
    parameters, `terrain` and `propulsion`, so they both need to be passed.

    This is the completed `Boat` class. All it does is extend the `Vehicle` class by defaulting the `terrain` attribute to
    `'water'`. All the other instance methods, including the ones defined by `attr_reader`, are inherited and can be used
    @@ -585,13 +585,13 @@ a_mountain_bike = MountainBike.new
    puts a_mountain_bike.gear
    ```

    Notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used instead of
    directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to follow because
    your class behaves in a predictable way to users. Some code that has an instance of `MountainBike` can access all the
    `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a deterministic way. By
    using existing instance methods inside your new instance methods, the code is more robust against future changes, too,
    because the way instance variables are reported can change or the instance variables can be removed an replaced by
    computed values and none of the other code has to be changed.
    Did you notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used
    instead of directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to
    follow because your class behaves in a predictable way to users. Some code that has an instance of `MountainBike` can
    access all the `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a
    predictable way. By using existing instance methods inside your new instance methods, the code is more robust against
    future changes, too, because the way instance variables are reported can change or the instance variables can be removed
    an replaced by computed values and none of the other code has to be changed.

    Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with
    an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object
    @@ -679,7 +679,7 @@ The rear gear of a `MountainBike` as previously mentioned, can't go above `6`. W
    instance variable from going above this amount.

    The value `6` has special meaning in the context of a `MountainBike`. In cases like these, instead of hard-coding the
    value `6` into multiple methods in a class, it is defined as a constant. Constants are defined inside a class
    value `6` into multiple methods in a class, it should be defined as a constant. Constants are defined inside a class
    declaration, are named in ALL_CAPS, and can't be changed. They can be referred to inside instance methods of the class
    they're defined in by name, but from outside the class they must be prefixed with the scope resolution operator, which
    is two colons, (`::`). For example:
    @@ -823,4 +823,4 @@ To practice working with classes and inheritance in Ruby, try some of the follow
    - Think of another kind of vehicle that wasn't discussed and come up with ways to define classes that take advantage of
    inheritance to simplify working with the subclasses.

    Good luck!
    Good luck!
  8. David VanDusen created this gist Jan 13, 2016.
    826 changes: 826 additions & 0 deletions ruby_classical_inheritance_review.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,826 @@
    # Classical Inheritance in Ruby

    For programmers who are new to Object Oriented Programming, the ideas behind classical inheritance can take some
    getting used to. This article walks you through the syntax of defining class inheritance in Ruby with explanations of
    each OOP feature along the way. You will have created many inheriting classes and used them in Ruby code by the end of
    this exercise.

    Create a new Ruby code file and copy the code examples into it as you go. Run the file with the provided driver code and
    read the output. Try writing your own driver code to try things out new concepts as they're introduced.

    ## A simple class

    In Ruby, all objects are instances of classes. There are a lot of built in classes, but you can define your own if none
    of the built in ones are sufficient for solving the problem you're trying to solve. This is often the case when you are
    working in a very specific problem domain. For example, if you want to program a traffic simulation, you probably need
    objects representing vehicles, which don't come pre-packaged with Ruby.

    Let's define a `Vehicle` class to see how that would look.

    ```ruby
    class Vehicle
    end
    ```

    Every class in Ruby starts off like this. The keyword `class` is followed by the name of the kind of object in
    UpperCamelCase, and the keyword `end` on a separate line. The specifics of the class come between those lines.

    To use this class in a program, you invoke the `.new` class method. Class methods are invoked by writing the class name
    followed by a dot and then the name of the method. In many cases, you can tell when documentation is referring to a
    class method because it precedes the method name with a dot (`.`) character. Let's try it.

    ```ruby
    my_vehicle = Vehicle.new
    puts my_vehicle
    ```

    If you run the above code after defining the `Vehicle` class, you should see a representation of a `Vehicle` object
    printed to the console. Presently `Vehicle` objects aren't very useful, but we can improve that by adding
    **attributes**.

    ## Instance attributes

    Custom objects become more useful when they are given attributes. Attributes are a combination of values that are
    unique to each instance of a class and methods for reading or writing those values. For example, every vehicle instance
    might have a value representing the kind of terrain that it can travel on, or it might have a value representing how
    it's propelled. Let's add those to our `Vehicle` class.

    The first step is to initialize the data. It doesn't make sense for a `Vehicle` to not have values for those two
    attributes, so we should force the programmer to specify values for each when they are creating a new instance. We do
    this by creating an instance method named `#initialize`, which is a special method name. It gets called automatically on
    new instances of our class when the class' `.new` method is called. The number sign (`#`) is just notation to indicate
    that it is an instance method, the same way the dot indicates a class method.

    ```ruby
    class Vehicle

    def initialize(terrain, propulsion)
    @terrain = terrain
    @propulsion = propulsion
    end

    end
    ```

    Adding an `#initialize` method is often the second thing you do when defining a new class. The parameters that the
    method takes represent the values that vary from one instance of the class to another. In this case, each `Vehicle` can
    travel on different terrain and have a different method of propulsion. The values of the passed parameters are stored
    inside of instance variables so that they can be accessed later on from within the class.

    Accessing the values from within the class is useful, but it is also useful to be able to ask an instance of `Vehicle`
    what its values for these attributes are, so we need to expose them. This is done by using the `attr_reader` method.
    When `attr_reader` is called from inside a class declaration, it creates "reader" methods (methods that just return the
    value of an instance variable) for each instance variable that matches the names of the symbols that are passed in as
    parameters. For example:

    ```ruby
    class Vehicle

    attr_reader :terrain, :propulsion

    def initialize(terrain, propulsion)
    @terrain = terrain
    @propulsion = propulsion
    end

    end
    ```

    Adding the `attr_reader` statement has generated two methods, one for each parameter. This is the same as:

    ```ruby
    class Vehicle

    def initialize(terrain, propulsion)
    @terrain = terrain
    @propulsion = propulsion
    end

    # Manually defined reader methods. It's better to use `attr_reader` for these.

    def terrain
    @terrain
    end

    def propulsion
    @propulsion
    end

    end
    ```

    You can define reader methods manually like this, but it's far more concise to use `attr_reader`. Now you can create
    `Vehicle` objects that can report their values for these attributes later on, like this:

    ```ruby
    steam_train = Vehicle.new('rails', 'steam')
    puts steam_train.terrain
    puts steam_train.propulsion
    skateboard = Vehicle.new('road', 'manual')
    puts skateboard.terrain
    puts skateboard.propulsion
    ```

    Notice that the `.new` class method now requires the two `#initialize` parameters to be passed into it. Under the hood,
    `.new` passes each parameter it receives along to the `#initialize` method of the new instance.

    Now we're getting to a point where we have object that are meaningful to a real problem domain. Often you have to create
    many instances of a class to solve a problem, and several of those instances will likely have the same values for some
    of their attributes. We can use inheritance to simplify this process.

    ## Class inheritance

    Let's say we need a bunch of `Vehicle` objects that represent boats. We could define each one separately and specify
    that the terrain they travel on is `'water'`, but it would be much simpler to define a `Boat` class that inherits from
    `Vehicle` that automatically specifies that for us.

    We can start the new `Boat` class the same way we start every class.

    ```ruby
    class Boat
    end
    ```

    The next step is to define the class that we want to inherit functionality from. The notation for that is to put a
    less-than sign (`<`) and then the name of the other class after the name of the new class.

    ```ruby
    class Boat < Vehicle
    end
    ```

    Now, the `Boat` class has a reference to the `Vehicle` class as its "superclass". This can even be seen by asking the
    `Boat` class object what its superclass is.

    ```ruby
    # Should print out `Vehicle`.
    puts Boat.superclass
    ```

    Notice the capital "B" on `Boat` in that snippet. Each class can be referred to as an object and it has its own
    attributes, such as its superclass. Take a look at the superclass of the `Vehicle` class.

    ```ruby
    # Should print out `Object`.
    puts Vehicle.superclass
    ```

    If you don't specify another class to inherit from, the default superclass for a new class in Ruby is the `Object`
    class.

    So, the new `Boat` class inherits from the `Vehicle` class, and therefore can be used just like a `Vehicle`, but when
    we try to initialize a new `Boat`, it still requires two parameters. This is because there is no `#initialize` method
    defined in the `Boat` class, so when creating a new `Boat` it goes to the superclass to find an `#initialize` method to
    call. Ruby will keep going from one superclass to another to find the `#initialize` method until it gets to a class
    where one is defined, and then it will call that one. This is the same for every instance method that you call on an
    object.

    To specify all boats use the `'water'` terrain, we can hard-code that into the `#initialize` method of `Boat`.

    ```ruby
    class Boat < Vehicle

    def initialize(propulsion)
    super('water', propulsion)
    end

    end
    ```

    There are a few things going on in the above class definition. The first is that we defined an instance method named
    `#initialize` that takes one argument: `propulsion`. This is the method that will be invoked when we call `Boat.new`.
    It takes this argument because different `Boat` objects can have different methods of propulsion. However, all boats
    travel on water, so we don't need to take a parameter for it.

    The next thing is the use of the `super` keyword. The way the `super` keyword works is just like any other method call.
    It calls the method of the same name as the one that the program is currently inside of, but in the superclass. In this
    case, the keyword `super` is inside of the `#initialize` method in the `Boat` class, therefore it will call the method
    of the same name &mdash; `#initialize` &mdash; in the superclass, which is `Vehicle`. The `#initialize` method of
    `Vehicle` takes two parameters, `terrain` and `propulsion`, so they both need to be passed.

    This is the completed `Boat` class. All it does is extend the `Vehicle` class by defaulting the `terrain` attribute to
    `'water'`. All the other instance methods, including the ones defined by `attr_reader`, are inherited and can be used
    on instances of `Boat`.

    ```ruby
    speed_boat = Boat.new('internal combustion')
    puts speed_boat.terrain
    puts speed_boat.propulsion
    ```

    `Boat.new` takes one parameter because the `#initialize` method in the `Boat` class takes one parameter. The `#terrain`
    and `#propulsion` instance methods work as expected because they are inherited from `Vehicle` and `super` was called
    inside of `Boat#initialize`, passing the parameter values, and the code inside of `Vehicle#initialize` set those values
    to instance variables.

    ## Querying inheritance

    With these two classes, there is now a new, interesting dynamic to the way you can write a program. If you have some
    code that only works when it has an instance of `Vehicle`, it will work for instances of `Vehicle` _and_ instances of
    `Boat`. Boats _are_ vehicles, after all. You can prove it by using the `#is_a?` method, which is inherited from `Object`
    by all the classes you define.

    ```ruby
    # Prints out details about the passed `vehicle` parameter.
    # Raises an ArgumentError if `vehicle` is not a `Vehicle`.
    def display_vehicle(vehicle)

    # Raises an error if `vehicle.is_a?(Vehicle)` returns false.
    # Note the use of capital "V" `Vehicle` here - the class object.
    raise ArgumentError, 'vehicle parameter must be a Vehicle' unless vehicle.is_a?(Vehicle)

    # Displays information about the passed `Vehicle`.
    puts "The vehicle can travel on #{vehicle.terrain} and is propelled by #{vehicle.propulsion}."
    end

    steam_boat = Boat.new('steam')
    display_vehicle(steam_boat)
    ```

    In the above example, `steam_boat.is_a?(Vehicle)` returns `true`, because `#is_a?` returns `true` if the class object
    that is passed into it is the same class or any of the superclasses of the object.

    ## New attributes

    Sometimes classes that inherit from other classes will have new properties that their superclasses don't have. For
    example, a sail boat is always propelled by wind, so it makes sense to define a `SailBoat` class, but it may also have
    an attribute that holds the number of sails that the boat has. Let's define this as a new class. The first step, as
    usual, is the basic class declaration.

    ```ruby
    class SailBoat
    end
    ```

    After that, specify the superclass.

    ```ruby
    class SailBoat < Boat
    end
    ```

    In this case, we're inheriting from `Boat` because a sail boat _is a_ boat. However, we need to specify that all
    `SailBoat` objects have `'wind'` as their propulsion.

    ```ruby
    class SailBoat < Boat

    def initialize
    super('wind')
    end

    end
    ```

    The superclass `#initialize` method in the `Boat` class only takes one parameter, the propulsion, so we only pass it
    one. Now we're ready to add a new attribute to this class, the number of sails.

    ```ruby
    class SailBoat < Boat

    def initialize(number_of_sails)
    super('wind')
    @number_of_sails = number_of_sails
    end

    end
    ```

    Just like the attributes we defined earlier, we add a parameter to the `#initialize` method because the number of sails
    can vary from one `SailBoat` to another. Then we save the value that was passed in to an instance variable so that it
    can be used later.

    Right now, there's no way to ask a `SailBoat` how many sails it has after it's instantiated, so we can add an
    `attr_reader` method for that instance variable.

    ```ruby
    class SailBoat < Boat

    attr_reader :number_of_sails

    def initialize(number_of_sails)
    super('wind')
    @number_of_sails = number_of_sails
    end

    end
    ```

    Now we have a useful `SailBoat` class that can be initialized with a number of sails. Let's give it a try.

    ```ruby
    schooner = SailBoat.new(7)
    puts schooner.terrain
    puts schooner.propulsion
    puts schooner.number_of_sails
    ```

    Instances of `SailBoat` have inherited methods as well as methods of its own, such as `#number_of_sails`. When they are
    invoked on an instance, Ruby looks up which one to use by first looking at the class of the object itself, then the
    superclass if it couldn't find the method, then the superclass of the superclass, and so on.

    ## Class inheritance practice

    Let practice declaring classes using inheritance. This time we'll define a `Bicycle` class that is propelled manually,
    and a `Fixie` class that can travel on `'road'` and a `MountainBike` class that can travel `'off road'`.

    First, declare the `Bicycle` class.

    ```ruby
    class Bicycle
    end
    ```

    Then, inherit from `Vehicle`.

    ```ruby
    class Bicycle < Vehicle
    end
    ```

    Then, default the `propulsion` parameter to `'manual'`.

    ```ruby
    class Bicycle < Vehicle

    def initialize(terrain)
    super(terrain, 'manual')
    end

    end
    ```

    The `terrain` parameter is still necessary and is passed along to the `Vehicle#initialize` method to be saved as an
    instance variable. Next, define the `Fixie` class.

    ```ruby
    class Fixie
    end
    ```

    Then, inherit from `Bicycle`.

    ```ruby
    class Fixie < Bicycle
    end
    ```

    Then, default the `terrain` parameter to `'road'`.


    ```ruby
    class Fixie < Bicycle

    def initialize
    super('road')
    end

    end
    ```

    Only one parameter has to be passed to `super` because the superclass `Bicycle` only takes one parameter to its
    `#initialize` method. Next, define the `MountainBike` class.

    ```ruby
    class MountainBike
    end
    ```

    Then, inherit from `Bicycle`.

    ```ruby
    class MountainBike < Bicycle
    end
    ```

    Finally, default the `terrain` parameter to `'off road'`.

    ```ruby
    class MountainBike < Bicycle

    def initialize
    super('off road')
    end

    end
    ```

    Now you can make two different kinds of bikes and neither of the `Fixie` or `MountainBike` `#initialize` methods take
    any parameters, so they're super easy to create! Try them out by writing your own driver code that produces output to
    the console.

    ## Default and writable attributes

    So far all the attributes of these classes been initialized to values specified as parameters to the `#initialize`
    method. Sometimes, each new instance of a class will have a starting value for an attribute. For example, each new
    `MountainBike` might start with its breaks released. It's easy to add these kinds of attributes. You just set them to
    their starting value inside the `#initialize` method.

    Let's add a `breaking` attribute to the `MountainBike` class that represents whether the breaks are being held, but
    let's give it a default value instead of taking its initial value as a parameter.

    ```ruby
    class MountainBike < Bicycle

    def initialize
    super('off road')
    @breaking = false
    end

    end
    ```

    Now every new `MountainBike` instance starts with the breaks released. Of course, we can't read whether an instance of
    `MountainBike` is breaking, so we should add an `attr_reader` for it.

    ```ruby
    class MountainBike < Bicycle

    attr_reader :breaking

    def initialize
    super('off road')
    @breaking = false
    end

    end
    ```

    Now we can access the value representing whether the `MountainBike` is breaking. Let's try it out.

    ```ruby
    a_mountain_bike = MountainBike.new
    puts a_mountain_bike.terrain
    puts a_mountain_bike.propulsion
    puts a_mountain_bike.breaking
    ```

    Ok, its breaks are off, but how do we change that? We did the right thing by defining the breaking attribute as read
    only first. All attributes should start as `attr_reader`s until you know for a fact that you want to expose the ability
    to update them. Exposing a "writer" method as well is as easy as using the `attr_accessor` method instead of
    `attr_reader`. This will also expose a method for updating the value of the instance variable using the equals sign
    (`=`). This is how it looks.

    ```ruby
    class MountainBike < Bicycle

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    end

    end
    ```

    It's a very simple change. The `attr_accessor` method creates two instance methods that are equivalent to the following.

    ```ruby
    class MountainBike < Bicycle

    def initialize
    super('off road')
    @breaking = false
    end

    # Manually defined accessor methods. It's better to use `attr_accessor` for these.

    def breaking
    @breaking
    end

    def breaking=(breaking)
    @breaking = breaking
    end

    end
    ```

    In Ruby, defining a method whose name ends with the equals sign, including automatically generated "writer" methods, is
    special. You can then use the following syntax for updating the value of an attribute on an object.

    ```ruby
    a_mountain_bike = MountainBike.new
    a_mountain_bike.breaking = true
    puts a_mountain_bike.breaking
    ```

    Now instances of `MountainBike` can break. As mentioned above, not all attributes should have "writer" methods.
    Generally you will define your own instance methods on a class that update the values of instance variables.

    ## Custom instance methods

    Most of the functionality of the classes you create will be inside of instance methods. The instance methods you define
    will typically change the values of instance variables that were initialized when the object was created. To see this,
    let's add front and rear gears to the `MountainBike` class.

    ```ruby
    class MountainBike < Bicycle

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    end
    ```

    When a new `MountainBike` is created, its front and rear gears are both in position `1`. Let's expose these using
    `attr_reader`.

    ```ruby
    class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    end
    ```

    Because they are only readers, we can't just add them to the `attr_accessor` call for the breaking attribute, so we
    add a separate line for the `attr_reader`s.

    The full gear that the bike is in can be calculated using the gears on the front and rear. Let's add an instance method
    that gets the actual gear that the bike is in.

    ```ruby
    class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    def gear
    (front_gear - 1) * 6 + rear_gear
    end

    end
    ```

    The maximum rear gear is `6`, so the full gear number can be calculated as in the example above. The `#gear` instance
    method should return `1` for each new `MountainBike` until the gears are changed.

    ```ruby
    a_mountain_bike = MountainBike.new
    puts a_mountain_bike.gear
    ```

    Notice that inside the `#gear` method the `attr_reader` methods `#front_gear` and `#rear_gear` are being used instead of
    directly accessing the instance variables `@front_gear` and `@rear_gear`? This is a useful convention to follow because
    your class behaves in a predictable way to users. Some code that has an instance of `MountainBike` can access all the
    `#front_gear`, `#rear_gear`, and `#gear` methods, and their values are linked to one another in a deterministic way. By
    using existing instance methods inside your new instance methods, the code is more robust against future changes, too,
    because the way instance variables are reported can change or the instance variables can be removed an replaced by
    computed values and none of the other code has to be changed.

    Be careful when following this convention, though. If the method you're using is a "writer", that is, the name ends with
    an equals sign, then you **must** prefix the method name with `self.`. The `self` keyword refers to the current object
    instance, and you can access its methods using dot-notation just like you can outside the method. For example, if you
    wanted to implement a `#stop` instance method on `MountainBike` that turns on the breaks, it would have to be written
    the following way.


    ```ruby
    class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    def gear
    (front_gear - 1) * 6 + rear_gear
    end

    def stop
    self.breaking = true
    end

    end
    ```

    Note the use of `self.` in the `#stop` method. If it was omitted, then Ruby would just think you are defining a new
    local variable named `breaking` instead of invoking the `#breaking=` instance method.

    There is still no way to change the gear that the `MountainBike` is in. `MountainBike` objects should only be able to
    increase or decrease their gears by one at a time. Let's implement an instance method that increases the rear gear by
    `1`.

    ```ruby
    class MountainBike < Bicycle

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    def gear
    (front_gear - 1) * 6 + rear_gear
    end

    def stop
    self.breaking = true
    end

    def increase_rear_gear
    @rear_gear += 1
    end

    end
    ```

    The new `#increase_rear_gear` method can't use a "writer" method like the `#stop` method did because there is no
    writer method for the `@rear_gear` instance variable. Let's try this method out.

    ```ruby
    a_mountain_bike = MountainBike.new
    puts a_mountain_bike.gear
    a_mountain_bike.increase_rear_gear
    puts a_mountain_bike.gear
    ```

    Alright, now we can increase the rear gear and the total gear goes up.

    ## Class constants

    The rear gear of a `MountainBike` as previously mentioned, can't go above `6`. We need to prevent the `@rear_gear`
    instance variable from going above this amount.

    The value `6` has special meaning in the context of a `MountainBike`. In cases like these, instead of hard-coding the
    value `6` into multiple methods in a class, it is defined as a constant. Constants are defined inside a class
    declaration, are named in ALL_CAPS, and can't be changed. They can be referred to inside instance methods of the class
    they're defined in by name, but from outside the class they must be prefixed with the scope resolution operator, which
    is two colons, (`::`). For example:

    ```ruby
    class MountainBike < Bicycle

    MAX_REAR_GEAR = 6

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    def gear
    (front_gear - 1) * MAX_REAR_GEAR + rear_gear
    end

    def stop
    self.breaking = true
    end

    def increase_rear_gear
    @rear_gear += 1
    end

    end
    ```

    The `6` that was previously being used in `#gear` has been replaced by the new constant `MAX_REAR_GEAR`. You can get
    the value of this constant from outside the `MountainBike` class like so:

    ```
    puts MountainBike::MAX_REAR_GEAR
    ```

    Note the use of the scope resolution operator, `::`. This can be useful if you need to access the value of the constant
    from another class.

    Now we can prevent `@rear_gear` from going over `MAX_REAR_GEAR` like this:

    ```ruby
    class MountainBike < Bicycle

    MAX_REAR_GEAR = 6

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    def gear
    (front_gear - 1) * MAX_REAR_GEAR + rear_gear
    end

    def stop
    self.breaking = true
    end

    def increase_rear_gear
    @rear_gear += 1 if rear_gear < MAX_REAR_GEAR
    end

    end
    ```

    Note the use of the `#rear_gear` method again. The instance variable is just being read here, so we can use the
    `attr_reader` that is defined.

    ## Class methods

    Sometimes there are actions that can be thought of as being done by the class of objects as opposed to individual
    instances. In these cases, class methods are used. For example, the `.new` method is an action of the `MountainBike`
    class. We could add a new action to the `MountainBike` class if we wanted to. Here's an example of adding a
    `.repair_kit` class method thar returns an array of tools for fixing `MountainBike`s.

    ```ruby
    class MountainBike < Bicycle

    MAX_REAR_GEAR = 6

    attr_reader :front_gear, :rear_gear

    attr_accessor :breaking

    def initialize
    super('off road')
    @breaking = false
    @front_gear = 1
    @rear_gear = 1
    end

    def gear
    (front_gear - 1) * MAX_REAR_GEAR + rear_gear
    end

    def stop
    self.breaking = true
    end

    def increase_rear_gear
    @rear_gear += 1 if rear_gear < MAX_REAR_GEAR
    end

    def self.repair_kit
    ['wrench', 'pliers', 'pump']
    end

    end
    ```

    As you can see, the method name starts with `self.`. This is different from the `self.` earlier, which was _inside_ an
    instance method and therefore referred to the instance. When `self.` appears after `def` or inside a class method, it
    refers to the class object itself, in this case `MountainBike`. Now, the `MountainBike` class object has a new method
    that can be called on it.

    ```ruby
    puts MountainBike.repair_kit
    ```

    As you can see, no instance of the class had to be created. The method is used on the class object itself.

    ## More practice

    To practice working with classes and inheritance in Ruby, try some of the following.

    - Finish the `MountainBike` methods for increasing and decreasing the front and rear gears, making sure that the gears
    can't go below `1` and the front gear can't go above `3`.
    - Think of another kind of vehicle that wasn't discussed and come up with ways to define classes that take advantage of
    inheritance to simplify working with the subclasses.

    Good luck!