Working with a large codebase with an untyped codebase is just a nightmare, even with powerful IDEs like RubyMine that are able to cover some of the paint points. I wonder how good Sorbet is these days, though, especially the RoR experience
You don't know how happy I was to read this... I thought I had a serious problem with getting distracted with my "projects" but it seems this is much more normal than I thought ;-)
I’m usually a Go person and love it, but building simple crud routes is not the fun part of it.
In part because for a modern frontend dx you will need hot reload, bundling, etc. But even if you make an SPA and only use eg .NET for an API, using something like OpenAPI to share the schema is a subpar solution. There's really nothing as good as sharing TS types across your whole app(s).
ideally a JS frontend app won't show logic not intended for the user type in question.
in practice, really often I see a huge app covering all roles and cases, a trove of info for the red team. and even worse, the reality of software development with LLMs in 2026 is plenty of code is being shipped without security audit.
I know it's not an inherent fault of the JS frameworks. bit I share what I see on the streets, most of custom JS apps I see are way more vulnerable to hacking than a old-style MVC app.
yes it is possible to make mistakes in both styles, but in JS apps I probably see 400% more easily discovered vulnerabilities than in a common MVC (even with stimulus) app
I gave RoR a try last year, and so far I'm at the same level I was with the other projects but I actually enjoy programming the project after the PoC phase. Maybe because third time's a charm? Maybe because I know what I need to do? Who knows! But RoR fits in that idiom.
Just to note, its a really boring app thats been done before (odeva.app)
However, modern day JS frameworks don't care about this at all. Most of them love flaunting about their raw performance numbers. Security? Fuck that. Not even basic form CSRF protection. A lot of times, there is not even SQL injection prevention in them.
Compound this with someone who just vibe codes their app on top of these frameworks - that's how you end up getting hacked. Every week there is an incident. That's why good frameworks like Rails are very important. People who actually care about writing secure, good quality software are on the decline, but thank God rails still exists as an option in 2026 despite the fact.
Having the productivity of Ruby + the static typing and LSP integrations is a wonderful combination. Sure, it might be less aesthetic than some might like, but at least in my experience, it works very well in practice and supports most of the needs I have from a type system for writing and understanding conventional software.
I'm rooting for further developments in this space because I love language and systems that can leverage it's strengths and layering on a type system makes it a very formidable technology for building real world products.
I have not used Rails in a few years, but I can also say that the Sorbet typing support for the framework is definitely there too -- to great credit to the folks at Shopify allowing users of the framework to benefit from this tooling.
I would happily work on a Rails system today too. It's an amazing testament to the community how the design has been able to continue to develop to meet the needs of the many developers building with it up to this point. I hope more people feel the same joy I do discovering the kinds of things they can make with the language and frameworks like Rails.
With the amazing advancements with the AI tools we have now the only real limits are with how big our imaginations and ambitions can get.
> Accelerate your agents with convention over configuration. Ruby on Rails scales from PROMPT to IPO. Token-efficient code that's easy for agents to write and beautiful for humans to review
And I fucking hate it. If I read this the first time I would think this is some kind of tool to optimize your LLM agents.
I have been using Rails for over a decade now and always liked the focus on writing beautiful and simple code. On making it easy to reason about with colleagues. Now it seems like DHH is throwing all what made Rails special overboard.
If we are all supposed to be talking to agents now, what's the difference if my agent uses fucking Next, Nuxt, Rails or Django?
however if your app doesn't fit the standard crud pattern you end just fiddling with a lot of things that you shouldn't and in that case I recommend Django which provides enough flexibility while providing a good base. There's less magic in Django
I moved on, not because Rails failed me, but because the things I started building next (Streaming infra, APIs behind proxies, lots of concurrent HTTP work, infrastructure tooling, AI/ML) just fit better in other languages. I pick tools by the problem, not loyalty.
What I notice in this thread is a lot of "Rails vs X" framing when the real answer is boring: use Rails (if you like the vision and Ruby) when the complexity is in the data model and business logic. Use something else when it's in concurrency or infrastructure. The one-person-framework pitch is real, but it's for a specific shape of problem.
Also: God i miss writing Ruby, its a fantastically and beautiful language.
Also: Blast from the past: Ruby is a great DSL for Rails.
I’m not into RoR, because I was mainly PHP rescuer in the beginning of my career, but they both are just problem solvers. Sit down, write minimal (in case of PHP not so cool looking) code and proceed to next task.
Right now, I would rather use Go with a simple framework, or even without one. With Go, it's so easy just to copy the binary over.
Not many frameworks have been thriving that long, and there's good reason.
It packs everything, is tidy and productive, with a pleasant language to read and write.
In the latest Stackoverflow survey, it's back at the "top 5 of desired stacks to use for next project" over a decade after its inception !
Give it a try.
Loco follows up the Rails formula pretty closely, and makes easier to learn Rust by taking care of a load of boilerplate code.
As for Rails, I guess now that Ruby is serious about having a JIT in the box, a few actually, it is kind of atractive.
Due to my experience with Tcl, and continuous rewriting into C modules, if a JTI isn't in the box, I kind of don't bother unless it is due to external factors.
I have the impression, though, that these days it only appeals to those who picked it up before version 3 or 4, when it was smaller, maybe more understandable, and incredibly better than all the competing frameworks (except Django maybe).
If your first contact with rails is version 7+ and you’re only comfortable with JS/TS, then you’re not going to get it and might actually strongly dislike it
The premise that you get meaningful efficiencies from JavaScript on the back-end just because you have to use it on the front-end has been pretty thoroughly debunked at this point. Instead you mostly get a larger blast radius when the front-end ecosystem has its monthly identity crisis. OP's "stacks-du-jour" and programming language "flavour of the month" framing is exactly right. A shocking amount of web software architecture is just following fashion trends dressed up as technical decision-making.
Most of the churn in tech stack isn't driven by engineering requirements, it's driven by résumé optimization and Hacker News anxiety. Rails has quietly been powering serious businesses the whole time. Does anyone think NPM's 3.1 million packages enable more functionality than RubyGems' 190,000 packages?
[^1]: https://github.com/mame/ai-coding-lang-bench?tab=readme-ov-f...
No other programming language brings developer joy because……. errrr because ……. Well because we are just super certain and confident, that’s why! Oh and DHH said so.
rails lost it's convention over configuration ways, the generated app is dozens of files, lack of explanations and guidance on how to setup various things like environments, kamal being the worst offender and the changes between recent major releases aren't making it any easier to read the (often ai slop) articles and docs
Have so many good memories working with Ruby.
If anyone has old codebase need to be updated and upgraded, refreshed. I am happy to do that.
It's been 20 years, and Ruby/Rails still can't get their typing working.
It's not just Rails. Ruby is dying. And has been for quite some years now.
The strangest things are people writing blog entries claiming the opposite. Like "ruby ages like fine wine". No, that is incorrect - it is dying. I have been using Ruby since about 2004 or 2005. I still use it just about daily. I started before Rails and couldn't care any less about Rails; sadly the ecosystem is infiltrated by corporations such as shopify and others. You can see how they took over RubyCentral effectively (and if they insinuate otherwise - nope, I am not an idiot. I see the pattern. I notice what is happening. You call a black cat a green frog and I call bullshit. It's a black cat. See RubyCentral running amok already before they mass-purged developers, but that's another story - let's go back to ruby, and rails.)
> And Ruby itself is nowhere near the top 10 languages, sitting just underneath Lua and freaking Assembly language in terms of popularity!
Yup. Ruby is dying. It is following perl.
Now, defining "dying" is hard because you still have an active community, even outside of the train wrek that is rails (anyone still caring what random crap DHH writes on his blog? I've noticed fewer people care about the garbage he publishes, other than making fun when shopify damages the ecosystem - oh wait, he sits on the board of shopify. Did I already point out how much damage shopify causes here?).
> But I’m a stubborn bastard, and if I find a technology I like, I’ll stick with it particularly for projects where I don’t have to care about what anyone else is using or what the latest trend is.
On this part I agree. Ruby as a language is very well designed. It is a great language. I don't think anyone really objects to this, so the criticism has to be split - some criticism is valid, some is not. As a language ruby is well designed.
Nonetheless it is dying too. That is also a factual statement. Anyone claiming the opposite is wrong. At the least this is the case right now, and has been in the last some years, to varying extent.
> realised Ruby was “a better Perl than Perl”.
Also true. Ruby is the better perl. But I actually call Ruby syntactic sugar over C, because this is actually what Ruby is, if you think it through. My use cases are mostly helper-scripts, tons of that, over whatever I do in general. Literally everything computer-related. That includes use cases for the world wide web. All my needs here are covered by Ruby - but not by rails. I don't need rails.
Sadly, ruby also has a second problem: documentation. The documentation is crap in general. Look at rack. Opal. WebAssembly for Ruby. That documentation is a joke. An insult. Even sinatra, though better documented than these, has a bad documentation for the most part. There are some exceptions; for instance, hexapdf is well documented, and Jeremy's projects are also well-documented. So I am not saying everything is poorly documented. But for a language that once claimed it wants to compete against python ... sorry, documentation-wise this is still an epic failure point. In some ways ruby actually deserves to follow the path to extinction like perl did, merely because it failed to adjust, adapt and really improve. Yes, there is some internal improvement, but in many ways ruby failed to solve the problems people critisized it for, for many years. And now catching up is SUPER hard. I don't think it can happen anymore. I thought it was possible 10 years ago, but the last 5 years made it clear that ruby is towards extinction. It still does not change me using it, since ruby covers my use cases, but anyone thinking there will be an influx of new young folk driving ruby forward, is just worshipping an illusion here.
> There’s just this minimal translation required between what I’m thinking and what I type.
That's true. Ruby is kind of transitioning your thoughts into code.
> Sure, I can knock things together in Python, Go, or whatever the flavour of the month is, but I always feel on some level like I’m fighting the language rather than working with it.
Kind of, though python works fairly well too.
> And of course there was the welcoming, quirky “outsider” community feel with characters like Why the Lucky Stiff and their legendary Poignant Guide To Ruby.
Well - _why ragequit when someone doxxed him. But even aside from this, I found the poignant guide super-confusing. It was art, but I prefer less confusion myself. Still, _why is gone from the ruby ecosystem. Supposedly he is still doing computer-related stuff in reallife but he is no longer really affiliated with ruby as such. Did I already point out that ruby is dying?
> it’s just so nice being able to write things like
unless date <= 3.days.from_now
I don't doubt that rails is useful, but code like that sucks. Rails
also came up with HashWithIndifferentAccess. This simply shows a lack
of UNDERSTANDING. They pushed the DSL madness way too far. Yes, I get
it, "I don't want to care if we have a string or a symbol" - easier
access. But it is the wrong THOUGHT process here. And just the name
itself ... HashWithIndifferentAccess versus Hash. Sorry rails guys -
you were not good designers in a general sense of the word. The DSL
may work; the DSL may be useful, but language or API designers? Nope,
sorry. It's awful. if upload_size > 2.megabytes
I don't like this either, but I have less reservation here than compared
to using numbers for time/date. It's cool that ruby is flexible to allow
this, but I still think it is the wrong THOUGHT process.I started my dev career with php and then nodejs, but recently got a job with rails, and honestly, it’s the worst among the 3.
There is no static typing whatsoever, it’s littered with magically generated methods, on a moderate size project the controllers or models directories grow to dozen of files. In general it feels like you need a lot of mental context in order to work with Rails, and I believe this is the reason people who run it for 10+ years in production love it. They simply carry all the magic in their heads, rather than let the framework guide you.
I, however, get much more DX and production stability by building with a boring (router + server side rendering) NodeJS stack with typescript and schema validation. My services are more stable and do not crash on “undefined method foo for nil”.
I guess people will defend whatever they know best, even if it has quirks.
On a positive note, I like ruby as a language. It has cool features like pattern matching, named arguments, or dropping verbose statements like “return” at the end of the function.
Also, every enterprise rails app I've seen (seven, to date) has been really poorly written/architected in a way that other backends just weren't. Even the fairly new ones felt like legacy code already.
Nowadays C# is anyways much more expressive than before. Meanwhile Ruby is still very slow.
Not to mention how poorly maintained are most Rails projects. People have been "vibe coding" forever.
A well-organized and maintained Rails app is great though. I'd definitely consider working with it again, but it really depends on what company it is.
Why would anyone ever choose ruby, python, etc when you don’t need to write it?
These languages are undoubtedly dead as of now. Python may live on in ML for a bit but probably not much longer
I certainly wouldn’t want to go back to working in dynamic languages without typing on top. That takes too much brain power, I’m too old for that now.
I would say Sorbet seems more “basic” than something like Typescript. It handles function calls matching signatures, potential nulls, making sure properties actually exist, that kind of thing. Whereas TS can get quite abstract, and at times you’re fighting to convince it that a value actually is the type you say it is.
TS is very powerful and expressive, to the point that it’s possible to do computation within type code. I’m not convinced I always need that power, or that it’s always more help than hindrance.
But you can also pick tight packages that do one thing well. Something like oRPC + Drizzle that lets you pipe data from your database to frontend with full typing and cross-boundary go-to-definition while covering most of what Nest and Adonis do with better focused APIs.
And in terms of security, I'll take Typescript with a strong compiler config anyday. For example, I disable: `any`, non-null asserts (no `!`), floating promises without `void` for explicitness, no unnecessary conditions, and a bunch of other strict rules. I also use Branded Types liberally. All of that makes logical errors that can become app-specific security issues (and are thus less readily detected) much less likely to happen. And as a bonus you get really reliable code too.
I've only just noticed that on the Rails homepage, and while I acknowledge everyone's chasing that sweet sweet AI hype, I gotta say that's... disappointing[1]. The reason I fell in love with Ruby (and by extension, Rails) is because it enabled me as a human to express myself through code. Not to become a glorified janitor for a LLM.
[1]=Well, I had a stronger response initially but I toned it down a bit for here...
it's a little cringe, but arguably the benefit of having agents use rails would be tht when you review and audit the agent produced code, you review something that is, as you put it: "beautiful and simple code" and "making it easy to reason about..."
I loved rails back in 2017. I may be an outlier but the line tempts me to try it again despite having adopted the who cares attitude to langs. Would be nice to hear from someone first hand if they felt it helped.
I'm not trying to be glib. The thing that seemed magic to me at that time was all the scaffolding that Rails provided with a few simple commands, making it possible to quickly build something that let the user authenticate and enter and display data. Sure, Ruby itself and the culture around it back then was also great and will always have a place in my heart. But the whole convention-over-configuration and scaffolding thing, that was what I liked so much about it, and I never found that in any other language/framework combo in a way that felt as smooth.
But now, I use AI for scaffolding, and for my side-projects often never have to touch code.
So why would I choose something for a CRUD application that might give me headaches down the road, when there's a possibility that the app might morph into something less conventional, when I could use *any* language/framework that's not as rigid and have the scaffold be built by AI?
I get it if you enjoy actually writing code. But I don't quite get the benefits if the goal is to have something working quickly and be able to potentially build it out to something that is not served that well by RoR.
And yeah you're right, like what is the difference at that point? If the pitch is "AI agents write this", then the obvious response is "well why would I not use what has more training data then?".
fwiw that headline is cringey for sure. but DHH has proven himself a great marketer. it very likely is riding the wave.
Trying to answer the question of, why is language and framework still relevant in a world where almost everyone uses an agent for coding?
JS solutions are loosely coupled, lots of good reasons to do so, but comes at a major complexity cost.
I think once you're deep into a project, you of course know the features needed and the constraints and you'll be more efficient the next time around.
I think the challenge is to keep working on your old legacy projects.
Plus Symfony is quite flexible on how you want to organize your code. Modular monolith, monolith, DDD, microservices, "junior developer just setting up controllers and entities".
This is so painful... I can't help but wonder who they're trying to target with such inane slogans.
Rails is amazing, but "token-efficiency" is not on the list of reasons why.
The claim seems quite clear to me: "convention over configuration allows coding agents to be more effective".
But yes, I do agree that the main line should say what Ruby on Rails actually is, not why it's good for your agent.
Because if the domain logic is getting more complex then more types will not catch the bugs unless you are willing for codify business rules within types so then you have to test the types.
I'm a fanboi, so I am biased, but Rails is still pretty great in 2026 for general business purposes.
Go and Rust fill in lots of gaps for more serious performance stuff.
There are endless tools available, and quick internet dopamine feedback loops, but almost no wisdom.
Give it a few more years and more inflation, and the remaining 35% of millennials will get out there to find their first jobs, and then the impact will be even worse.
> There’s just this minimal translation required between what I’m thinking and what I type
That's really the essence of Ruby for me.
In the past month I migrated a 20 year old Python project (2.6, using the pylons library) to modern Python in 2 days. Runs 40-80 times faster too.
But I learned to do that by working on codebases that were the opposite.
Oh, where did you find that?
Only info I could find was that Rails is at rank 10 in the Web Frameworks category for Admired vs. Desired in the 2025 survey: https://survey.stackoverflow.co/2025/technology/#2-web-frame....
At least in Python (as a comparison example), imports are only available in the module doing the importing - it's more explicit. You'd have to really work to get the same "everything all at once" as Rails has.
And AI is doing the rest. The path to exctinction.
Does Claude make "scripting" languages obsolete? I mean that knowledge becomes less useful if AI autogenerates everything.
> This can be something simple like caching for a specific time period:
<% cache "time_based", expires_in: 5.minutes do %>
<!-- content goes here -->
<% end %>
I absolutely hate ERB. It is strange that it is such an integral part of rails.I abandoned PHP for many reasons but one was the spaghetti problem. Rail has the same spaghetti problem, though ruby is prettier than PHP. Still, that spaghetti design is just awful.
> This is why services like Heroku and Pivotal Cloud Foundry thrived back then
Heroku is also in the process of dying. There were some recent discussions about it on reddit as to why.
> While the Stack Overflow survey isn’t necessarily an accurate barometer of developer opinion, the positions of Ruby and Rails do show it’s fallen from grace in recent times.
It's not just SO though. TIOBE, despite being crap, also shows a similar trend. And if you research things, you notice many people moved on from ruby, for many reasons - often work-related.
The numbers are all there though. Now people either believe the numbers - or they write fake analysis such as this here: https://medium.com/railsfactory/ruby-is-not-dying-its-aging-...
> Anecdotally, I find a lot of documentation or guides that haven’t been updated for several years and the same goes for a lot of gems, plugins and other projects.
Yup. A dying ecosystem. I stopped using rubygems.org myself after Marty pushed the ecosystem into shopify's corporate pet project. 100.000 downloads and then your project is hijacked? Or the new shiny corporate rules? Nah. Go to corporate land and leave us alone, Marty. RubyCentral most definitely does NOT represent "the community". The original guys who wrote rubygems - now these were community folks, not some corporate ponicorns. Sadly when money is tight, bad things happen, and the ruby ecosystem showed this beautifully. Kind of bad too because it means money wins over community; but this is a chicken-egg problem, because how to grow a community if the trend goes downwards, for whatever the reason(s)?
> And I find that most gems follow a similar downward trend of activity.
Yup - but this is also old, even before RubyCentral transitioned into CorporationCentral. Ruby folks left ruby, years ago already. The people I knew from, say, 2005 to 2012 or so, also from IRC - some still use ruby, but most moved on to other things (also for financial reasons usually).
> Rails on the other hand actually seems to be picking up steam and has been remarkably consistent since the big “boom” of Rails 3.0 in 2010
Nope. Rails is also dying. It does not have the same 1:1 problem as ruby has, but the decline is 100% there too.
> Rails is a rare example of an OSS project that’s grown into its release cadence rather than burning out.
Not really. Ignore the promo. Look at the facts. Rails has been hurting too (which makes DHH laughing about when RubyCentral mass-purges developers both evil and silly, because that hurts the whole ruby ecosystem too - what was shopify thinking here?).
> Whether it can still find an audience amongst new developers is an open question
Yeah that is the question. Unfortunately the answer is there: new developers won't use ruby for the most part. AI also competes here now.
2026 is not like 2006, sorry folks.
> I probably could eventually build things almost as fast in another language or framework, but I doubt I’d be smiling as much while I did so.
Well, I used perl, php, python (still use python too). Ruby is more efficient for my brain though. And I disagree that I could be as fast as in another language. I simply have fewer barriers when writing ruby for the most part. Less restrictions. I don't think anyone thinks ruby itself is a bad language at all. We need to keep the discussions separate.
Pretty good comment from Mark Dastmalchi-Round by the way. Well-written, tons of details, opinions - even if I may disagree with some points, the overall quality of his blog entry is very good. We should give him very good marks for the blog - even more so as it is not on medium.com. I hate medium.com (and the link above, is to medium.com ... why did I link it ...)
I vouched for your reply below, and to answer in the meantime:
Yes, it's runtime, but that only matters if your code can't be initialized without unacceptable side effects.
In which case you don't have a functioning test suite either, and have much larger problems.
Otherwise, just load the code you struggle to figure out into irb, or pry, or a simple test script, and print out source-location.
If that is impossible (aside from the fact that codebase is broken beyond all reason), the marginally harder solution is to use ruby-lsp[1] and look up the definitions.
This is only hard if you insist on refusing to use the available - and built in, in the case of source_location - tooling.
> and at times you’re fighting to convince it that a value actually is the type you say it is.
Might just be allocating that brain power to the same task but calling it a different thing.
It means that there are many more people using NPM.
That means more users. More users is almost always better, for any language.
Also many of those gems on rubygems are dead since decades, literally. Probably also for NPM. We can not just compare the numbers without analysis.
Good advice that I keep trying to adopt myself, but I have to confess a large personal bias for languages that I like, even if it keeps me from certain classes of problem (I like Ruby, though).
What did you move onto for those next things you started building?
Honestly, breath of fresh air.
It's the closest I've come to that old school "in the box" desktop development experience you used to get from building desktop software with Visual Studio or IntelliJ IDEA or NetBeans or Eclipse or any of the other IDEs of the 90s/00s (I never used Delphi or VB but I imagine in some sense they were even moreso than the ones I've listed, which are the ones I used), only it's web development.
For me web development has always felt like a frustrating ordeal of keeping track of 10,000 moving parts that add noise and cognitive load and distract you from fixing the actual problems you're interested in solving. This means the baseline ancillary workload is always frustratingly high. I.e., there's too much yak-shaving.
Whereas Rails seems to drag that all the way down to a level where it feels more similar to the minimal yak-shaving needed to (at least superficially) build, run, and distribute desktop software. Not that this is without its challenges, because every deployment environment is a little different in the desktop world, but the day to day developer experience is much lower friction that modern web development in general.
Also, no sodding TypeScript to deal with. I hate TypeScript: an ugly, verbose, boilerplatey abomination that takes one of the nicest and most fun features of JavaScript (duck typing) and simply bins it off. Awful.
edit: Django was release in 2005
So everyone just stop worrying what everyone else thinks or seems to think and just use the right tools for you and get on with it
It is surprising to me seeing the rust web backend scene; many libraries, server frameworks, and users, but they are all Flask-analogs, without the benefit of the reasonably-robust ecosystem Flask has. My suspicion is that people are using them for micro-services and not websites/webapps, but I haven't been able to get a straight answer on this about how people are using these tools. I.e. even though rust is my favorite overall language and see no reason it couldn't be used for web work, I still use Django.
Axos, Axum, Rocket, Diesel etc, are all IMO not in the same league as Django. My understanding is that addressing this is Loco's Raison d'etre.
Another aspect of the Rust web ecosystem: It's almost fully gone Async.
I tried sorbet a couple of times and totally get why it’s useful but imo it’s not just lacking (e.g. compared with what can be done with TS or even a simple type system like golang) but it also removes all the fun parts of ruby / rails.
I never used Ruby, but Python code bases love mixing in strings that are actually enums and overloading functions that accept all kinds of types. You just had to hope that the documentation was correct to avoid a crash.
Java 1.7 to Python feels very freeing from all the boilerplate. Kotlin, or any other modern language with a well designed standard library, to Python just feels like a bunch of extra mental work and test to write to just avoid brackets.
irb(main):005:0> Foo.new.method(:bar).source_location => ["tmp/test.rb", 5]
Not until they get their gradual typing story straight.
Can you expand on why you found it so appealing or "holy crap, this is awesome" things I should look at first ?
Partly because the handling of JavaScript is much less bespoke and complex.
This is the primary issue with Rails in my experience. It takes intentional effort to internalize the idioms before it clicks and you unlock the magic that makes it so insanely productive. JS devs will keep trying to force backend business logic into Franken-React Stimulus components and complaining it's not very good.
In late 2025 we decided to migrate one of them to Inertia. Public facing pages is already done, and we're 80% through migrating the logged in area (it's a huge app). We choose Vue.js.
It's amazing how powerful this stack is and how little you have to change in the backend.
The people who think that spicy autocomplete actually has an understanding of the slop it's churning out for them.
That said, absolutely worth a look.
Discussion on Hacker News Discussion on lobste.rs
I love a good side-project. Like most geeks, I have a tendency to go down rabbit holes when faced with problems - give me a minor inconvenience and I’ll happily spend weeks building something far more elaborate than the situation warrants. There’s joy in having a playground to explore ideas and “what ifs”; Building things just for the sheer hell of it, as Richard Feynman put it “The Pleasure of Finding Things Out”.
So when my covers band started having trouble keeping track of our setlists and song notes (“How many times do we repeat the ending?”, “Why did we reject this song again?”…) I decided to build an app. We’d tried various approaches from spreadsheets to chat groups, and nothing seemed to work or provide a frictionless way of capturing notes and planning gigs in a consistent way.
I’ve been working on https://setlist.rocks for the last few months in my evenings and spare time and I’m pretty pleased with the result - It’s become an all-in-one tool we use to manage our rehearsals, practice songs and plan gigs. But most importantly (and the subject of this article) is that I’ve also re-discovered just how enjoyable building a web application the old-fashioned way can be! I usually gravitate towards retro-computing projects as I’ve been pretty bummed out with the state of the modern landscape for a while, but I can honestly say I haven’t had this much fun with development in a long time. And that’s mostly due to just how plain awesome Rails is these days.
Table Of Contents
I know, right? Rails. That old thing ? People still use that ? But as I was doing this purely for fun, I decided to forgo the usual stacks-du-jour at $DAYJOB, and go back to my “first love” of Ruby. I also figured it would be a great opportunity to get re-acquainted with the framework that shook things up so much in the early 2000s. I’d been keeping half an eye on it over the years but it’s been a long time since I’ve done anything serious with Rails. The last time I properly sat down with it was probably around the Rails 3-4 era about 13-14 years ago now. Life moved on, I got deep into infrastructure and DevOps work, and Rails faded into the background of my tech stack.
The 2025 Stack Overflow Developer Survey paints a similar picture across the wider developer world as a whole, too. Rails seems to have pretty much fallen out of favour, coming in at #20 underneath the bulk of top-10 JavaScript and ASP.NET frameworks:

And Ruby itself is nowhere near the top 10 languages, sitting just underneath Lua and freaking Assembly language in terms of popularity! I mean, I love me some good ol’ z80 or 68k asm, but come on… For comparison, Javascript is at 66% and Python is at 57.9%.

But I’m a stubborn bastard, and if I find a technology I like, I’ll stick with it particularly for projects where I don’t have to care about what anyone else is using or what the latest trend is. So Ruby never really left me. I’ve always loved it, and given the choice, it’s the first tool I reach for to build something.
In recent years, the need to glue infrastructure together with scripts has diminished somewhat, as most things seem to be “black boxes” driven by YAML manifests or HCL codebases. But when I first discovered Ruby, it felt like finding a language that just worked the way my brain did. Coming from Perl (which I’d adopted for sysadmin scripting after years of shell scripts that had grown far beyond their intended scope), I read Practical Ruby for System Administration cover-to-cover and realised Ruby was “a better Perl than Perl”. There’s the same wonderful expressiveness to it, just without all the weird voodoo. I love the way you can chain methods, the blocks with yield, and how even complex logic reads almost like English. There’s just this minimal translation required between what I’m thinking and what I type. Sure, I can knock things together in Python, Go, or whatever the flavour of the month is, but I always feel on some level like I’m fighting the language rather than working with it. And of course there was the welcoming, quirky “outsider” community feel with characters like Why the Lucky Stiff and their legendary Poignant Guide To Ruby.
I should point out that my interest (and focus of this blog post) has always been firmly in the “engine room” side of development - the sysadmin, DevOps, back-end infrastructure world. Probably for much the same reason I’ve gravitated towards the bass guitar as my musical instrument of choice. Now, I’m conversant in front-end technologies, having been a “webmaster” since the late 90s when we were all slicing up images in Fireworks, wrestling with table-based layouts and running random stuff from Matt’s Script Archive for our interactive needs.
But the modern world of front-end development - JavaScript frameworks, the build tooling, the CSS hacks - it’s never really captured my imagination in the same way. I can bluff my way in it to a certain extent, and I appreciate it on the level I do with, say, a lot of Jazz: It’s technically impressive and I’m in awe of what a skilled developer can do with it, but it’s just not for me. It’s a necessity, not something I’d do for fun.
While I haven’t built or managed a full Rails codebase in years, I’d never completely left the Rails ecosystem. There’s bits and pieces that are just so useful even if you’re just quickly chucking a quick API together with Sinatra. ActiveSupport for example has been a constant companion in various Ruby projects over the years - it’s just so nice being able to write things like
unless date <= 3.days.from_now
or
if upload_size > 2.megabytes
But sitting down with Rails 8 proper was something else. It’s recognisable, certainly - the MVC structure, the conventions, the generators are all where you’d expect them. Someone with my dusty Rails 3 experience can still find their way around and quickly throw up the basic scaffolding. But under the hood and around the edges, it’s become a very different beast.
So let’s tackle this part first. Although it’s an area I usually stay clear of, the first and most apparent changes are how front-end code is handled. As someone who’d rather chew glass than configure Webpack, the “no build” approach Rails 8 has taken is right up my street. I grew up on server-side generated pages as I went through Perl CGI.pm, PHP, Java & Struts and onwards to the “modern era” and really like how I can still use a modernized version of that approach instead of running the entirety of the application in a browser and relegating the backend to purely processing streams of JSON.
I did want to include niceties like drag-and-drop setlist re-ordering though, so I particularly appreciated being able to build an interactive application with modern conveniences while writing the smallest possible amount of JS (again, something I always find I’m fighting against). The default Hotwire (“HTML Over The Wire”) stack of Stimulus and Turbo provided enough low-friction functionality to build my frontend without drowning in JavaScript.
Turbo handles things like intercepting link clicks and form submissions, then swapping out the <body> or targeted fragments of the page to give a Single Page App-like snappiness without actually building a SPA. I could then sprinkle in small Stimulus JS controllers to add specific behaviour where needed, like pop-up modals and more dynamic elements. It was pretty impressive how quickly I could build something that felt like a modern application while still using my familiar standard ERB templates and server-side rendered content.
While Stimulus seems to have a smaller developer community than the big JS toolkits/frameworks, there are plenty of great, carefully-written and designed component libraries you can easily drop into your project. For example check out the Stimulus Library and Stimulus Components projects which include some great components that you can tweak or use directly.
This was my first introduction to the vastly simplified JS library bundling tool that seems to have been introduced around the Rails 7 timeframe. Instead of needing a JS runtime, NPM tooling and separate JS bundling/compliation steps (Webpack - again, urgh….), JS components are now managed with the simple importmap command and tooling. So, to make use of one of those components like the modal Dialog pop-up for example, you just run:
$ bin/importmap pin @stimulus-components/dialog
This downloads the package from a JS CDN and adds it to your vendor directory and updates your config/importmap.rb. The package then gets automatically included in your application with the javascript_importmap_tags ERB tag included in the <head> of the default HTML templates. You can see how this gets expanded if you look at the source of any generated page in your browser:

You can then register the controller as per the docs (a few lines in javascript/controllers/index.js which can be skipped for locally-developed controllers as they’re handled by the autoloader) and get using it right away in your view templates. As the docs say: “This frees you from needing Webpack, Yarn, npm, or any other part of the JavaScript toolchain. All you need is the asset pipeline that’s already included in Rails.”
I can’t express how grateful I am for this change. I’m also annoyed with myself for missing out that this was added back in Rails 7. Had I noticed, I probably would have taken it out for a spin far sooner! I have to confess though that beyond the basics, I have somewhat lacking front-end skills (and was quickly developing The Flexbox Rage), so I took bits from various templates & online component generators, and got Claude to generate the rest with some mockups of common screens and components. I then sliced & diced, copied & pasted my way to a usable UI using a sort of customized “UI toolkit” - Rails partials are great for these sorts of re-usable elements.
I have mixed feelings about this. On the one hand, it helped me skip over the frustrating parts of frontend development that I don’t particularly enjoy, so I could focus on the fun backend stuff. It also did produce an objectively better experience far quicker than anything I’d have been able to come up with purely by myself. On the other… I view most AI-generated content such as music, art & poetry (not to mention the typical LinkedIn slop which triggers a visceral reaction in me) to be deeply objectionable. My writing and artistic content on this site is 100% AI-free for that very reason; To my Gen-Xer mind, these are the things that really define what it means to be human and I find it distasteful and unsettling in the extreme to have these expressions created by an algorithm. And yet - for me, coding is a creative endeavour and some of it can definitely be considered art. Am I a hypocrite to use UI components created with help from an AI ? What (if any) is the difference between that and copying from some Bootstrap template or modifying components from a UI library ? I’m going to have to wrestle with this some more, I think.
A slight detour here to explain my workflow and hopefully illustrate why I love Rails so much in the first place. It really shook things up in the early 2000s - before that, most of the web frameworks I’d used (I’m looking at you, Struts…) were massively complex and required endless amounts of XML boilerplate and other configuration to wire things up. Rails threw all that away and introduced the notion of “convention over configuration” and took full advantage of the expressive, succinct coding style enabled by Ruby.
A good way to get familiar with Rails is to follow the tutorial, but here’s a quick walkthrough of the dev process I’ve used since the early days which highlights how easy it is to get going. So, using the “tags” system (that bands can attach to songs, setlists etc.) as an example: I first planned out the model, what is a tag, what attributes should it have (a text description, a hex colour) and so on. Then I used a Rails generator to create the scaffolding and migrations:
$ bin/rails generate model Tag label:string color:string band:belongs_to
This resulted in a app/models/tag.rb like this:
class Tag < ApplicationRecord
belongs_to :band
end
This automagically fetches the column names and definitions from the database, no other work required! Of course, we usually want to set some validation. There’s all kinds of hooks and additions you can sprinkle here, so if I wanted to validate that for example a valid Hex colour has been set, I could add:
validates :color,
presence: true,
format: { with: /\A#[0-9a-fA-F]{6}\z/, message: "must be valid hex" }
Then I set up URL routing. While you can later get very specific about which routes to create, a simple starting point is just this one line in config/routes.rb
resources :tags
Which generated the standard RESTful routes automatically:
$ rails routes -c TagsController
Prefix Verb URI Pattern Controller#Action
band_tags GET /bands/:band_id/tags(.:format) tags#index
POST /bands/:band_id/tags(.:format) tags#create
new_band_tag GET /bands/:band_id/tags/new(.:format) tags#new
edit_band_tag GET /bands/:band_id/tags/:id/edit(.:format) tags#edit
band_tag GET /bands/:band_id/tags/:id(.:format) tags#show
PATCH /bands/:band_id/tags/:id(.:format) tags#update
PUT /bands/:band_id/tags/:id(.:format) tags#update
DELETE /bands/:band_id/tags/:id(.:format) tags#destroy
Note all the .format stuff - this lets you respond to different “extensions” with different content type. So in this case, requesting /bands/1/tags/5 would return HTML by default, but I could also request /bands/1/tags/5.json and the controller can be informed that I’m expecting a JSON response.
I tend to use this to quickly flesh out the logic of an application without worrying about the presentation until later. For example, in the Tags controller I started with something like this to fetch a record from the DB and return it as JSON:
class TagsController < ApplicationController
# Auth and other stuff skipped for brevity...
def show
@tag = @band.tags.find(params[:id])
respond_to do |format|
format.html # Use ERB template show.html.erb when I implement it
format.json { render json: @tag }
end
end
end
And then I could test my application and logic using the RESTful routes using just plain old curl from my terminal:
$ curl --silent -XGET \
-H "Authorization: Bearer <token>" http://localhost:3000/bands/4/tags/5.json | jq .
{
"id": 5,
"band_id": 4,
"label": "Bass Change",
"color": "#3288bd",
"created_at": "2026-01-15T04:42:24.443Z",
"updated_at": "2026-01-15T04:42:24.443Z"
}
Once that was all working, I moved on to generating the views as standard ERB templates. Combined with live-reloading and other developer niceities, I could go from idea to working proof-of-concept in a stupidly short amount of time. Plus, there seems to be a gem for just about anything you might want to build or integrate with. Want to import a CSV list of songs ? CSV.parse has you covered. How about generating PDFs for print copies of setlists ?
pdf = Prawn::Document.new do
text "I <b>LOVE</b> Ruby", inline_format: true
end
print pdf.render
And so on. Have I mentioned I love Ruby?
I’ve always liked the way Rails lets you enable components and patterns as you scale. You can start small on just SQLite, move to a dedicated database server when traffic demands it, then layer in caching, background jobs and the rest as the need arises.
But the problem there is all the additional infrastructure you need to stand up to support these things. Want caching? Stand up Redis or a Memcache. Need a job queue or scheduled tasks? Redis again. And then there’s the Ruby libraries like Resque or Sidekiq to interact with all that… Working at GitLab, I certainly appreciated Sidekiq for what it does, but for the odd async task in a small app it’s overkill.
This is where the new Solid* libraries (Solid Cache, Solid Queue and Solid Cable) included in Rails 8 really shine. Solid Cache uses a database instead of an in-memory store, the thinking being that modern storage is plenty fast enough for caching purposes. This means you can cache a lot more than you would do with a memory-based store (pretty handy these days in the middle of a RAM shortage!), but you also don’t need another layer such as Redis.
Everything is already setup to make use of this, all you need to do is start using it using standard Rails caching patterns. For example, I make extensive use of fragment caching in ERB templates where entire rendered blocks of HTML are stored in the cache. This can be something simple like caching for a specific time period:
<% cache "time_based", expires_in: 5.minutes do %>
<!-- content goes here -->
<% end %>
Or based on a model, so when the model gets updated the cache will be re-generated:
<% cache ["band_dashboard", @band.cache_key_with_version, expires_in: 1.hour] do %>
<!-- dashboard content here -->
<% end %>
And sure enough, you can see the results in the SQLite DB using your usual tools. Here’s the table schema:
sqlite> .mode column
sqlite> PRAGMA table_info(solid_cache_entries);
cid name type notnull dflt_value pk
--- ---------- --------------- ------- ---------- --
0 id INTEGER 1 1
1 key blob(1024) 1 0
2 value blob(536870912) 1 0
3 created_at datetime(6) 1 0
4 key_hash integer(8) 1 0
5 byte_size integer(4) 1 0
And we can examine the cache contents:
sqlite> select id,substr(key,1,40),created_at,byte_size from solid_cache_entries;
id substr(key,1,40) created_at byte_size
-- ---------------------------------------- ----------------------- ---------
1 development:views/home/index:09337f42ae0 2026-03-06 09:29:06.237 2034
2 development:views/band_dashboard/bands/4 2026-03-06 09:34:17.591 1990
3 development:views/band_dashboard/bands/4 2026-03-06 17:43:56.357 1992
4 development:views/band_dashboard/bands/4 2026-03-06 17:56:26.855 1992
5 development:views/band_show/bands/4-2026 2026-03-06 18:02:06.766 2244
Note though that the actual cached values are serialized Ruby objects stored as BLOBs, so you can’t easily view/decode them outside of the Rails console.
Solid Queue likewise removes the dependency on Redis to manage background jobs. Just like Solid Cache, it by default will use a database for this task. I also don’t need to start separate processes in my dev environment, all that is required is a simple SOLID_QUEUE_IN_PUMA=1 bundle exec rails server and it runs an in-process queue manager.
Declaring jobs is equally simple:
# app/jobs/my_sample_job.rb
class MySampleJob < ApplicationJob
queue_as :default
def perform
Rails.logger.info "Yup, I still love Ruby..."
end
end
And is scheduled in a typically plan-language fashion:
# config/recurring.yml
production:
sample_job:
class: MySampleJob
schedule: every day at 3am
Beautiful! The upshot is that I could start making use of all these features from the get-go, with far less fiddling required, and running entirely off a SQLite database.
I honestly didn’t use Solid Cable much, apart from indirectly. It’s an Action Cable adapter which again uses a database by default. It’s useful for real-time websockets features, although I only ended up using it to enable debugbar for local testing. Debugbar provides a handy toolbar that lets you inspect your SQL queries, HTTP requests and other useful debugging features while you’re developing. Reminded me a lot of the debug toolbars found in a lot of PHP frameworks like Symfony. Still, I really appreciated again being able to make use of all this without needing to spin up additional infrastructure.
The last component I didn’t really look into (although I’m kinda having second thoughts about that) is the new authentication generators. Rails 8 ships with a built-in authentication generator which is a bit of a game changer for smaller projects. It’s not trying to be everything, it just scaffolds out a clean, no-nonsense auth system but is vastly simpler than using something like Devise which was always my go-to. Devise is incredibly full featured and offers things like built-in sign-up flow, account locking, email confirmations and lots of extension points. I wanted to do things like hook into Omniauth for “Login with Google”, add token auth for local testing with curl and there’s just way more guides and documentation available with Devise. Plus it was just easier for me to pick back up again, so that’s what I started with and I’m pretty happy with it.
That said, Devise is a bit of a beast. The more I look into the auth generators, the more I like the simple understandable philosophy and as I read more about the comparisons, if I was starting all over again I’d probably lean more towards the native Rails option just because honestly it feels like it’d be more fun to hack on. But with things like Auth, there’s a lot to be said for sticking to the beaten path!
This is another area where Rails 8 gave me a very pleasant surprise. I really like PostgreSQL as a database (and much more besides) - I used to maintain the Solaris packages for Blastwave/OpenCSW waaaay back (now that really does age me!) and have run it in production for decades now. But it’s still another dependency to manage, back-up and scale. SQLite by comparison is as simple as it comes: Single file, no DB server required. It can also be pretty efficient and fast, but while it can be used for high-performance, read-heavy applications it always used to require a fair amount of tuning and patching of Rails to get there.
Rails used to use SQLite with its default settings, which were optimized for safety and backward compatibility rather than performance. It was great in a development environment, but typically things started to fall apart the moment you tried to use it for production-like load. Specifically, you used to have to tweak various PRAGMA statements:
journal_mode: The default rollback journal meant readers could block writers and vice-versa, so you effectively serialized all database access. This was a major bottleneck and most apps would see frequent SQLITE_BUSY errors start to stack up as a result. Instead, you can switch it to WAL mode which uses a write-ahead journal and allows readers and writers to access the DB concurrently.
synchronous: The default here (FULL) meant SQLite would force a full sync to disk after every transaction. But for most web apps, if you use NORMAL (sync at critical moments) along with the WAL journal, you get much faster write performance albeit with a slight risk of losing the last transaction if you have a crash or power failure. That’s usually acceptable though.
Various other related pragmas which had to be tuned like mmap_size, cache_size and journal_size_limit to make effective use of memory and prevent unlimited growth of the journal, busy_timeout to make sure lock contention didn’t trigger an immediate failure and so on…
All in all, it was a pretty big “laundry list” of things to monitor and tune which only reinforced the notion that SQLite was a toy database unsuitable for production. And it was made more complex because there wasn’t an easy way to set these parameters. So you’d typically have to create an initializer that ran raw SQL pragmas on each new connection:
ActiveSupport::on_load(:active_record_sqlite3adapter) do
module SQLitePragmas
def configure_connection
super
execute("PRAGMA journal_mode = WAL")
execute("PRAGMA synchronous = NORMAL")
execute("PRAGMA mmap_size = 134217728")
# etc...
end
end
class ActiveRecord::ConnectionAdapters::SQLite3Adapter
prepend SQLitePragmas
end
end
This was obviously pretty fragile, so most developers I worked with simply never did it, and just followed the pattern of “SQLite on my laptop, big boy pants database for anything else”.
When I checked out Rails 8, I noticed straight away that not only is there now a new pragmas: block available in the database.yml, but the defaults are now also set to sensible values for a production application. The values provided to my fresh Rails app were equivalent to:
production:
adapter: sqlite3
database: storage/production.sqlite3
pragmas:
journal_mode: wal
synchronous: normal
mmap_size: 134217728
cache_size: 2000
busy_timeout: 5000
foreign_keys: true
journal_size_limit: 67108864
All this makes SQLite a genuinely viable production database for small-to-medium Rails applications and combined with the Solid* components, means it’s not just a local dev or “getting started” convenience!
If you have an older Rails codebase and want to use a similar approach, a neat method of monkey-patching the SQLite adapter to provide a similar pragmas: section in the database configuration is detailed in this great article.
However, deploying Rails apps was always the weak spot. I remember being blown away by the demos of “let’s build a blog from zero in a few minutes” but was always frustrated that the same developer elegance didn’t extend to the deployment side of things. Things like Passenger (née mod_rails) and Litespeed eventually helped by bringing a sort of PHP-like “just copy my code to a remote directory” method of deployment, but I still remember pushing stuff out with non-trivial Capistrano configs or hand-rolled Ansible playbooks to handle deployments, migrations and restarts. And then there were all the extra supporting components that would inevitably be required at each step along the way.
I had to include that old capture of the modrails.com site circa-2008 because a.) I really miss when websites had that kind of character, and b.) that is still a totally sick wildstyle logo 😄
This is why services like Heroku and Pivotal Cloud Foundry thrived back then - they offered a pain-free, albeit opinionated way to handle all this complexity. As the Pivotal haiku put it:
Here is my source code
Run it in the cloud for me
I do not care how.
You just did a git push or cf push, vague magic happened, and your code got turned into containers, linked to services and deployed.
These days I prefer to do the building of containers myself. Creating an OCI image as an artifact gives me flexibility over where things run and opens up all kinds of options. Today it might be a simple docker-compose stack on a single VPS, tomorrow it could be scaled out across a Kubernetes cluster via a Helm chart or operator. The container part is straight-foward as Rails creates a Dockerfile in each new application which is pretty much prod-ready. I usually tweak it slightly by adopting a “meta” container approach where I move some of the stuff that changes infrequently like installing gems, running apt-get and so on into an image that the main Dockerfile uses as a base.
You’re of course free to use any method you like to deploy that container, but Rails 8 makes Kamal the new default and it is an absolute joy to use.
I’ve seen some dissenting opinions on this, but bear in mind I’m coming from a place where I’m already building containers for everything anyway. I generally think this is “the way to go” these days and have the rest of the infra like CI/CD pipelines, container registries, monitoring and so on. Plus, given my background, I crank out VMs and cloud hosts with Terraform/Ansible “all day errday”. If you don’t have this stuff already or aren’t happy (or don’t have the time) to manage your own servers remember that Kamal is not a PaaS. It just gets you close to a self-hosted environment that functions very much like a PaaS. Now that Heroku is in a “sustaining engineering model” state, there are several options in the PaaS space you may want to investigate if that’s more up your street. I hear good things about fly.io but hasten to add I haven’t used it myself.
Your Kamal deployment configuration lives in a deploy.yml file where you define your servers by role: web frontends, background job workers and so on:
servers:
web:
- web.rails.example.com
job:
hosts:
- jobs.rails.example.com
cmd: bin/jobs
Or you can point everything to a single host and scale out later. These files can also inherit a base which makes splitting out the differences between environments simple. There’s also handy aliases defined which makes interacting with the containers easy, all that is required is a SSH connection to the remote hosts.
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password"
When you deploy, Kamal will:
The routing bit is handled by kamal-proxy, a lightweight reverse proxy that sits in front of your application on each web server. When a new version deploys, kamal-proxy handles the zero-downtime switchover: It spins up the new container, health-checks it, then seamlessly cuts traffic over before stopping the old one. I front everything through Nginx (which is also where I do TLS termination) for consistency with the rest of my environment, but kamal-proxy doesn’t require any of that. It can handle your traffic directly and does SSL termination via Let’s Encrypt out of the box.
Secrets are handled sensibly too. Rather than committing credentials to your repo or fiddling with encrypted files, Kamal reads secrets from a .kamal/secrets file that simply points at other sources of secrets. These get injected as environment variables at deploy time, so you can safely handle your registry password, Rails master key, database credentials and so on. You can also pull secrets from external sources like 1Password or AWS SSM if you want something more sophisticated, and the sample file contains examples to get you going.
That’s a lot, but bear in mind it’s all driven by a single command: kamal deploy.
Here’s an Asciinema capture of a real-life manual deploy session including a look at what’s happening on my staging server in my homelab:
I have this triggered by GitLab CI pipelines, with protected branches for each of my environments. So usually, deployment happens after a simple git push or merge request being approved. The upshot is that it feels like that old Heroku magic again, except you own the whole stack and can see exactly what’s happening. A single kamal deploy builds, pushes and rolls out your changes across however many servers you’ve configured. It’s the kind of tooling Rails has needed for years.
Well, nothing’s perfect and I feel like if I use any technology for long enough I’ll eventually find something about it that pisses me off. I just tend to gravitate towards things that piss me off the least and avoid those that give me the “red mist” without any balancing upsides that make the pain worthwhile. Ruby and Rails definitely falls firmly into the former camp, but that’s like, just my opinion, man.
What I find appealing about the “magic” of Ruby might feel opaque and confusing to you. If you like expressive code and come from a Perl “There Is More Than One Way To Do It” background, I imagine you’ll love it. But I’ve come to realise that choice of tools (vi vs emacs vs vscode - FIGHT!) can be a very personal matter and often reflect far more of how our own minds work. Particularly so when it comes down to something like language and framework choice: These are the lowest layers that are responsible for turning your thoughts and ideas into executable code.
As a matter of taste, Ruby lines up more or less exactly with my sense of aesthetics about what a good system should be. But it is certainly an acquired taste, and that’s the biggest downside. Remember the survey results from the top of this article ? There’s no denying that Ruby and Rails’ appeal has become…. “more selective” over the years - to coin another phrase, this time from Spinal Tap.
It’s used in a lot of places that don’t make a lot of noise about it (some might surprise you), and there are still plenty of big established names like Shopify, Soundcloud and Basecamp running on Rails. Oh and GitHub, although I’m not sure we should shout about that anymore… But. While the Stack Overflow survey isn’t necessarily an accurate barometer of developer opinion, the positions of Ruby and Rails do show it’s fallen from grace in recent times. Anecdotally, I find a lot of documentation or guides that haven’t been updated for several years and the same goes for a lot of gems, plugins and other projects. Banners like this are becoming more and more common:

