Run the tests to make sure some of your tests are passing.
Note: Run the learn test
command to pass all the local tests for this lab. We will be altering the methods where these tests will no longer be valid.
In the first code along, we built a module called Dance
, which contained methods that we intended to be used as instances methods in the Dancer
class.
In the second code along, we built the module MetaDancing
, whose methods were intended to be used as class methods in the Kid
and Dancer
classes.
There are two drawbacks to this approach. First, if another developer looks at your modules, there is absolutely no way to determine how those methods are intended to be used. Are they class methods? Are they instance methods? Nobody knows!
Secondly, we had to build two separate modules that contained methods that were all related to the same functionality (dancing). But because there was no way to designate class methods versus instance methods, we were forced to define two separate modules, which violates the single responsibility principle. Wouldn't it be great if there was a way to define one module and specify which methods were intended as class methods and which methods as instance methods.
Guess what?? There is!! We're going to refactor the two modules into one, and use nested module namespacing to clarify our code.
module FancyDance
module InstanceMethods
def twirl
"I'm twirling!"
end
def jump
"Look how high I'm jumping!"
end
def pirouette
"I'm doing a pirouette"
end
def take_a_bow
"Thank you, thank you. It was a pleasure to dance for you all."
end
end
module ClassMethods
def metadata
"This class produces objects that love to dance."
end
end
end
First, we define our FancyDance
module. Then, inside the FancyDance
module, we define a second module, InstanceMethods
. Inside the InstanceMethods
module, we place all our methods that we intend to be used as instance methods (twirl
, jump
, pirouette
, take_a_bow
). Next, we define a second nested module (nested inside of FancyDance
) called ClassMethods
. Inside, we place the metadata
method, which we intend to be used as a class method.
So how do we use these nested modules?
class Dancer
extend FancyDance::ClassMethods
include FancyDance::InstanceMethods
end
class Kid
extend FancyDance::ClassMethods
include FancyDance::InstanceMethods
end
Note: remember to require the fancy_dance.rb
file inside the dancer.rb
and kid.rb
, just like we did with our other file requirements.
We refer to the name-spaced modules or classes with ::
. This syntax references the parent and child relationship of the nested modules.
Remember, include
is used to add functionality to our classes via instance methods. The InstanceMethods
module inside the FancyDance
module contains the methods twirl
, jump
, pirouette
, and take_a_bow
, which any instance of the Dancer
or Kid
class can do.
We can call:
angelina = Dancer.new
angelina.twirl
// returns "I'm twirling!"
angelina.jump
// returns "Look how high I'm jumping!"
buster = Kid.new
buster.jump
// returns "Look how high I'm jumping!"
buster.take_a_bow
// returns "Thank you, thank you. It was a pleasure to dance for you all."
Because we included the FancyDance::InstanceMethods
nested module, we can call those instance methods on instances of our classes.
And extend
is used to add functionality to our classes via class methods. We can now call the metadata
class method on the Dancer
and Kid
classes:
Dancer.metadata
// returns "This class produces objects that love to dance."
Kid.metadata
// returns "This class produces objects that love to dance."
Inheritance using the <
syntax, implies that a class is a type of something. A BMW
class should inherit from a Car
class because a BMW is a type of car: class BMW < Car
.
But what about the ::
that we're using for our modules? The ::
syntax just denotes a name-space. Doing BMW::Car
just gives the BMW
class access to all constants, instance methods, etc, without stating that a BMW is a type of car. The ::
syntax carries all public items over to the inheriting class or module.
That's it! Now that we are familiar with several methods of sharing code between classes, you're ready to move on to the next few labs.
If you have a module whose methods you would like to be used in another class as instance methods, then you must include the module.
If you want a module's methods to be used in another class as class methods, you must extend the module.