A Ruby stack for modern web appsJune 24, 2022 · Felipe Vogel ·
When I started my career switch into software development two years ago, I decided to focus my efforts on Ruby. I did this for a few reasons, but one of them is that Rails offers great “bang for the buck”: there’s a lot that I can build with just Rails, HTML, and CSS.
However, this minimal vanilla stack becomes limiting when two factors come into play:
- The MVC architecture of Rails won’t always be enough to keep your code organized. You’ll notice it painfully if your app ever grows beyond a small project.
- For your app to feel modern, the frontend will need to act like an SPA (Single-Page Application). The “official” way to do this is now Hotwire, but there are other tools worth keeping in the toolbelt. More on that below.
Making a new acronym for your favorite tech stack is a popular thing these days, so I’ll coin a new acronym: RVTWS… pronounced “erv toes”? Yes, this is great. It will go viral in no time.
Joking aside, I’m using this acronym here only as an outline for this blog post, rather than for any marketing value. The “V” (ViewComponent) addresses point #1 above, and “TWS” are concerned with #2.
- Rails or Roda
- ViewComponent: for frontend architecture
- Turbo: for an SPA feel, using the server
- Web Components: for an SPA feel, using the client
- StimulusReflex: for more complex frontend magic
Rails or Roda
There’s not much to say about Rails: it’s boring tech, and therefore a good choice for most web apps.
Roda is also worth considering. Unlike the batteries-included philosophy of Rails, Roda is bare-bones by default but highly extensible. This makes Roda apps fast and more architecturally flexible. Besides the docs, here are some places to get started with Roda:
- An interview with its creator Jeremy Evans.
- Roda + Sequel app skeleton, which has a similar purpose to the
- A post on Bridgetown’s ongoing Roda integration, which is making Roda more accessible thanks to Bridgetown’s batteries-included approach.
Partials are the standard Rails way to define distinct parts of a view. Partials provide a way to separate out part of a template, but they don’t provide a way to separate out view-related logic, which often ends up being thrown into models and/or controllers. This is one reason why models and controllers in Rails can so easily become huge and messy.
ViewComponent provides a home for this view-related logic. This post on the Code with Jason blog explains it best. In short, ViewComponent fills a big gap in MVC architecture.
Some readers may be wondering, “Is that all? What about other architectural improvements, like service objects?” It’s true that certain types of apps have problems best solved with certain design patterns, and maybe a very large app needs a specialized architecture. But in general, Rails models designed in a careful, object-oriented way can take you very far, and I think the popularity of service objects is unjustified because they can easily muddle up a codebase. Food for thought:
- Why Service Objects are an Anti-Pattern at Fullstack Ruby
- How I Organize My Rails Apps at Code with Jason
On to the frontend! Turbo is part of Hotwire, which now ships with Rails. Turbo makes it really easy to give server-rendered pages a snappy SPA feel, where parts of the page are updated instantly instead of a full page reload.
At the heart of Turbo is “HTML over the wire” (for which HOTWire is an acronym), which means the server sending HTML fragments for partial page updates, which (here’s the big win) eliminates the need for client-side state management. There are lots of tools taking this approach now.
Unpoly and HTMX are two of the most intriguing alternatives to Turbo because they are more framework-agnostic and have a flexible, concise syntax. Turbo, on the other hand, seems easier to get started with if you’re in Rubyland.
Sidenote: If you’re wondering why all these “HTML over the wire” tools came about and what they’re pushing back against, take a look at these two comparisons of web app architecture from 2005 vs. ten years later: one from Unpoly (2005) vs. 2015), and another from the developer of Turbo’s predecessor Turbolinks (2005 vs. 2016).
Besides Turbo, the other part of Hotwire is Stimulus, which typically is used for adding client-side reactivity in situations where you want to sprinkle in some JS. After all, you wouldn’t want every user interaction to involve a round trip to the server. So why am I including only Turbo here and not Stimulus?
Actually, Stimulus is pretty cool because you can compose multiple pre-built behaviors into one Stimulus controller, for a sort of functional approach to component behaviors. The tradeoff is that a growing web of Stimulus controllers (plus HTML data attributes associated with them) can become complex and hard to understand.
Web components are an architecturally simpler way to add client-side behaviors, and they also have the advantage that they’re a web standard. This blog post on Fullstack Ruby shows the power of web components in the context of Ruby.
Turbo + web components can take you a long way in making your Rails app feel modern, but there are other tools in this space that can complement them. I’ve already mentioned Stimulus, but there’s also StimulusReflex which is like Stimulus but on the server, and CableReady which is somewhat like Turbo Streams but more flexible. Be sure to give these a try if your app is highly interactive, or if you just want to expand your horizons beyond Hotwire.
Also, the StimulusReflex Discord server is an amazing place even for discussing all the other tech mentioned in this post. (Which is very ironic. Ever since Hotwire’s release, it has marketed itself as THE solution, not even mentioning the similar tools had already been coming out of the StimulusReflex community for two years. Even so, it’s in this very community that you can find the best Hotwire support.)
I’ve been thinking about this ideal Ruby stack because for the first time I’m working on a Rails app that has a React frontend, and it’s been a painful adjustment. Sometimes it feels like I’m writing logic twice, once on the backend and again on the frontend. And what do I get for it? Smoother page transitions and buttons that do things without refreshing the page. That’s nice, but do these simple enhancements have to involve so much extra work?
On the other extreme, the “vanilla Rails” approach to writing views is clunky and outdated, and Hotwire doesn’t fix all the omissions. It’s no wonder that people go looking for outside frontend solutions such as React.
So I started dreaming about what frontend tools could give a smooth user experience without so much extra complexity, and this “RVTWS” stack is the result. (Yeah, I need to work on that acronym.)
On a final note, if you’d like to learn more about Hotwire and StimulusReflex, check out the resources that I’ve compiled for both of them in my “Learning Ruby” list.