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.
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 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 CliControls
.
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 inheritsFunStuff
-
-
Layer 2:
-
iCalendar
,Add_New_Sub
,Update_Sub
,LoginControls
,SpendingAnalyzer
each directly inheritCliControls
-
-
Layer 3:
-
AccessSubscriptions
directly inheritsiCalendar
,Add_New_Sub
,Update_Sub
- UserSettings inherits LoginControls
-
-
Layer 4:
-
SubscriptionTracker
directly inheritsAccessSubscriptions
,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.
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.
Top comments (0)