Build the Framework You Need

Or how to retain the vanilla Rails feel beyond the early stages.

The article is focused on Ruby on Rails, as I often use it on client projects, but the principle is technology-agnostic and applies to Django, Laravel, and others. If you’d like to maintain the vanilla Rails feel and productivity in the long-term the read on.

When Vanilla Rails Feel is Lost

Ruby on Rails offers a convenient starting point for development by introducing a rudimentary structure comprised of models, views, controllers, and so on. As long as the team stays within the bounds established by the framework, work progresses smoothly, with developers focused on implementing features, fixing bugs, and other value-added activities. This is when you get the vanilla Rails feel.

Sadly, sooner or later a product feature is going to require capabilities absent from the framework. Those could be workflows, form objects, state machines, and so on. Often, the first developer who runs into that limitation ends up implementing an ad hoc solution to push the feature he was working on out the door. The vanilla Rails feel is now lost.

As a result, the code starts to lose consistency, clarity, and understandability. Continuing down that path will likely lead to lower pace of work, more bugs, and developer burn out.

Fortunately, this can be prevented by treating the losing of the vanilla Rails feel as a signal to be acted upon.

How to Retain the Vanilla Rails Feel

The title says it all: build the framework you need. This doesn’t mean falling prey to the NIH syndrome, but rather enhancing Rails with generic concepts that would be useful for building your product. Let’s discuss a few examples from products I built for clients.

Almost all products need form objects for representing interactions that don’t map to a single model or don’t operate on models at all. In a Rails app, they can be built using Active Model, put under app/forms and test/forms, and treated as a built-in Rails feature.

Similarly, workflows (i.e. multi-step background jobs) can be implemented with Active Job, Single-Table Inheritance and the state machine pattern. If a workflow needs to be woken up from the outside (e.g. upon a response from a third-party API) then this can be handled by throwing a controller into the mix.

Testing benefits from this approach, too: a custom test base class can simplify preparing code for being tested; custom assertions increase expressivity. The meaning of assert_transitioned(workflow, from:, to:) is self-evident.

The list of potential additions is endless: scrapers, API clients, validators, errors, form builders. It’s up to you to decide which concepts your product would benefit from.

Let’s discuss how to apply this idea in practice.

In Practice

If you’re an individual contributor then be prepared for running into your framework’s limitations. The proper response isn’t cursing it, wishing you had picked a different one. Instead, solve the problem, make a mental note of the missing generic piece (“I wish we had a simple way of building workflows!”), and bring it to the team’s attention.

If you’re a lead developer, VP, or CTO then it should be much easier for you to have a bird’s-eye view of the product, allowing you to identify the missing pieces. You can then help the team prioritize, scope, design, and implement them.

However, this approach comes with two risks.

First, it’s easy to start working on a feature, realize it’d benefit from introducing a new concept, and end up working on two major pieces of work at the same time. I recommend tackling one large task at a time, so either:

  1. Build the feature in an ad hoc way and then implement the concept, or …
  2. Build the generic concept first and then use it to build the feature.

Second, if you overdo it you may end up with many one-off concepts don’t provide much value due to low reuse. Naturally, it may be worth it in complex cases to make the code more understandable, but it’s something to keep in mind.

The Result

The result is making the framework serve you much longer, potentially even indefinitely, allowing you to ship new features consistently, predictably, and with high quality.

In some cases, you may even be able to spin off an open source project containing the pieces you added. Depending on your industry, this can be great for marketing or attracting talented potential hires.

Enjoyed the article? Follow me on Twitter!

I regularly post about Ruby, Ruby on Rails, PostgreSQL, and Hotwire.

Leave your email to receive updates about articles.