Hey there, today I'm writing about an interesting gem I used some days ago to render some slow pages. This gem has an interesting workflow. It renders a loading page that we've already defined while a background task makes the processes we need to display information for the user, e.g. heavy SQL queries, extended operations, or anything that makes the rendering of the pages running slow. After the background task finishes, the loading page is replaced by the page that we normally display. The author of the gem has an example app about how to use it. The gem also generates most of the configuration files that it needs to work, even when some files are required to be created manually. I'll show you what files are needed to get schwifty to work.
Get the gem from its own repo and follow the installation instructions there, or in the link below: https://github.com/danielwestendorf/get_schwifty
Installation
- Add to the gem file
gem "get_schwifty"
- Run in your bash the
bundle
command - Run the gem generator
rails generate get_schwifty:install
Now, the following files should have already been created, but just in case something goes wrong, I'll show you the default files content:
- config/initializers/get_schwifty.rb
# frozen_string_literal: true
# GetSchwifty configuration initializer.
# Use this file to configure GetSchwifty according to your needs.
GetSchwifty.configure do |config|
# Configure rerendering
#
# By default, job parameters are stored in the Rails cache and they’re not removed after rendering.
#If you're not expiring keys with a least recently used policy, your cache could get filled up with #values which will never be reaccessed.
# Allow rerendering
# This allows caching to the `get_schwifty` helper calls in views
# config.allow_rerender = true # Default
# Disable rerendering
# This disables rerendering, and the cacheability of `get_schwifty` helper calls. Subscriptions
# will be rejected after the first render of a cached `get_schwifty` call
# config.allow_rerender = false
end
- app/controllers/get_schwifty_controller.rb
# frozen_string_literal: true
# Used for rendering partials in schwifty background jobs
class GetSchwiftyController < ApplicationController
prepend_view_path Rails.root.join("app", "views", "cables").to_s
end
- app/channels/get_schwifty_channel.rb
# frozen_string_literal: true
# Channel for handling schwifty subscriptions
class GetSchwiftyChannel < ApplicationCable::Channel
include GetSchwifty::Channel
end
- app/jobs/get_schwifty_runner_job.rb
# frozen_string_literal: true
# Job for running schwifty cables in the ActiveJob
class GetSchwiftyRunnerJob < ApplicationJob
include GetSchwifty::Job
end
- app/cables/base_cable.rb
# frozen_string_literal: true
# Base cable class to inherit from when getting schwifty
class BaseCable < GetSchwifty::Cable::Base
include Rails.application.routes.url_helpers
# Access to pundit helper methods for authorization
# include Pundit
# Utility method shared across cables for accessing the current user
# def current_user
# identifiers[:user]
# end
end
- app/views/cables
This will be an empty folder where you should put the views you want to have rendered by the action cable after it finishes the tasks.
- app/assets/javascripts/channels/get_schwifty_channel.js
document.addEventListener("DOMContentLoaded", function() {
GetSchwifty(App).showMeWhatYouGot();
});
These are configuration files. ,, there is no problem you copying them from the demo app, now you need to create your first cable. This is the main point of the gem, but before going there, I want to give you a general view of how the gem works and its workflow.
Gem workflow.
As you can see in the image, these are the four steps we have:
The Rails controller
First, we create the action in our controller just as all the rails applications work, nothing new here.
Loading view
Then the controller loads a partial that we had already created as the loading page. This page will be shown to the users while get schwifty does all the heavy things in a background job, and the loading page will be replaced by the final page until schwifty finishes loading. In this loading page we can send data we need to use on the cable using the get_schwifty
view helper. Just be careful because all the data sent here can be visible to the user if he inspects the HTML tags, for example:
#This is the loading view, loaded by the controller
Calculating Fibonacci of, please wait...
You would see something like this in the explorer if someone inspects the element.
As you can see the params sent by the get_schwifty view helper are visible in the HTML's tag, this is how the gem saves and recovers values, so be careful with the data you send. Try to send information that is not relevant in case the user gets to see it. The data sent by this helper will be retrieved as standard ruby objects. For example during my first attempt, I tried to send a kaminari array which was retrieved as a simple array, so I lost all the methods that kaminari provides to those objects like total_pages
and current_page
methods, so keep an eye on those details.
The cable
Next step, be aware of where we are, the cable is a background job, this means that you have lost all the variables and values that you had in the controllers and the views. The requested object has been lost, so you should send the data needed from the loading view using the get_schwifty
view helper. These values are available here inside a hash called params. During this step we tell the cable what partial should replace the loading one, and send params if needed, we can do that with the stream helper, for example:
def fibonacci
n = params[:i] || SecureRandom.rand(20..40)
calculated = calculate_fibonacci(n)
stream partial: "dashboard/fibonacci", locals: { calculated: calculated, n: n }
end
Cable’s rendered view
This is the last step. At this point, the loading page has been replaced by the cable’s rendered view, and it should be the one requested.
How to create a new cable?
-
rails generate get_schwifty:cable YourCableName method_name
That command will create two files like the following:
rails generate get_schwifty:cable Test test:
- app/cables/test_cable.rb
class TestCable < BaseCable
def test
a = 1
b = 2
stream partial: "test/test", locals: { a: a, b: b }
end
end
- app/views/cables/test/_test.html.erb
<!-- Slow to render HTML goes here -->
How to use the recently created cable?
First, call the loading page from your application controller.
- app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def index render partial: 'index'
render partial: 'index'
end
end
- app/views/application/_index.html.erb
<%= get_schwifty "test_cable#test" do %>
Loading :D
<%- end %>
- app/cables/test_cable.rb
class TestCable < BaseCable
def test
# Your super awesome code goes here
#Please use a better name than test :D
stream partial: "test/test", locals: { foo: 'foo', bar: 'bar' }
end
end
- app/views/cables/test/_test.html.haml
Super awesome final page. I received the params Foo: <%= foo %> Bar: <%= bar %>
And that is it, now you should have a functional cable.
Super awesome final page. I received the params Foo: foo Bar: bar
I hope this post is helpful for you. Feel free to leave any comments or questions below.