Toggling view layouts with Kredis, Turbo Frames, and Rails
07 Mar 2022Kredis is a new gem that makes it easier to work with Redis keys in Ruby on Rails. Kredis was added a suggested gem for new Rails applications starting with the release of Rails 7.0 in December of 2021 and is likely to become a larger force in the Rails world in the coming years.
From the documentation, Kredis “encapsulates higher-level types and data structures around a single key, so you can interact with them as coherent objects rather than isolated procedural commands”.
What this means for us is that we can use Kredis to make it easier to use Redis as a data store in our application. With Kredis, it is simple to use Redis to read and write complex data structures. Kredis’ integration with ActiveRecord allows us to work with Redis data alongside existing models.
Today we will use Kredis to power a card/list view toggle for a resource’s index page, persisting the user’s view preference across requests. This tutorial will offer a gentle introduction into how Kredis works while exploring how Kredis can help us implement a common real-world UX pattern.
We will also add a bit of Turbo functionality in the form of a Turbo Frame to wrap the list of items to make applying the user’s view preference a bit more efficient.
When we are finished, our application will work like this:
Before we begin, this tutorial is best suited for folks with experience building simple applications with Ruby on Rails. If you have never used Rails before, this tutorial will be difficult to follow. You do not need any prior experience with Turbo or Kredis to follow along with this tutorial.
As usual, you can find the complete code for this application on Github.
Let’s start building.
Application setup
To begin, create a new Rails 7 application from your terminal and scaffold up a Players
resource, which we will use as the base for our Kredis-powered view toggle.
Note the inclusion of the css=tailwind
option in the rails new
command. We will use Tailwind to style our application so we can stay focused on building with Kredis instead of copy/pasting CSS.
Once the application is created and the Players resource is scaffolded, you can start up the server and build Tailwind’s CSS with bin/dev
.
Because we are building a view toggle for the Players index page, you may also want to seed the database with a few players to save you some manual typing. Head to db/seeds.rb
and update it:
Then seed the database from your terminal:
Build the list and card views
Before introducing Kredis to the application, we will begin by building a list/card view toggle that allows users to swap between the two views by reading URL parameters directly in the view.
Since we used the Rails scaffold generator, we already have a simple list view for Players ready on the index page. Let’s start by updating the generated views to be a little easier to process.
Update app/views/players/_player.html.erb
like this:
Just regular ERB and some Tailwind classes for styling. Now update app/views/players/index.html.erb
:
Again, just ERB and Tailwind, nothing fancy here.
Next we will add the card view, starting with a _card
partial. Create the new partial from your terminal:
And then fill in the new _card
partial:
With the card partial created, we can now add a simple toggle to the Players index page to switch between the list view and the card view.
Head to app/views/players/index.html.erb
and update it:
Here we add a messy but functional if
statement that checks the value of params[:view]
. When the view param equals card, we render the players as a grid of cards, otherwise the players are rendered as a list. Both of the view also have a link to toggle the index page to the opposite view, with link_to players_path(view: "list"/"card")
.
This “works” as a starting point. Head to http://localhost:3000/players and click the button to toggle between the views and the page layout changes. However, we can quickly see the limits of this params
based approach:
- Toggle the index page to from a list to cards
- Click on the view link for any player
- Click the “Back to players” link on the show page
Instead of the card layout, the Players index page switches back to the list view. What is happening here? The Players index view relies on the presence of a view
URL parameter to determine which layout to display. Because params do not persist between requests, the layout of the Players index page will always fall back to the default list view when visiting /players
without any URL parameters.
This is where Kredis
comes in. Instead of using the url params to set the view of the Players index page, we can use Kredis to persist the user’s view preference between requests so that the index page retains the expected layout.
Let’s see how Kredis works.
Using Kredis to store preferences
Kredis is a suggested gem in Rails 7, which means it is included in the default Gemfile but it is commented out. To install Kredis, first uncomment it in the Gemfile:
Then from your terminal:
Restart your Rails application after installing the Kredis gem and running the install task to avoid errors about Kredis being undefined.
At this point, you will also need a Redis server running in your development environment. If you do not already have Redis installed and running, Mac users will find this gist helpful. Linux users may find this guide helpful.
With Kredis installed and Redis running in our local environment, we can now use Kredis to store the user’s view preference instead of relying on the presence of URL parameters.
Head to app/controllers/players_controller.rb
and update the index
action:
Here we are using Kredis to fetch a preferences
key from Redis, initializing it as a hash and setting the user_preferences
instance variable to be a new instance of a Kredis Hash.
Then, when params[:view]
is present in the request, we update the preferences
hash to store the value of view
. update
here updates the preferences
key in Redis along with updating the user_preferences
instance variable.
Because @user_preferences
is an instance of a Kredis Hash, we can treat it (mostly) like a regular hash, as demonstrated by a little bit of experimenting in the console:
To use our new persistent Kredis value instead of URL parameters when rendering the index page, head back to app/views/players/index.html.erb
and replace the existing if
block with the below:
Here we updated the if
condition to check @user_preferences
instead of params
to determine which layout to render.
With that change in place, head back to http://localhost:3000/players and toggle between the list and card views. If all has gone well, toggling should still work. Even better, if you click on a player and then click to return back to the players index page, your list view preference will be retained.
Neat!
Adding a Turbo Frame
Now that we do not need the URL parameter on every request, it also makes sense to think about what actually needs to be updated when the user toggles between the list and card views. The rest of the page will not change — only the list of players will.
Turbo Frames give us the tools to adjust the application to only update the players section of the page when the user toggles the view, making the application feel faster and more responsive to user input.
For our application, Turbo Frames also resolve a subtle problem that you might have encountered while testing Kredis.
Turbo Drive takes a snapshot of every page to speed up applications. In almost all circumstances, this caching is something we just get for free without needing to think about; however, in this case, caching our players index page creates some issues that we need to resolve.
The issue works like this:
- Head to http://localhost:3000/players
- Toggle the view from list to card
- Click on the view link for a player
- Click on the link to go back to the players index page
- Notice that for a brief moment, the index page renders the list view before replacing it with the card view
This flash of content happens because Turbo Drive caches the index page before navigating from /players
to /players?view=card
. The next time you visit /players
, Drive uses the original, list-view version of /players
from the cache to “preview” the index page before updating it with the new, card-view version of the index page rendered from the server.
Turbo Frames resolve this issue because Turbo does not cache a page when navigation is scoped within a Turbo Frame. Turbo Drive’s snapshot caching only occurs when a full-page navigation occurs.
This means that with Turbo Frames users are free to toggle between list and card views on the players index page as often as they like — the content of the index page will not be cached until they navigate away from the index page entirely.
Because the content is not cached until the final navigation, the correct layout of the page gets cached and the “preview” displayed by Turbo on the next visit to the index page matches the final version, eliminating the flash of bad cached content.
Let’s see how this works in practice.
First, update app/views/players/index.html.erb
to wrap the list of players in a Turbo Frame:
Here we removed the “players” div and replaced that div with a <turbo-frame id="players"
, using the turbo-rails provided turbo_frame_tag
helper method.
Now that the list of players is wrapped in a Turbo Frame, links inside of that frame will update the content of that frame instead of updating the entire page.
This means that when the user clicks on the view toggle links, Turbo will extract the content of the players
Turbo Frame from the response and use that content to update the players
frame while discarding the rest of the content. When a Turbo Frame request is initiated, Rails helpfully renders the response without a layout, saving a (very small amount of) work on the server.
Finish up adding Turbo Frame support by updating the links to view players in both the card
and player
partials like this:
The addition of data-turbo-frame="_top"
to links wrapped inside of a <turbo-frame>
tells Turbo to perform a normal full-page navigation, instead of scoping the navigation within the frame. This ensures that going to players#show
still works as expected.
After making these changes, head back to http://localhost:3000/players, toggle the view from list to card (or card to list) and then click to view a player, and finally click back out to the players index page. If all has gone well you will not see any flashing cached content.
Improving our Kredis usage
Our current implementation of Kredis will only work if our application has exactly one user. This is because we are using a static key, preferences
, to set the players list layout.
In the real world, we will (hopefully!) have more than one user in our application. Each of our users should be able to store their own view preferences or our view toggle will not be a very useful feature! Let’s wrap up this tutorial by looking at how we can use Kredis in ActiveRecord models to store user-specific preferences, making view toggling much more useful.
From your terminal, create a User
model:
And then update app/controllers/application_controller.rb
:
We do not want to build out a whole user authentication system to learn more about Kredis, so here we are faking it by using session.id
to find or create a new user in the database and then defining current_user
as a helper_method
available throughout our application.
In a real application, current_user
would be a real, actual, logged in user, but our fake users will be good enough to demonstrate the key concept here.
Our goal is to be able to associate user preferences with a User
using Kredis. Our first implementation of Kredis hardcoded a preferences
key to store all preferences. Our new implementation will give each User
their own unique key through Kredis’ integration with ActiveRecord.
Head to app/models/user.rb
and update it like this:
This update means that all users will have a unique key/value pair that stores their preferences
in Redis. We can access this attribute with user.preferences
, which will return an instance of a Kredis::Hash
, just like Kredis.hash('preferences')
did in our controller.
To use this new preferences
attribute, update the PlayersController#index
action in app/controllers/players_controller.rb
:
Here we use the same update
method we used before, this time using current_user.preferences
to access a unique hash for the current user.
Then update the players index view to reference the right hash:
The if
statement now checks current_user.preferences
instead of @user_preferences
.
To test out these changes, open two separate browsers, toggle the list/card view to different options in each window, and then refresh each window to see that each “user” has their own unique preferences.
With that last change, you have reached the end of this tutorial! Incredible work today.
Wrapping up
Today we learned a little about how to use Kredis to unlock more power from our Redis-enabled Ruby on Rails applications.
Kredis’ tight integration with ActiveRecord gives it a leg up on other tools that are often used to do this type of work — if you have used Rails for a while, you have probably stuffed view preferences, filter and search terms, and other information into the session to persist the information across page turns. Redis is a more resilient and more appropriate tool for storing this type of information, and Kredis makes it simple to use Redis for this type of work in Ruby on Rails applications.
While we used a hash for storing data, Kredis supports a variety of datatypes to suit your needs — explore the repo to see the full list of available types.
One of the more exciting aspects of Kredis is the tools that can be built on top of it — we can build our own simple functionality like the preference storage we created today, but other smart folks are building gems on top of Kredis to enable functionality like presence tracking for application resources or complex, multi-stage forms.
That’s all for today. As always, thanks for reading!