The Trouble with Turbo
What happens when Ruby on Rails, a framework designed around server-side rendering, has to support Single Page Applications (SPAs)? Many moons ago, the answer was a combination of jQuery and AJAX. But even back then, fledgling frameworks like React, Ember, Angular (RIP), and Vue were making it obvious that the writing was on the wall for the jQuery/AJAX soup.
'Hotwire (HTML over the wire) Turbo' was shipped by default in Rails 7, which was released in 2022. Turbo, along with a JS framework called Stimulus, attempts to solve the problem of getting Rails to act like a proper SPA. Turbo does this by intercepting all page-state changing requests within predefined sections of the DOM and feeding them into Turbo handlers on the controllers asynchronously (1).
Essentially, it allows you to do things like this:
new.html.erb
<%= turbo_frame_tag <your_model> do %>
<%= render "form", <your_model>: @<your_model> %>
<% end %>
create.turbo_stream.erb
<%= turbo_stream.prepend <index_view>, partial: <your_model_partial> %>
<your_model>_controller.rb
def create
@<your_model>.create(params)
respond_to do |format|
format.turbo_stream
end
end
Hopefully you get the idea, but if not, this tutorial is phenomenal (2).
Let's review quickly what Turbo is trying to accomplish before getting into how it fails:
Take a look at that last one. Now look at your 500+ line package.json file, your three different JavaScript package managers, your JavaScript transpilers, and your state management libraries. Wouldn't it be nice if you didn't have to write JavaScript anymore? Wouldn't it be easier to put everything in a special little DOM tag and take care of all that state business on the server side? The appeal is certainly there. The JavaScript ecosystem has been hard to wrangle for a criminally long time, and no one can be blamed for wanting to take it down a notch. However, here are some things to consider before you start embracing Turbo:
Turbo Does Not Eliminate the Need for JavaScript
Turbo is primarily concerned with removing the need to manage complex page-state across many client-side requests using javascript. For everything else it offers Stimulus, a small javascript framework focused on consolidating side effects from said requests, in addition to being a repository for any nifty gizmos that spice up the page but are unrelated to the client-server relationship.
There is nothing inherently unpleasant about working with Stimulus. It's only crime is that it presents a back door for javascript functionality to begin leaking back into the project. Turbo is very powerful, but at some point you will inevitably be faced with the decision to either implement a complex request flow using Turbo, which is time consuming, or use the raw access to the DOM and event handlers to do the same in much less time. As long as the temptation is there it's only a matter of time.
Turbo Wants a Completely RESTful App
Like many things in Rails, to feel its full potential, Turbo needs to be interacting with an application that's carefully designed to be RESTful. Have a lot of controllers that activate business logic without an associated resource of some kind? Turbo will not be kind to you. Of course, you can do it, but you'll be going against the grain. All of the nice conventions that allow you to avoid lengthy .turbo_stream files (basically JS handlers for CRUD actions) won't work.
The fact of the matter is that nearly everything in Rails is easier if your app is perfectly RESTful, but in the real world it can't always be done and Turbo is yet one more thing that will punish you for deviating even slightly.
You Are Locked into Strada for Mobile
Although I'll admit it's been a while, I have never had a good experience using a mobile hybrid app framework. I've heard they've come a long way, but the main reason I still avoid them is simply because they create a dependency on APIs that Apple has never seemed comfortable offering up for the purpose of avoiding native development. Apple invests great time and energy to make Xcode the realm of development for Apple products. Circumventing this, even though I'm sure it looks clean in many cases, runs the great risk of plunging you into a perennial battle with the platform you rely on to publish your mobile applications.
领英推荐
Strada is the hybrid app framework for Rails apps using Turbo and Stimulus. I haven't used Strada (I'm curious to hear what others' experiences have been), but I can't help but feel it will suffer from the same issues plaguing all hybrid frameworks as described above. Strada claims to be able to transpile your JavaScript code into native components (so <button> becomes Button in Swift), which in theory should make things easier and less "WebView-y". Nevertheless, I worry about what happens with all those mappings to native components every time Apple shakes things up with their API.
It Breaks MVC
The single most frustrating issue with Turbo is that it allows and encourages you to stream HTML from anywhere in your application. Remember this?
<%= turbo_stream.prepend <index_view>, partial: <your_model_partial> %>
<your_model>Controller.rb
def create
@<your_model>.create(params)
respond_to do |format|
format.turbo_stream
end
end
Well, turns out you can do that wherever you want:
broadcast_prepend_to(:channel, target:, html: "<some_partial>")
And guess what? You can find many examples where people are adding these to ActiveRecord callbacks. Say what you will about your Javascript woes, at least your backend code is isolated from the headache of DOM state management. Obviously there are ways in traditional Rails apps to make DOM updates from outside the Controllers (like websockets), but giving such a careless option to broadcast to the DOM from wherever you want is a recipe for disaster.
In Conclusion
I'm of the firm opinion that nearly any idea can work under the right circumstances. I'm sure Turbo is being used effectively this very moment by a great many applications in the wild. Nevertheless, the question is not whether a tool can work, it's will this tool set your team up for success? My current opinion on Turbo is no.
Footnotes
1.)
It is not so terribly different from the old pattern of using RJS in conjunction with remote: true. In fact, if you take a look at this ancient Railscast, you'll notice the RJS actions sound similar to Turbo's DOM manipulation options:
# Remove a Turbo Frame
turbo_stream.remove
# Insert a Turbo Frame at the beginning/end of a list
turbo_stream.append
turbo_stream.prepend
# Insert a Turbo Frame before/after another Turbo Frame
turbo_stream.before
turbo_stream.after
# Replace or update the content of a Turbo Frame
turbo_stream.update
turbo_stream.replace
2.)
It's actually the only way you're gonna learn Turbo in the context of Rails since the "Hotwire" suite has been released independently of Rails. In theory as long as your REST controllers respond to Turbo requests in a way the framework understands you could add it to anything. Rails of course comes with a gem that makes it easy.
3.)
I've never heard or had much issue with hybrid apps on the Android side, but if you're being forced out of a WebView-based solution by Apple, it's not going to help much that it may work for Android. You'll need something on the backend that can communicate using JSON so you might as well do Android and Apple with that in mind.