How to declutter your lib directory

If you’re working on a Rails app, let’s do a quick test: check how many files you have in the lib directory of your project. If you have less than 10, it must be a fairly new project. Most projects I’ve seen have at least 20-30. In our current project we had over 200 of them a few months ago…

The problem with the lib directory is that there are no official guidelines of what we should do there. Rails has a well-defined directory structure, which is great because you don’t have to think of how to organize your code since all the directories covering controllers, models, views, etc… are set up from the beginning. The downside is that once you start creating files that don’t fall into any of the predefined categories, it’s hard to decide what to do with them. So they usually end up in lib, which becomes a real mess over time.

I started searching for a solution – I asked on Twitter and looked for any relevant blog posts. The best idea I found was the one presented by Bryan Helmkamp:

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?

That makes perfect sense: the app directory is for your app’s code, as the name implies. All of it – not just assets, controllers, helpers, mailers, models and views, just because these are the subdirectories that Rails creates by default. You can always make your own.

So what exactly did we find in our lib directory?

Resque jobs

If you use any background queue library such as Delayed Job or Resque, you probably have a collection of classes for the tasks performed in the background. In our project we have a whole directory tree consisting of Resque job files – guess where we had to put them? In lib of course, specifically in lib/queues.

But the background jobs are an integral part of the app, they perform work that would normally be done by controllers or models, and usually call in some other models, except they do it asynchronously. So, according to the rule mentioned above, they should be put somewhere inside the app directory. Of course there was no pre-configured directory where we could put them all, but that doesn’t mean we can’t add one, and that’s what we did – so the Resque classes were moved to app/jobs.

Decorators & presenters

We also have quite a large directory with decorator or presenter classes, which we have put in to app/decorators. These are all support classes for rendering views that present models to them in some way; most of the time it’s about rendering model data to JSON which is used by API controllers. We also have some presenters used by standard HTML views, which organize records into some specific type of hierarchy that a particular partial requires, in order to move the logic out of ERB partials (or models).

Concerns

Concerns are with modules that are intended to be included in other classes and are used for sharing code between a group of related classes, e.g. models or controllers. This concept was popularized by DHH and 37signals and is now officially supported in Rails 4 – Rails now auto-generates app/controllers/concerns and app/models/concerns directories and adds them to the autoloading list.

This feature is a bit controversial and there are some people who say that there’s no place for such thing in good Rails apps. I disagree – I think concerns are OK unless they’re overused.

If you have 200-lines-long concern modules that are included everywhere, or if you have as many concerns as you have models, you’re probably doing something wrong – any bigger and isolated pieces of functionality from concerns should be extracted to some separate, independent classes. But if you have a few short modules with just a couple of short methods each, that doesn’t do any harm at all and can make your code shorter and more DRY. One example could be when a few models have a field with the same name (e.g. enabled) and you want to share some setters, scopes or finders that deal with that field, which would look exactly the same in all of those models.

The concerns support added in Rails 4 is really just a change to generators and the autoloading list, so you can add this yourself easily to your Rails 3 apps. We’ve also added app/decorators/concerns which holds a couple of shared modules used by presenters.

Services

Another non-standard directory that we’ve added is app/services, in which we keep a group of classes and modules that don’t belong to any other category such as models or presenters, but are still specific to this project’s domain. This is a pretty broad category, though not as broad as the old lib directory (right now we have about 40 files and directories in it). Some services are classes that you make instances of, some are modules that you use directly and some are whole directories of a few cooperating classes grouped in a namespace.

The name “service” comes from a pattern called Service Object which is gaining some popularity in the Rails community recently, and it’s basically about extracting pieces of specific functionality that would be normally written in a model or a controller to a separate class or module. The reason is that if you put everything that a user can do into the User model, as is often the case at the beginning of a project, this model can grow to hundreds or thousands of lines of code over time (user.rb is often the most complex class in a Rails project).

Models

A few files from lib actually ended up in the app/models directory. The purpose of app/models seems to be clear, until you start thinking about it: what is a model really? Is it just for subclasses of ActiveRecord (or ROM/Mongoid/etc.), which represent tables in your database, or is it for all classes which are part of your model layer, regardless of how they’re implemented? At what point it’s not a model anymore, but rather a service?

We’ve decided not to restrict app/models only to ActiveRecord models, but instead just use common sense to decide what should go there. Some rough guidelines that we’ve used were:

  • AR models go to models
  • classes whose purpose is to store and fetch data from Redis structures also go to models – after all, the only difference from AR models is that they use a different storage backend
  • classes that are mostly about storing and accessing data, validations, calculations etc. should rather go to models
  • classes that are about interaction, doing, changing or sending something (often with names like “Creator”, “Handler”, “Uploader”, etc…) go to services
  • groups of classes in a namespace usually go to services (e.g. Auth module that implements various kinds of authentication or an ABTesting module which handles A/B tests)

Monkey patches

The Ruby open classes feature that allows you to monkey-patch other people’s code is a great thing. Even though it’s considered dangerous, it’s often hard to resist because it’s so easy and gives you power to change anything you want. We also had a bunch of monkey patches for other classes scattered over the project, mostly somewhere in lib and in config/initializers. Now we’ve moved them all to lib, divided into two groups.

