As a former Rails developer, I figured it would be worth mentioning reasons I disagree.

> Rails is not the fastest framework in the world… but I will argue that performance is the last thing you should worry about when you want to scale.

> In my experience, NodeJS or projects written in other frameworks start to collapse architecturally after an alarmingly small amount of complexity is added to them.

> More important than how many requests you can serve per second, is how your development team can maintain productivity over time.

This is what we call dismissing the actual concern (performance) and instead going on an unrelated talking point (maintainability). And frankly, legacy Rails apps are not actually that great to live in. The Rails approach, which I can best describe a “precious”, contains a lot of abstractions that don’t actually abstract successfully, breaking down when faced with the real world in ways that require ugly fixes.

> Where should we put files relating to our data model? /app/model!

> Where should we put our config? /config

> Our email code? /app/mailers

> Decision made. Conversation over. Now let’s get back to our desks and write some code!

If your backend consists merely of “data models” (with business logic and behavior bolted on in an inflexible Kingdom of Nouns sense), routes, mailers, jobs, and maybe response templates, Rails is opinionated enough. At least until your business logic is complex enough that you can’t just use active records as your sole method of abstraction. (Also, the active records themselves are poorly conceived, with obvious foot guns that persist to this day, though at least they’re better documented now. But more on that later.)

I usually find that the architecture I get from Rails is neither necessary nor sufficient. Do I really need controller classes just to route endpoints to handler functions? No. Do I need an ORM? No. Do I need HTML templating? Also no. But I’m going to need more sophisticated design patterns than active records. Decoupling data access from business logic is one of the first and most important separations of concerns that exists, and Rails actively discourages that. Another crucial separation is between work that can be done locally (and hence unit tested) and behavior that entails a network dependency, which includes DB access! Rails really leads you down a bad path if you just naively follow the defaults, especially if your app is so monolithic that my mailers and asynchronous jobs are in the same artifact as my response handlers. (I’m not down on monoliths; Rails just does too much for a microservice and not enough for a monolith. Hell, Rails does too much for a macroservice let alone a microservice.)

> Database management is hard. Managing database schemas and migrations is hard.

> Rails provides you with a no-nonsense way of defining your database structures, as well as a full suite of CLI commands for migrating, resetting, seeding, dropping and creating databases across all environments.

> Rails handles databases in a ubiquitous way. There are database drivers for almost every popular database, and schema management and Object Relational Mapping is handled the same way across all data stores.

These are almost all false.

* SQL isn’t hard to use for schema definitions or migrations. The Rails DSL for database schemas is marginally prettier than SQL, at the expense of adding an extra layer of indirection and making you learn yet another precious Ruby DSL when, if you’re worth your salt, you already understand SQL anyway.

* Rails has an inbuilt assumption that your app connects to a single DB instance per environment. Cool, but why on earth would I connect to different RDBMS systems across environments (except maybe in-memory SQLite for “unit tests”, which is already an anti-pattern?). And given this constraint, what exactly does ActiveRecord get me? I can wake up one morning and just port my whole app to Postgres without changing a single line of code? That’s not bloody likely in the real world and we all know it.

* Rails adds even more unnecessary brittleness to common DB access scenarios, which paradoxically means that the “simple” but broken Rails solution has to be augmented or replaced by ugly workarounds. For example, ActiveRecord validations are inherently prone to race conditions that make them both unnecessary and insufficient. From the current Rails guide:

> 2.11 uniqueness

> This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index on that column in your database.

OK, so the uniqueness validation doesn’t guarantee uniqueness if there are concurrent connections to my DB. Also, every real world Rails app is deployed so it runs as multiple processes. So it never guarantees uniqueness. Instead, I create a unique index, and ActiveRecord...sometimes throws an exception that’s distinct to the RDBMS but not distinct to the failure mode, so I still have to string match against my DB’s particular phrasing of “uniqueness constraint got violated” and handle that as an unnecessarily generic Ruby exception. Which is exactly the same amount of work that would exist if uniqueness validation just wasn’t a Rails feature to begin with.

A thoughtful design would realize: at least some validation has to happen in the DB and not in the application code, and it will anyway, so let’s make patterns for living with that. Why don’t my models automatically detect my unique indexes and throw a “not unique” Rails exception when the DB complains? Why does it instead insist on doing a shitty job of trying to guarantee uniqueness itself? It gets worse and worse when you run into scenarios like, “I want to run SQL queries that don’t cleanly map onto ActiveRecord without monkey patching every damned column of the result set onto individual instances of a loosely related ActiveRecord class”.

> Rails (and Ruby) Have a Deep Culture of Code Quality

Hah. I think I’ve already debunked that one. Rails has a deep culture of precious, brittle abstractions that make easy things easier and hard things harder if not impossible.

> Asset Management

If your app is monolithic enough that you’re managing your assets in your Rails app, it’s probably big enough that you’ve run into the problems with Rails I’ve mentioned earlier. If it isn’t that big, odds are you’re in a microservice environment where your assets are most likely deployed in a highly decoupled way—perhaps from a separate repo owned by your front end devs. Maybe the asset pipeline is a perfectly reasonable way of managing a full-stack monorepo, and you can still deploy your Rails assets to a CDN and make it work. But that implies that your web app is a Rails monolith, and Rails monoliths need a lot of thoughtful design decisions above and beyond “put models in app/models”.

> RSpec is quite simply the gold standard for behaviour driven development, and almost single-handedly created the BDD movement that is still raging in the industry today nearly a decade later.

RSpec is nothing special. I actually find Spock a lot more featureful and self-documenting.

> A Smorgasbord of Plugins

The first plugin on the list he links to is activerecord-import, which only exists to work around a problem Rails itself creates in the first place. The rest are either generic things that every other language has and other instances of workarounds for Rails limitations.

Rails (and Ruby in general) isn’t unique in terms of library support. Python has greater breadth, JVM has greater breadth, heck I bet you can still find stuff on CPAN that never made it into a RubyGem.

> Heroku

Works with everything; not a selling point for Rails.

> ActiveAdmin

Admittedly, something like this is one of the few redeeming qualities of an ORM, which is why Django always had this built in. Why not Rails?

The article keeps saying that Rails is “batteries included”. “Your app behaves in a reasonable way without ugly DB-specific exception handling code if you run more than one parallel instance of it” didn’t count as batteries either, though. But I guess HTML templates did. Rails’ design choices would be logical if you treat Rails as a framework for prototyping, but isn’t that when you need admin screens the most?

> Rails is battle-hardened and industry-proven. There is a long list of (very) successful businesses building their product with Rails.

Starting with Twitter, which migrated away from Rails onto a JVM backend as soon as they grew up. If you want to go with “battle-hardened and industry-proven”, use the Spring ecosystem. You can even deploy it on Heroku.