Every once in a while I come across the question “Where should I put my POROs in Rails?”
In order to answer this question, I would actually zoom out and ask a broader question: How should we organize our files in Rails in general?
Rails’ organizational limits
To some it might seem that this question already has an answer. Rails already gives us app/controllers
for controllers, app/models
for models, app/helpers
for helpers and so on.
But after a person works with a growing Rails app for a while, it eventually becomes clear that Rails can only take you so far. The sheer quantity of code overwhelms Rails’ ability to help keep the code organized. It’s like piling pound after pound of spaghetti onto a single dinner plate. It only makes sense up to a certain point. Past that point the result is a mess. (This isn’t a criticism of Rails. It’s a natural fact of frameworks in general.)
A Rails codebase can grow both “horizontally” and “vertically”. Horizontal growth means adding more resources: more database tables, more model files, more controller files, etc. Rails can handle horizontal growth just fine, indefinitely.
Vertical growth means a growth in complexity. If the amount of domain logic in an application continues to grow but the number of controllers/models/etc. stays the same, then the result is that the individual files all grow. If the “fat models, skinny controllers” heuristic is followed, then the complexity accumulates in the model files. The result is huge models. These huge models are hard to understand because of their sheer size and because they lack cohesion, meaning that each model isn’t “about” one thing, but rather each model file is just a dumping ground for everything that might be loosely related to that model.
Common (poor) attempts to manage complexity growth
A common way to address the complexity problem is to split the code according to design patterns (decorators, builders, etc.) and put the files in folders that are named for the design patterns: app/decorators
, app/builders
and so on. The logic of this approach is that it’s a continuation of what Rails is already doing for us, which is to divide files by design pattern. At first glance it seems like a sensible approach.
However, I don’t think this approach does a very good job of addressing the problem of being able to find what we need to find when we need to find it. Here’s why.
Let’s say for example that I need to make a change to some billing-related logic. I know that the code I’m looking for has something to do with billing of course, but I might not know much else about the code I’m looking for. I have no idea whether the code I’m interested in might lie in app/models
, app/decorators
or anywhere else. I probably have a sense of whether the code is display-related (app/views
), domain-logic-related (app/models
) or related to the request/response lifecycle (app/controllers
), but beyond that, I probably have no clue where the code is located. How could I?
When people try to extend Rails’ convention of dividing files by design pattern, they’re missing an important point. Decorators, builders, commands, queries, etc. are all different from each other, but they’re different from each other in a different way than models, views and controllers are different from each other.
Think of it this way. Imagine if instead of being divided into meat, produce, dairy, etc. sections a grocery store was organized by “things in boxes”, “things in plastic bags”, etc. The former is an essential difference while the latter is an incidental difference. Unless you know how everything is packaged, you won’t be sure where to look. The difference between models, views and controllers is like the difference between meat, produce and dairy. The difference between decorators, builders, commands, queries, etc. is more like the difference between how the items are packaged. Again, the former is essential while the latter is incidental.
Organizing by meaning
A better way to organize Rails code is by meaning. Instead of having one folder for each design pattern I have, I can have one folder for each conceptual area of my app. For example, if my app has a lot of billing code, I can have folders called app/models/billing
, app/controllers/billing
and so on. This makes it much easier to find a piece of code when I don’t know anything about the code’s structure, I only know about its meaning.
Regarding design patterns, I think design patterns are both overrated and overemphasized, at least in the Rails world. A lot of Rails developers seem to have the idea that every file they create must belong to some category: model, controller, worker, helper, decorator, service, etc. Maybe this is because in a vanilla Rails app, pretty much everything is slotted in to a category in some way. But there’s no logical reason that every piece of code has to fit into some design pattern. The plain old “object” is an extremely powerful and versatile device.
But what if everything in a Rails app is just plain old Ruby objects? Won’t the app lose structure? Not necessarily. Most objects represent models in the broad sense of the term “model”, which is that the code represents some aspect of the world in a way that’s easy to understand and work with. Therefore, the objects that comprise the app’s domain logic can go in app/models
, organized hierarchically by domain concept. Plain old objects can sit quite comfortably in app/models
alongside Active Record models.
Now let’s go all the way back to the original question: where should you put POROs in Rails?
The answer depends on how you organize your Rails code in general. It also depends on what you consider POROs to be. I consider most of my POROs to be models, so I put them in app/models
.
Takeaways
- Rails can only help with code organization when the amount of code is small. Past a certain point it’s up to you to impose your own structure.
- If the aim is to be able to find the code you need to find when you need to find it, organizing by design pattern doesn’t help much if you don’t already know how the code is structured. Organizing the code by meaning is better.
Over the years I’ve come to a similar tactic, for many of the same reasons, and having learned a number of the same lessons. Some time ago (8 year, apparently!?!) I wrote about it. While some of the specifics (and examples) might have changed in the years since, the concept overall still holds true for me. https://stevenharman.net/reclaim-your-domain-model-from-rails
It’s almost like you invented namespaces