Toggling view layouts with Kredis, Turbo Frames, and Rails07 Mar 2022
Kredis 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.
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
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.
app/views/players/_player.html.erb like this:
Just regular ERB and some Tailwind classes for styling. Now update
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
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.
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.
app/controllers/players_controller.rb and update the
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.
params[:view] is present in the request, we update the
preferences hash to store the value of
update here updates the
preferences key in Redis along with updating the
user_preferences instance variable.
@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.
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?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.
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
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
And then update
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.
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
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:
if statement now checks
current_user.preferences instead of
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.
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!