Rails is great. You can still check DHH's 15-minute blog demo and just appreciate the combination of features and ease of use of the framework. Even though there are even easier ways to make a blog nowadays (cough cough Gatsby), Rails is still a rock-solid choice for crafting digital products.
Rails and the modern web
There are pros and cons to each approach and we like to analyze those on a per-project basis. More and more we see great success on teams going with fully server-side approaches, like Basecamp for example. Github is another case of a mostly server-side Rails app that works great (most of the time).
Even with a fully server-side approach, you can create experiences that for end-users, will be indistinguishable from a fully client-side web app. The instant page transitions, components, and cool APIs to interact with the DOM. Let's see how.
Turbolinks and remote helpers
The big deal about SPAs is on its name. single page applications don't have page transitions, it's usually done in the client-side and it looks and feels like a native app. An issue with server-side approaches is that every page change (clicking links) triggers a server request and a subsequent full page load. But you can fix that with Turbolinks.
head and swaps the
body. Thus, changing a page without a full refresh.
How do you install it? Simple, you don't, Rails comes with it installed and set up by default! For non-Rails projects it's pretty simple, just check out their documentation
The only thing that is not handled by default in Rails and Turbolinks is when a controller action renders a new template. Turbolinks supports
redirect_to out of the box, but when a template is rendered using
render, it causes a full page refresh. This often happens after you submit a form. Even if you are using the
form_with helper, which triggers an AJAX request instead of the traditional form submission, you will still re-render the page.
Take for instance, this
create controller action:
By default, Turbolinks handles the case where the form is valid because we use
redirect_to. But when the form is invalid, we will have a full page refresh, because we are re-rendering with the
Solution? Just install this handy gem made by Jorge Manrubia. That's it. Right now, at the time of writing, Turbolinks version 5 doesn't support this, but the upcoming version 6 will handle this with no extra setup.
Right now, developers and designers on the web design and implement UI with a component-based approach. It's easier to organize and re-use code this way. It's also the way design systems are usually maintained. And now with design systems becoming more and more popular, components even feel like the way to make web applications.
The traditional SPA assumes this model, usually, and it's pretty easy to do it. However, on Rails, there is no notion of this, and you are on your own to implement your components. The most popular way is just to use CSS. Naming schemes like SuitCSS and BEM allow you to define reusable CSS classes for your project. But what happens when you also need markup or behavior?
view_component, made by Github. Using this library, you can define all of your components on
A component is a combination of a Ruby class, an ERB template, a CSS file, and a JS file. You can control server-side behavior on that Ruby class, define your markup on your ERB template and then control the frontend side of things with CSS and JS.
As an example, I'll show you the implementation of a flash alert component, that we can use to render Rails flash notices and errors.
First, we need to define the Ruby class that renders the component. In its basic form, it's just a regular constructor that you use to set instance variables, kinda like a controller.
render? method can be used to determine if we should render the markup of the component. In this case, we don't render anything at all if we don't have any flash messages.
Then we just define markup like we would for a regular Rails view. You can use any template engine you like (
slim for example). I prefer traditional ERB.
Some basic styling:
And that's all it takes to define a component. To use it, we just reference it like this:
You can do this for all sorts of UI elements and it allows you to encapsulate UI logic on these re-usable bits. Now, you probably noticed that we don't have any JS here. Will touch that in a bit, let's just talk about Stimulus first.
Now, our previous example was pretty nice, but we lack JS to make the flash component behave. When I first started working with Rails (back in 2014) the defacto way was to just use jQuery to make stuff all interactive and nice. And we all know how that ends up with large codebases.
Spaghetti code get it? Yes, it's a lame joke.
I'm not going super in-depth on Stimulus, their docs are pretty thorough and easy to pick up. Still, let's see how we can combine
view_component to make our flash component nicer. Currently, when a flash message is rendered, it stays there forever, so lets us make it go away after some time and also add a button to dismiss it.
Refer to Stimulus docs to set it up. Then refer to this part of the
view_component docs to integrate both libraries.
By default Stimulus just searches for controllers on the
Now, back to our flash component. Let's change our old markup a little bit. We need to add the button and the respective Stimulus related attributes.
We added the
data-controller attribute, to tie this markup with its respective Stimulus controller. Then we added a button with a
data-action attribute. We specify
click->flash-message#close, which means we are mapping the
click event with the
close function of our
flash-message Stimulus controller.
The naming of these files matters a lot, like most Rails stuff. The
flash_message_controller defines the
flash_message controller. If you want to use it on your markup, you must reference it that way, always. And yes you can use it in different places on your HTML. Warning, controllers on
app/components can have name collisions with components on
Now, the controller:
Stimulus controllers have a bunch of lifecycle related functions, you can check those on the docs, but here we are redefining
disconnect. We use those to set a timeout to close the flash message and also to clear that message if the controller is unloaded. Just a nice touch.
Then, we get to our
close function that we use on the markup. Every Stimulus controller has access to the respective DOM element where it's being used. That
data-controller attribute takes care of that. When we use
this.element we are reaching for that DOM piece, then we can just call
.remove to delete that from the DOM, making the flash message go away.
A fun alternative
Remember, pick technologies that make your team happy and productive! In our case, everyone loves React and everyone loves Rails, but it's refreshing to just go full Rails once in a while and break out of the norm. If you are interested in solving problems more on the server-side, keep tuned, as we are going to talk about StimulusReflex for Rails and also LiveView for Phoenix.
The modern web is great because you have a ton of choices on how to do things, let's cheers on that.