A common question teams run into while building a Rails app is “What code should go in the lib/
directory?”. The generated Rails README proclaims:
lib – Application specific libraries. Basically, any kind of custom code that doesn’t belong under controllers, models, or helpers. This directory is in the load path.
That’s not particularly helpful. By using the word “libraries” in the definition, it’s recursive. And what does “application specific” mean? Isn’t all the code in my Rails app “application specific” by definition?
So teams are left to work out their own conventions. If you quizzed the members of most teams, I’d bet they’d give different answers to the “What goes in lib/
?” question.
Antipattern: Anything that’s not an ActiveRecord
As we know from the secret to Rails object oriented design, well crafted applications grow many plain old Ruby object that don’t inherit from anything (and certainly not the framework). However, the most common antipattern is pushing any classes that are not ActiveRecord models into lib/
. This tremendously harmful to fostering a well designed application.
Treat non-ActiveRecord classes as first class citizens. Requiring these classes be stored in a lib/
junk drawer away from the rest of the domain model creates enough friction that they tend not be created at all.
Pattern: Store code that is not domain specific in lib/
As an alternative, I recommend any code that is not specific to the domain of the application goes in lib/
. Now the issue is how to define “specific to the domain”. I apply a litmus test that almost always provides a clear answer:
If instead of my app, I were building a social networking site for pet turtles (let’s call it MyTurtleFaceSpace) is there a chance I would use this code?
Now things get easier. Let’s look at some examples:
- An
OnboardingReport
class goes inapp/
. This depends on the specific steps the users must go through to get started with the application. - A
SSHTunnel
class used for deployment goes inlib/
. MyTurtleFaceSpace needs to be deployed too. - Custom Resque extensions go in
lib/
. Any site may use a background processing system. - A
ShippingRatesImporter
goes inapp/
if it imports a data format the company developed internally. On the other hand, aFedExRateImporter
would probably go inlib/
. - A
GPGEncrypter
class goes inlib/
. Turtles need privacy too.
Some developers find it distasteful to have classes like OnboardingReport
and ShippingRatesImporter
living in app/models/
. This doesn’t bother me as I consider them to be part of the broadly defined “domain model”, but I’ll often introduce modules like Reports
and Importers
to group these classes which avoids them cluttering up the root of the app/models/
directory.
I’m sure there are other patterns for dividing code between lib/
and app/
. What rule of thumb does your team follow? What are the pros and cons?
Trending from Code Climate
1.
How to Navigate New Technology Expectations in Software Engineering Leadership
Rapid advancements in AI, No-Code/Low-Code, and SEI platforms are outpaced only by the evolving expectations they face. Learn how engineering leaders can take actionable steps to address new technology challenges.
2.
Mapping Engineering Goals to Business Outcomes
Understanding how engineering activities impact business objectives enables engineering leaders to make informed strategic decisions, keep teams aligned, advocate for resources, or communicate successes.
3.
Unlocking Efficiency: Optimizing Pull Request Reviews for Enterprise Engineering Teams
As engineering teams grow, so can the complexity of the code review process. From understanding industry benchmarks to improving alignment across teams, this article outlines strategies that large engineering organizations can use to optimize Review Cycles.
Get articles like this in your inbox.
Get more articles just like these delivered straight to your inbox
Stay up to date on the latest insights for data-driven engineering leaders.