And I find that most gems follow a similar downward trend of activity. Take Devise for example. Plotting a graph of releases shows a pattern I see around a lot of Rails-adjacent projects. Big spikes or projects launched around the Rails “glory years” and then slowly trailing off into maintenance mode:

Apart from a spike in 2016 where it appears there was a bunch of activity around the v4 release, it’s been pretty quiet since then. The optimist might say that’s because by this point, most of these projects are simply “done”. These are really mature, reliable projects with around 2 decades of history running mission critical, high traffic websites. At what point are there simply no more features to add ?
But let’s look at the flipside. Rails on the other hand actually seems to be picking up steam and has been remarkably consistent since the big “boom” of Rails 3.0 in 2010:

Despite the changing trends of the day, it’s consistently shipped releases every single year since it hit the bigtime. If anything, Rails is a rare example of an OSS project that’s grown into its release cadence rather than burning out. Whether it can still find an audience amongst new developers is an open question but I’m glad there are obviously a few more stubborn bastards like myself refusing to let go of what is clearly, for us, a very good thing. I probably could eventually build things almost as fast in another language or framework, but I doubt I’d be smiling as much while I did so.
If you’ve made it this far, congratulations and “thanks for coming to my TED talk” / protracted rant! I’m guessing something has piqued your curiosity, and if so, I highly recommend taking Rails out for a spin. Work through the tutorial, build something cool, and above all enjoy yourself while you’re at it - because at the end of the day, that’s what it’s all about. Sure, there are more popular frameworks that’ll make a bigger splash on your resume. But as I said at the start, sometimes it’s worth doing things just for the sheer hell of it.
Have Fun!
❤️
The opinions and views expressed on this website are my own and do not necessarily reflect the views of my employer, past or present.
Could you elaborate on that?
One-off libraries that don't have a runtime dependency on Rails are typically very low-maintenance. You can mostly leave them alone (even a security vulnerability is unlikely to be exploitable for how you're using one of these, as often user input isn't even getting through to them). For instance a gem you install to communicate with the stripe API is not typically going to break when you upgrade Rails. Or adding httparty to make some API requests to other services.
Then there are libraries that are really framework extensions, like devise for authentication or rspec for testing. These are tightly coupled to Rails, sometimes to its private internals, and you get all sorts of nasty compatibility issues when the framework changes. You have to upgrade Rails itself because you really do need to care about security support at that level, even for a relatively small company, so you can end up in a situation where leaving these other dependencies to fester makes upgrading Rails very hard.
(I run a startup that's a software-enabled service to upgrade old Rails apps).
I think rust programers are more likely to want flask/rack than Django/rails.
Obviously there are some dependencies that you cannot easily avoid (like the things you mention). On the other hand there is a lot off stuff used that is not that hard to avoid - things like wrappers for REST APIs are often not really necessary.
I'll say this, coding agents make the lack of a "batteries included" framework like rails or Django somewhat less daunting.
But "convention over code" and having a default structure / shape for projects is extremely helpful and you feel it when it's missing.
For my last small project I looked at Loco but ended up passing on it because I felt like adoption wasn't great yet. I really hope it takes off, though.
I don't understand...Rails does not replace a HA PostgreSQL cluster or Redis, they are orthogonal. Why would you not have to think about them?
Before you get to a scale where Rails become a problem you need to have a product that drives a pretty significant engagement, that’s where most fail.
But if someone is actually relying on literal true/false instead of truthiness, you now have a bug.
I say this as a Ruby evangelist and apologist, who deeply loves the language and who’s used it professionally and still uses it for virtually all of my personal projects.
When it comes to web development specifically, what really got me hooked, was LiveView from the Phoenix framework. It keeps a persistant WebSocket connection to the client which it uses to push DOM updates directly. Instead of the usual request/response cycle on the client side, the server holds the state and just pushes the diff to the browser. It just made so much sense.
I still love and use ruby a ton for scripting, and still reach for Sinatra for super simple server needs, but Phoenix is my go-to stack these days.
I've also found the Elixir community to be amazing in the same ways the Ruby community is/was. It's not all roses, for example there's not as many libraries out there. Distribution is also not awesome so for example I currently use ruby or rust when writing CLIs. But for anything distributed (especially web) Phoenix is amazing.
This is a self plug, but I did a conference talk introducing Ruby veterans to Elixir/Phoenix some years ago. It's probably aged a bit, but should still be pretty accurate. https://www.youtube.com/watch?v=uPWMBDTPMkQ
The original conference talk is here (https://www.youtube.com/watch?v=sSoz7q37KGE), though the made-for-youtube version above is better because it's slightly updated, and I didn't run out of time :-)
The reason it's lacking features is because it's not very popular and hasn't gotten much outside contribution... I seem to also recall something about the founder being too hostile to outside ideas/suggestions also, but I could be misremembering
The agents pick up conventions from the extensive code in their corpus and aggressively follow them. I don't think Rails being explicit about it adds a lot unless someone is prone to prompting towards absurdity.
Typical DHH insanity.
Thankfully, updating to a new Django version is usually simple. It does not require many code changes.
But finding small bugs after an update is hard, unless you have very good test coverage. New versions of middleware/Django plugins often behave slightly differently, and it's hard to keep track of all the changes when you have so many dependencies.
The troubles arise when you get to huge codebases or complicated frontend patterns that aren't ideal for SSR / hotwire.
Also, it's impossible to separate Rails from DHH, whose xenophobic politics are unfortunately front and center.
Django had private use before then, but rails was also in private use before it was released.
Elixir is a functional programming language based on the "BEAM", the Erlang VM. We'll get back to the BEAM in a moment, but first: the functional programming aspect. That definitely took getting used to. I remember being _very_ confused in the first few weeks. Not because of the syntax (Elixir is quite Ruby-esque) but because of the "flow" of code.
However, when it clicked, it was immediately clear how easy it becomes to write elegant and maintainable code. There is no global state in Elixir, and using macros for meta-programming are generally not encouraged. That means it becomes very easy to reason about a module/function: some data comes in, a function does something with that data, and some data comes out. If you need to do more things to the data, then you chain multiple functions in a "pipe", just like how you chain multiple bash tools on the command line.
The Phoenix framework applies this concept to the web, and it works very well, because if you think about it: a browser opening a web page is just some data coming in (an HTTP GET request), you do something with that data (render a HTML page, fetch something from your database, ...) and you return the result (in this case as an HTTP response). So the flow of a web request, and your controllers in general, becomes very easy to reason about and understand.
Coming back to the BEAM, the Erlang VM was originally written for large scale (as in, country size) telephony systems by Ericsson. The general idea is that everything in the BEAM is a "process", and the BEAM manages processes and their dependencies/relationships for you. So your database connection pool is actually a bunch of BEAM processes. Multi-threading is built-in and doesn't need any setup or configuration. You don't need Redis for caching, you just have a BEAM process that holds some cache in-memory. A websocket connection between a user and your application gets a separate process. Clustering multiple web servers together is built into the BEAM, so you don't need a complex clustering layer.
The nice thing is that Elixir and Phoenix abstract most of this away from you (although it's very easy to work with that lower layer if you want to), but you still get all the benefits of the BEAM.
However having worked with Typescript for 8 years now... I'm not sure I could go back to Ruby without types. For LLMs thats important as well, the more guard rails you can give them the better. What's the state of type checkers today?
Frameworks and structure will save you from neither stupidity nor ignorance.
Elixir is also a very cool language in a lot of ways. I wouldn't go all in on Elixir/Phoenix, but that's because there's not a huge demand for it, at least where I reside. I would 100% consider it for some smaller projects though, if I stood between that and Rails, and I wouldn't mind having to get more comfortable with Elixir.
Edit: I haven't used Rails 8, and haven't followed the ecosystem since a bit before, so not sure how this feels nowadays. I *really* enjoy Rails backend though, but the frontend stuff never quite clicked.
that's not how it works. and i'm fairly sure most all apps deal with databases, unless they're explicitly static pages.
edit: sql injection is about hacking the parameters used in a query. they almost always in some way come from external sources, user input. so they have to be sanitized. it sounds straightforward but bounties are paid all the time on hackerone with documented cases of injection. people are very clever.
i've had to patch some verified cases where the hacker used the name field to pass code in and alter links in emails to make it look like they came from our (household name) company.
Also breaking APIs should be regarded very poorly. It isn’t. But it should be.
Do I need a library to sort an array? The 5 years ago option is going to be the more likely choice. A library updated 2 weeks ago is highly suspicious.
Do I need a library to provide timezone information? The 2 weeks ago option, unquestionably. The 5 years ago option will now be woefully out of date.
Very true for me as well. I've never worked with Ruby but feel the same way about Django.
Btw, if you're looking for a "Rails but with TypeScript," my colleagues and I are working on almost just that: https://wasp.sh/.
The main difference, besides the ecosystem, is that we're more in the "configuration over convention" camp. Wasp has a simple DSL for specifying said configuration, but it's about to be replaced with a TypeScript file.
Wasp is still in beta and nowhere near Rails-level polish. But, depending on your early adopter tendencies, you might find it interesting regardless. If you do try it out, please reach out and share your thoughts.
All right, fine: TypeScript uses structural typing which is if you like a specialisation of duck typing but, whatever, compared with JS's unadorned duck typing it still leads to embellishment of the resulting code in ways that I don't enjoy.
I've been using TypeScript across different projects at different companies since 2013 and I've absolutely given it an honest go... but I just don't like it. I even allowed its use at a mid-size company where I was CTO because it fit well with React and a sensible person picks their battles, but I still didn't like it.
I'm now in the very privileged position where I don't have to use it, and I don't even have to allow it a foot in the door.
Now I'm sure that won't last forever, and I'll have to work with TypeScript again. I'll do it - because I'm a professional - but I'm still entitled to an opinion, and that opinion remains that I don't like the language. After 13 years of use I feel pretty confident my opinion has settled and is unlikely to change. I find it deeply unenjoyable to work with. BUT the plus side is that in the era of LLMs perhaps I no longer need to worry so much about have to deal with it directly when it eventually does impinge upon my professional life again.
Django's was July 2005: https://www.djangoproject.com/weblog/2005/jul/15/chipy/
You just keep up as you go, as long as you keep things close to the framework it's fine.
However, the fact its still the js ecosystem with react, thing is even though it's super productive in churning out the code, there's too many possible ways to do something. it's unwieldy.
For example Claude is obsessed with making react context providers. it'll make tons of them to power every feature. and your app will happily hold 20 layers of russian doll'd state in memory with no way to link to anything.
you have to tell it, no don't do that. i need you to power this thing through the router, through the url. and that has to be designed cohesively. and that's very different from the context free-for-all.
You might sanitize for different reasons like business logic, but if it's your first line of defense against sql injection, you're already on the losing side.
Because there are fewer and fewer ruby/rails people available.
It is the simplest explanation - and the one that makes the most sense, too.
It has been absolutely wonderful building this with Elixir/Phoenix. Obviously any codebase in any language can become a tangled mess, but in 7 years we have never felt the language or framework were in our way.
On the contrary: I think Elixir (and Phoenix) have enabled us to build things in a simple and elegant way that would have taken more code, more infrastructure, and more maintenance in other languages/frameworks.
I agree. Not only that, I feel like TypeScript is currently the only popular high-level language with a type system capable of communicating all meaningful information. It seems to have hit an LLM sweet spot.
Looking at other candidates:
- Rust is popular and has a powerful type system, but it forces you to program at a level that's lower than necessary for most projects, hindering usability.
- Go is much more usable and very popular, but its type system can't communicate much.
- Haskell has an excellent type system, but it's nowhere near popular enough, and its usability suffers due to esoteric constraints (laziness, purity).
- etc.
I don't know the recent developments in Python's and Ruby's type systems. They may be able to compete these days, but they were nowhere near TS's level in terms of contract a few years ago when I last tried them out.
And I admittedly have no idea what's going on with C# and Java, but I'd love to hear about it.
I always liked Rich Hickey's point, that you should program on the inside the way you program on the outside. Over the wire you don't rely on types and make sure the entire internet is in type check harmony, it's on you to verify what you get, and that was what Alan Kay thought objects should do.
That's why I always find these complaints a bit puzzling. Yes in a dynamic language like Ruby, Python, Clojure, Smalltalk you can't impose global meaning, but you're not supposed to. If you have to edit countless of existing code just because some sender changed that's an indication you've ignored the principle of letting the recipient interpret the message. It shouldn't matter what someone else puts in a map, only what you take out of it, same way you don't care if the contents of the post truck change as long as your package is in it.
He said "Updating a project that was started 5-6 years ago takes a lot of time."
The BEAM itself runs multiple OS threads (it can use all cores of the CPU if so desired), and the BEAM scheduler gives chunks of processing time to each BEAM process.
This gives you parallel processing out of the box, and because of the networking capabilities of the BEAM, also allows you to scale out over multiple machines in a way that's transparent to BEAM processes.
As an example, the "react" is just a view layer is a purest pov, to me (or whatever the given framework in question is). Nextjs, Vercel, supabase, lovable, and so on down the line all empower millions of people to ship full-blown apps. We might get carried away with which specific layer is in question, but it doesn't matter if they're always used together to ship millions of (in)secure apps.
Yeah, I agree, and the thing is, you're going to write automated tests whether you're developing in JavaScript or TypeScript so the extra cruft of TypeScript seems even less worthwhile.
The argument I've heard people put forward is that JS is fine for small projects or a couple of developers but doesn't scale to large projects or teams. I don't know how large large is, but I've worked on a project with around 30 devs where the front-end was all JavaScript and everyone was touching it and, sure, that project had some problems, but the root cause of those problems wasn't using JavaScript.
Testing is more expensive up front and in maintenance than type annotations. A test suite comprehensive enough to replace type annotations would have an ass load of assertions that just asserted variable type; if you were involved in early pre-TS Node, you remember those test suites and how they basically made code immutable.
> (2) it regularly missed deeper problems
This is a skill issue. If your types do not match runtime behavior and you choose to blame the programming language rather than your usage of it, that's on you. There are a lot of unsafe edges to TS, but a diligent and disciplined engineer can keep them isolated and wrapped in safe blocks. Turn off `any`, turn on all the maximal strictness checks, and see if your code still passes, because if what you said about infinite scroll is true, it won't.
And one of its many problems is that it tries quite hard to pass itself off as not having those unsafe edges which, ironically, makes it easier to get tripped up by them.
that's not a big deal, when we exchange generic information across networks we parse information all the time, in most use cases that's not an expensive operation. The gain is that this results in proper encapsulation, because the flipside of imposing meaning globally is that your entire codebase is one entangled ball, and as you scale a complex system, that tends to cost you more and more.
In the case of the OP where a program "breaks" and has to be recompiled every time some signature propagates through the entire system that is significant cost. Again if you think of a large scale computer network as an analog to a program, what costs more, parsing an input or rebooting and editing the entire system every time we add a field somewhere to a data structure, most consumers of that data don't care about?
this is how we got micro-services, which are nothing else but ways to introduce late binding and dynamism into static environments.
The goal is to do this parsing exactly once, at the system boundary, and thereafter keep the already-parsed data in a box that has "This has already been parsed and we know it's correct" written on the outside, so that nothing internal needs to worry about that again. And the absolute best kind of box is a type, because it's pretty easy to enforce that the parser function is the only piece of code in the entire system that can create a value of that type, and as soon as you do this, that entire class of problems goes away.
This idea is of using types whose instances can only be created by parser functions is known as Parse, Don't Validate, and while it's possible and useful to apply the general idea in a dynamically typed language, you only get the "We know at compile time that this problem cannot exist" guarantee if you use types.
The answer is the same in both cases: acquire some discipline and treat maintenance with the respect it deserves.
You are only parsing once at the system boundary, but under the dynamic model every receiver is its own system boundary. Like the earlier comment pointed out, micro services emerged to provide a way to hack Kay's actor model onto languages that don't offer the dynamicism natively. Yes, you are only parsing once in each service, but ultimately you are still parsing many times when you look at the entire program as a whole. "Parse, don't validate" doesn't really change anything.
I'm not claiming that it can't be done that way, I'm claiming that it's better not to do it that way.
You could achieve security by hiring a separate guard to stand outside each room in your office building, but it's cheaper and just as secure to hire a single guard to stand outside the entrance to the building.
>micro services emerged to provide a way to hack Kay's actor model onto languages that don't offer the dynamicism natively
I think microservices emerged for a different reason: to make more efficient use of hardware at scale. (A monolith that does everything is in every way easier to work with.) One downside of microservices is the much-increased system boundary size they imply -- this hole in the type system forces a lot more parsing and makes it harder to reason about the effects of local changes.
Same thing, no? That is exactly was what Kay was talking about. That was his vision: Infinite nodes all interconnected, sending messages to each other. That is why Smalltalk was designed the way it was. While the mainstream Smalltalk implementations got stuck in a single image model, Kay and others did try working on projects to carry the vision forward. Erlang had some success with the same essential concept.
> I'm claiming that it's better not to do it that way.
Is it fundamentally better, or is it only better because the alternative was never fully realized? For something of modern relevance, take LLMs. In your model, you have to have the hardware to run the LLM on your local machine, which for a frontier model is quite the ask. Or you can write all kinds of crazy, convoluted code to pass the work off to another machine. In Kay's world, being able to access an LLM on another machine is a feature built right into the language. Code running on another machine is the same as code running on your own machine.
I'm reminded of what you said about "Parse, don't validate" types. Like you alluded to, you can write all kinds of tests to essentially validate the same properties as the type system, but when the language gives you a type system you get all that for free, which you saw as a benefit. But now it seems you are suggesting it is actually better for the compiler to do very little and that it is best to write your own code to deal with all the things you need.
Scaling different areas of an application is one thing. Being able to use different technology choices for different areas is another, even at low scale. And being able to have teams own individual areas of an application via a reasonably hard boundary is a third.