Ruby Modules, require, module_function, and Rails
My motivation for writing this article on Medium is to have a place to articulate my thoughts regarding the things I rudely jotted down in the title and do so on a shareable medium — no pun intended. So, keeping that in mind — this may NOT be the best fit for what you are specifically doing- grain of 🧂
Working with a multitude of frameworks and software languages, I’ve seen a myriad of implementations around modular programming (splitting software code into reusable or easily swappable modules).
I work with Rails and several other stacks and technologies in my professional career, and Rails many times has its own “notions” about the way to do things, and the Rails team has provided a lot of very useful tools and ideas around convention and best practices.
One recent issue I had was on an application that recognizes the idea of “Service Objects” — I won’t go into that here, but this article provides a good overview. Service objects in our app mean we have typical app/services
directory. Some developers don’t care for or have moved away from the service object type pattern, and honestly some things don’t make sense as a service object. In our case we needed to provide functionality throughout application controllers to “do” certain things, that might be useful in other places, such as other controllers, or even external libraries. In that case I found following this structure below, which may or may not be the most ideal, was helpful.
Given a controller that returns some data — in this case via a non-resourceful action (I know — for shame)get_some_complicated_plot_data
which provides data for a frontend data plotting library. The code below is just a “for instance” that closely resembles an actual use case, so I realize naming is not ideal in some instances.
First the module and its logic, followed by the controller that requires it:
And the directory structure again looks like:
app/
--controllers/
--some_data_plot_controller.rb
--lib/
--complicated_data/
--plot.rb--models/--services/ # where all the service objects live
If you had a second method you wanted to utilize, but you wanted to put it in a different file — it seems perfectly reasonable to add it:
app/lib/complicated_data/another_file_with_a_method.rb
Then you would require it with:
require 'complicated_data/another_file_with_a_method`
I like this approach over creating PORO (plain old ruby objects) all over the place. Utilizing module_function
also differentiates (in my mind) something used for just “utility” versus using a class
.