Jon Leighton

Square pegs, round holes, and Rails' directory structure

Where should you put code in your Rails application? Often, the answer is obvious: models go in app/models/, controllers go in app/controllers/, and so on.

Many people have taken to extending the use of directories beneath app/ to allow for new categories of objects, such as app/presenters/ or app/validators/. For a little while now, Rails has embraced this pattern – all directories beneath app/ are automatically added to config.eager_load_paths, so Rails will look in app/*/foo.rb when trying to load the Foo class.

But what if your new object is a beautiful unique snowflake that defies categorisation?

Suppose you want to add a ChatNotifier, which allows you to post messages to a chat room. What category does it go in?

Perhaps, because we can say it is a “notifier”, you should create app/notifiers/ and add it there? But you probably won’t have any other “notifiers” in the future. Creating a whole new directory purely to house a single file seems a tad excessive.

Hmmm… well since ChatNotifier contains business logic, perhaps it should go in app/models/? That’s where business logic goes, right? Yes! And no. Business logic is found all over your application. There’s lots of important business logic in your models, but that doesn’t mean you should lump everything together in one massive directory.

I know, let’s put it in lib/, the standard I-don’t-know-where-the-fuck-this-goes directory! Apart from anything else, lib/ is for code which we might share between applications, right? We might end up sharing this!

Here’s the thing: you might theoretically use any of your code in other applications. When you do, I recommend you extract it to a separate codebase as a library. Until then, this code is part of your application, and so it should live in app/.

lib/ is not just a junk drawer, it’s a junk drawer found in a ramshackle outhouse, away from the rest of your stuff. Don’t use it for application code. (Actually, maybe just don’t use it.)

In the past, I’ve created an app/misc/ directory for these situations, and it has steadily filled up with stuff I don’t know where else to put. This is clearly just another junk drawer, but at least it’s a junk drawer alongside the rest of my code.

The other day, a rather obvious solution dawned on me: what if I could just put it directly inside app/? When working on non-Rails projects, I have no qualms about putting things directly inside lib/{gem_name}/. As and when it makes sense to do so, I often group related objects and organise them into subdirectories, but I don’t start from the assumption that everything must be assigned a category up-front.

I’d always assumed this would be difficult to achieve with Rails’ constant autoloading system, but it’s not hard at all. All that is needed is the following in config/application.rb:

config.eager_load_paths << "#{config.root}/app"

Now when I reference ChatNotifier, Rails will load app/chat_notifier.rb.

Wait, haven’t I just made app/ my new junk drawer? Maybe, but I think it’s better than app/misc/. Having these files at the root of app/ will encourage you to place things there sparingly, because you’ll have to see them all the time. As and when new categories of things emerge, you’ll organise them into new directories under app/.

Unlike with lib/ or app/misc/, you can’t just shut the drawer and forget about it.

16 March 2017

Jon Leighton is an experienced software engineer, specialising in Ruby. He is based in the beautiful mountains of Snowdonia in North Wales, and is particularly interested in projects with a social purpose. Do get in touch if you'd like to chat.

Comments