I don’t think it’s any secret that working on a significant-sized Rails codebase is not nearly as free and easy as any number of make-a-blog-in-fifteen-minutes screencasts would have you believe.
The good news is, I think I have the solution, and it’s simple. The bad news is, that doesn’t necessarily mean it’s easy.
The short explanation is: write small components and minimise their coupling. This allows you to develop and test them largely in isolation. If that all sounds glaringly obvious, well, it is, in a way. The gulf between intention and action remains nonetheless huge.
This means that your controller actions should just create, update, and destroy resourcs. If those resources don’t correspond to database tables, that’s OK: just create an intermediate plain old Ruby object that does. It also means that your ActiveRecord objects should not be responsible for sending emails, resizing photos, or anything else not related to record persistence, retrieval, or validation. Trust that ActiveRecord works as advertised.
Finally, disable database access (use NullDB) in all tests except for some full-stack integration tests that run through the happy path for each feature, and, with luck, you’ll find that you can make changes with relative ease.
On the last two greenfield projects on which I’ve tried this approach, the test suites run in under ten seconds. That’s a significant improvement in programmer happiness over typical test suites that take five, ten, or thirty minutes to run.
It’s not quite as easy as all that, though. There are a couple of things in Rails that make it a hard path to follow, and they’re almost all related to ActiveRecord.
First, the fact that ActiveRecord is responsible for a large part of the work of translating to and from the HTML-forms representation of data makes it difficult to swap in other Ruby objects.
Second, the natural object-oriented way of dealing with entity relationships—of delegating everything to the immediately related object—tends not to produce efficient queries. In fact, it can produce almost comically inefficient database usage. This is, of course, the evergreen object-relational impedance mismatch, but it’s still a problem.
It’s my strong suspicion (based on about eight years of wrestling with it) that ActiveRecord might be fundamentally inimical to making scalable and maintainable applications, and that we’d be better served by an easier way to build plain Ruby objects that can interact with forms and form data combined with a means of expressing the full object graph required for each action.
If that sounds like I’m suggesting that Rails’s path of least resistance—of one database table to one model to one controller—is not the right convention, then yes, I think that’s what I’m suggesting. I can’t think of many applications I’ve worked on in which resources as seen by the user correspond one-to-one with database tables, and it seems almost absurdly technocentric to even think that it would.
I still don’t know quite what the solution looks like, but I feel like I’m getting closer.
I’d be very interested to learn about alternatives that might avoid the same pitfalls, in any language or on any platform.