Setup

All existing tests should pass. Optionally, run the tests continuously while developing by running guard

Namespacing

Modules are used to namespace Ruby classes. For example, if we had this code:

1234

moduleSampleclassHelloendend

The class Hello would be wrapped in the Sample namespace. That means when we want to create an instance of Hello instead of:

1

h=Hello.new

We would prefix it with the namespace and two colons:

1

h=Sample::Hello.new

The primary purpose of namespacing classes is to avoid collision. If I write a library that has an Asset class, for instance, and you use my library in a program which already has an Asset class, the two class definitions will merge into one Frankenstein class, usually causing unexpected behavior.

If, instead, I wrap mine into a namespace Packager, then my Packager::Asset in the library and your Asset class can happily coexist.

In Rails

Modules can be used to namespace a group of related Rails models:

123456789101112131415

# In packager/asset.rbmodulePackagerclassAsset<ActiveRecord::Baseendend# In packager/text.rbmodulePackagerclassText<ActiveRecord::Baseendend# Used in a controller:asset=Packager::Asset.newtext=Packager::Text.new

Typically the classes would be stored in a subfolder of models with the name of the namespace, so here app/models/packager/*.rb

Common Code

The more common usage of modules in Rails is to share common code. These sometimes go by the nickname "mix-ins", but that just means modules.

Inheritance, Modules, and Rails

Ruby implements a single inheritance model, so a given class can only inherit from one other class. Sometimes, though, it’d be great to inherit from two classes. Modules can cover that need.

In ActiveRecord, inheritance leads into Single Table Inheritance (STI). STI sounds like a good idea, then you end up ripping it out as the project matures. It just isn’t a strong design practice.

Instead, we can mimic inheritance using modules and allow each model to have its own table.

Instance Methods

Let’s look at a scenario where two classes could share an instance method. You’ll find these methods implemented in the sample project’s models:

The word_count method is obviously repeated verbatim. We could imagine that, in the future, we might want to modify the word count method so it doesn’t include "a", "and", "or", etc. Or we want to pass in a word and have it tell us how many times that word appears in the document.

These changes will mean changing the same code in two places, and that’s a recipe for regression bugs. Instead, we extract the common code.

Creating the Module

First, we define the module. It can live in /app/models or another subfolder if you prefer:

Previously we used include to add the module methods as instance methods. Here, we use extend to add the methods in the module as class methods to the extending class. Our functionality, like Article.total_word_count would be the same.

Sharing a Module

These two modules are really related to the same domain concept, so let’s figure out how to implement the same functionality with just one module.

But this won’t work. The self.total_word_count is defined on the module, not on the including class. We need to do more in the module.

The self.included Method

Our module can define a self.included method which will be automatically triggered when the module is included into a class. It usually looks like this:

12345

moduleMyModuledefself.included(including_class)endend

The parameter including_class is a reference to the class which is including this module.

How does this help us? To define our class methods previously we used extend. With the included method, we have a reference to the including class so we can tell that class to extend our class methods.

But extend expects a module. We can wrap our class methods into a nested module like this:

The block passed to included is executed as though it were in a class_eval.

Interior Modules

If our module follows the pattern of defining class methods in an interior module named ClassMethods, ActiveSupport::Concern will automatically extend the including class with that module. So we can omit the call to extend in our included method:

ActiveSupport::Concern allowed us to save a few lines of "boilerplate" code in the module.

Exercises

Define the TextContent module as described above.

Include the module into both Comment and Article models.

Pull the related tests out of article_spec.rb and comment_spec.rb, write a text_content_spec.rb, and relocate the tests. Now that you’ve ensured the functionality of the methods, from the article_spec.rb and comment_spec.rb you can just check that the class and instances respond to the proper methods.

Define a second module named Commentable that, for starters, just causes the including class to run has_many :comments. Remove the has_many from Article and, instead, include the module. Imagine that, in the future, we’d have a Photo object which also accepted comments.

Define an instance method in the Commentable module named has_comments? which returns true or false based on the existence of comments. In the articles#show view, use that method to show or hide the comments display based on their existence.