Refactoring App Features into Modules in Ruby

Tags: ruby, modules, cli, inheritance
Published: 2021-01-20 21:37:41

Refactoring App features into Modules can make debugging, managing, and extending the code base easier - if done right
---

Recently I was pair-programming on a Ruby CLI app to help manage subscription services for my [phase 1 project](https://medium.com/r/?url=https%3A%2F%2Fgithub.com%2Fsimonjacobs212%2Fsubscription-tracker) at Flatiron School. After building out the models and schema utilizing ActiveRecord my partner and I began writing out the primary app file that would run and manage the app interface. 

After getting through just the user login control, we quickly realized that this file was about to become incredibly large, and search through hundreds of lines of code to find specific methods or for debugging was going to be a pain. 

From our previous experience with incorporating modules ([described in this blog post](https://medium.com/r/?url=https%3A%2F%2Fdsasse07.medium.com%2Fabstracting-refactoring-in-ruby-with-send-97e6ef1a4661)) we thought it would be a good idea to build the major menus/features of the app into their own modules and have our primary app file inherit them. 

Using the `require_all` gem, we built out the modules and required each of them in our `environment.rb` file using `require_all 'app/tools'`. The first few modules went smoothly, but then we quickly began receiving `Load_Error` and `Uninitialized Constant` errors as more modules were constructed even though all of the files were being required.


---

## Debugging

In trying to debug the errors, we realized that some of our modules were being loaded fine, and others were not. For the ones that failed to be loaded, they tended to be named last alphabetically. We also saw that the modules that had errors were the ones that had were being included in multiple places such as `CliControls` which managed user input methods. From this, two issues needed to be resolved:

* __Issue #1:__ How to provide module to multiple others without creating cross-dependencies
* __Issue #2:__ How to ensure the module files were loaded in the appropriate order so that the `CliControls` module was loaded before the others that inherited it.



---

### Issue #1: Module Inheritance

In researching the first issue, we came across a [post from StackOverflow](https://medium.com/r/?url=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F4914913%2Fhow-do-i-include-a-module-into-another-module-refactor-aasm-code-and-custom-sta) that discussed how Modules can pass along other modules that they themselves have inherited. Essentially if Module B inherits Module A and then a Class inherits Module B, it will gain access to the methods from both Modules B and A.

{% gist 
https://gist.github.com/dsasse07/c74d194d5e27487b4da1c87c8a37a694
%}


From this, we realized that if we determined a hierarchy for our modules/features, with the overall app at the top and the most widely used module on the bottom we can ensure that the main app still has access to all of the necessary methods even if it is not directly inherited. 

The app we designed is a Subscription tracking app, and we ultimately ended up with the hierarchy below. Where ASCII graphics and coloring, and sound files for our CLI app were in the lowermost `FunStuff` module, which was needed by the `CliControls`. 

![Module Heirachy Diagram](https://cdn-images-1.medium.com/max/1600/1*-BQlGXnCQIYD9AIJe1omSA.png)

Since we inherited `FunStuff` in `CliControls`, the modules that inherited `CliControls` would automatically gain access to `FunStuff` just by inheriting `CliControls`. We followed the same pattern upwards, with our primary app class `SubscriptionTracker` needing to inherit directly from just three modules `AccessSubscriptions`, `UserSettings`, `SpendingAnalyzer` to gain access to all methods.

* __Layer 1:__
 * `CliControls` directly inherits `FunStuff`
* __Layer 2:__
 * `iCalendar`, `Add_New_Sub`, `Update_Sub`, `LoginControls`, `SpendingAnalyzer` each directly inherit `CliControls`
* __Layer 3:__
 * `AccessSubscriptions` directly inherits `iCalendar`, `Add_New_Sub`, `Update_Sub`
 * UserSettings inherits LoginControls
* __Layer 4:__
 * `SubscriptionTracker` directly inherits `AccessSubscriptions`, `UserSettings`, `SpendingAnalyzer` 



---

### Issue #2: Requiring the Files

By resolving the first issue, we realized that the issue of requiring the module files in the appropriate order can now also be easily solved. Instead of using the `require_all` gem which loads all files in a folder in alphabetical order, we could load each file individually using `require_relative` starting with the module files at the bottom of the hierarchy, and working upwards.

{% gist 
https://gist.github.com/dsasse07/d3b84beba40ad2c809432a439c4532b2
%}

With the inheritance set up in the appropriate order, and files loaded in matching suit, the main app class `SubscriptionTracker` will be able to call all of the methods for the different menus and features.