<Daniel Sasse/>
Woodland Background
Refactoring App Features into Modules in Ruby
 cover image

Refactoring App Features into Modules in Ruby

ruby

modules

cli

inheritance

Published: Wed Jan 20 2021 (~ 3 min)

Read on Dev.to

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

Module Heirachy Diagram

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.

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.