The first one, lib/ext, is for extensions to core Ruby classes such as Array or String. There aren’t a lot of these, but sometimes you really want that core class to have that particular method instead of having to wrap the objects with something. The other directory is lib/hacks and it’s meant for, well, hacks; the files that should ideally not exist at all, and will hopefully be removed in the future, but for now they have to be there because the world is not perfect and sometimes you just have to hack something to make it work. At least this way we clearly see how many of those we have – this is similar to the idea of having a shame.css stylesheet that keeps all the ugly CSS you aren’t proud of, isolated from the rest of the code.

What stays in lib

The lib directory should ideally contain only those files that are generic and reusable (those that could be useful in the turtle social networking site). Things like a basic interface to some web service, database tools, asset processors or compressors definitely belong there. On the other hand, things that use words like “user”, “game”, “event” or any other words that appear in your model names probably don’t belong in lib.

Here are my guidelines for a proper lib file:

  • it should not access any of your models, services or anything else from the app in any way
  • it can only access other libs, Ruby core libraries or stuff from gems
  • it should not rely on any global variables, constants or ENV variables to be defined
  • it should be easily extracted to a gem and put on GitHub and RubyGems without too much effort

If it needs some small amount of project-specific configuration (e.g. an API key for a web service), make it possible for external configuration, just like you’d do if you wanted to put it in a gem, e.g.:

# lib/twitter_poster.rb

module TwitterPoster
mattr_accessor :api_key

end
# config/initializers/twitter.rb

TwitterPoster.api_key = 'qwerty'

At the moment we have just 46 Ruby files in lib, including the extensions and hacks.

Other approaches

This list is clearly not a complete solution that covers every possible case in every Rails project; every project is different, each has a different set of features and is built slightly differently. Not everyone agrees that the Rails conventions are something that you should try to stick with, and some people in the community argue that you should build your whole app separately from Rails and only use the app directory to build an interface between Rails and the core of your app. I wouldn’t go that far, but I’d agree that you should only treat the default directory structure as a starting point and then modify it according to your needs – where you end up will depend on your application’s complexity, architecture and your team’s preferences.

  • Maciej Kowalski July 11, 2013, 2:44 pm

    Moving files from /lib folder into /app subdirectories has one disadvantage.

    If you run rake stats, you won’t get proper statistics, cuz this task is ignoring custom made directories (app/services, app/jobs, app/decorators etc).

    Reply
  • Edward Price July 11, 2013, 10:41 pm

    Thanks for writing this common sense approach to keeping the app structure clean, tidy, and maintainable. Now I have to go clean out my /app/poro directory.

    Reply
  • Nicolas Mondollot July 11, 2013, 11:38 pm

    Great write-up! I agree with pretty much everything you said, and have been using the exact same file hierarchy in my latest (large) project. I’m m very pleased with it so far.
    The only dilemma I face is rake tasks. They are usually domain specific, and shouldn’t sit in lib/tasks IMO. But I haven’t come to terms with moving it to app/tasks yet. Any opinion?

    Reply
    • Mirek Wozniak

      mirek July 12, 2013, 10:17 am

      Kuba is on holiday in Georgia, but I’m sure he’ll reply as soon as it’s possible :)

      Reply
    • Kuba Suder

      Kuba Suder July 23, 2013, 5:16 pm

      To be honest, I had exactly the same thoughts as you… I even moved the rake tasks to app/tasks at one point, but I guess I was scared with what I’d done and I reverted this, because it didn’t feel right to move something that Rails expects to be in a different place. But maybe I should have left them in app.

      Reply
  • tamouse July 17, 2013, 5:05 am

    *Really* great article. Having just stepped away from a rather complex Rails app, much of what you’re saying here mirrors my thoughts on how to structure (or restructure) an app.

    The single thing that I see across many older Rails apps, and that seems to be getting some popular traction now, is the concept of moving from monolithism to modularity and services. There’s a happy medium, I think, that one should consider outside the extremes. Not everything should be moved to services and gems, and not everything needs to be split.

    I do appreciate the concept of using the lib/ directory only for things which are really independent of the app’s domain. This is where I tend to put wrappers.

    I share @Nicholas’s concern about lib/tasks. There are tasks which are clearly part of the application, and there are tasks which are clearly *not* part of the application. That they reside in the same directory seems backwards.

    Reply
    • Kuba Suder

      Kuba Suder July 23, 2013, 5:26 pm

      Moving away from a monolithic app to cooperating services running as separate processes is another step, but we didn’t get that far, since it’s a much bigger problem, especially if you haven’t done this before – you need to figure out how to do all the communication between them, what should be separate and what shouldn’t, etc. But with bigger Rails apps this would definitely be a good thing to do, it’s just that it’s not a standard practice yet so it’s harder to start.

      I think the app/services approach is a good first step though, because once you have groups of classes grouped under a namespace in app/services you already have isolated pieces of the system that deal with a specific things, so you could potentially just take some of them and extract them to a different project/process (once you figure out how to connect them).

      Reply
  • r April 3, 2014, 3:36 am

    Where are you putting tests in this scheme? Are you putting services tests under test/services, the non-AR model stuff under test/models, etc.?

    Reply
    • Kuba Suder

      Kuba Suder April 3, 2014, 12:27 pm

      Yup, the test directory structure usually mirrors exactly the app directory structure, so spec/controllers, spec/models, spec/services, spec/lib etc.

      Reply
      • r April 3, 2014, 1:11 pm

        Okay, thanks!

        Reply
  • Rafael April 16, 2014, 2:15 pm

    Very interesting subject and neat approach to make lib folder lean.

    Also I’ll remember to think about ‘MyTurtleFaceSpace’ whenever I’m in doubt where to put a specific piece of code :)

    Reply

What’s on your mind?