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 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) 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.
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
Uninitialized Constant errors as more modules were constructed even though all of the files were being required.
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
CliControlsmodule was loaded before the others that inherited it.
In researching the first issue, we came across a post from StackOverflow 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.
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
Since we inherited
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
SpendingAnalyzer to gain access to all methods.
SpendingAnalyzereach directly inherit
- UserSettings inherits LoginControls
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.
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.