Painlessly managing long running processes in your views using Ruby on Rails
This problem has been explored by other developers (such as Tomasz Gryszkiewicz in his work on Drab for Elixir) and the general consensus seems to be to use WebSockets or SSEs to easily and reliably relay information to the frontend from the backend in real time.
While Ruby on Rails does provide a framework for WebSockets called Action Cable, it does not provide a convenient way to make frontend modifications once the state of a process changes (which would require to write a new collection of frontend and backend code). Additionally, some work would also need to go into creating and managing individual channels for each user in order to relay the information back to them. Overall, this process is lengthy and needs to be standardized.
Let’s delve into how to simulate and manage long running processes in fie.
You probably already know about commanders, which are used by fie in the same way controllers are used by Ruby on Rails. However, since long running processes are handled by the Active Jobs framework which is external to fie’s commanders, fie has another feature called
Manipulator which injects your classes with the ability to manipulate the state of your user’s views in the same way as the commander.
Manipulator within your classes, you provide them with access to basic fie commander methods which include
state for viewing and changing the shared state,
commander_exists? to verify whether the commander/client whose state you wish to manipulate still exists.
Your first step to recreating the example displayed in the GIF above is to define your instance variables (or your overall state) within your controller which should look like this:
As you can see, two instance variables are created and will be used for the following purpose:
@long_job_progresskeeps track of the progress of our simulated long running job, which is 0 out of 100 at the time of initialization.
@is_long_job_running, verifies whether the long running job is currently running or not. If we do not keep track of whether the job is already running or not, we risk having the same job run multiple times concurrently, especially if the view does not prevent the user from triggering it multiple times for one reason or another.
Once your state is initialized in your controller, the next step is to create your view which will display the progress of your long running job to the user:
The view may display one of two things depending on whether the job is already running or not (which is represented in the
@is_long_job_running instance variable).
- If the job is already running, the user will see a progress bar equal to the current value of the
@long_job_progressinstance variable and a maximum value of 100. Therefore as the value of
@long_job_progresschanges, so will the progress bar.
- If the job has finished or not yet started, the user will simply see a button that will prompt them to start the long running job. If the user clicks on the button, the commander method
start_long_jobwill be triggered.
As the button is currently triggering a method named
start_long_job in our commander and it does not yet exist, we will create it. It should look like this:
To begin with, if the job is currently running, the method simply exits (through an empty return). Otherwise, the value of
@long_job_progressis reset to 0 and the long job is set as running through modifying the value of
@is_long_job_running to true. The actual job is then set to run asynchronously through its
perform_later method. We also pass the identifier of our commander
@connection_uuid to the job so that the commander can manipulate the state of our commander/view.
Let’s now create the part we have been waiting for, the actual JOB (in the app/jobs folder):
It is first important to note that we include the
Manipulator module through
include Fie::Manipulator in order to have access to the methods necessary to modify the state from our job.
Once we include
Fie::Manipulator , the next step is to define the perform method of our job which accepts as an argument the identifier
connection_uuidof our commander/view. In order for the methods included from
Fie::Manipulator to work, we need to define the instance variable
@fie_connection_uuid as equal to the
connection_uuid of the commander/view whose state we would like to manipulate. After doing so, we can create a lambda literal named
is_job_finished which will tell us whether the progress of the job is over 100 or not.
Based on whether the job has finished running or not (meaning the client disconnected or the job completed at a progress of ≥ 100), the job will complete one of two actions.
- If the client is still connected (verified through the
commander_exists?method) and the job is still running (verified through calling
is_job_running), the value of
@long_process_progressis increased by 10, and after a waiting period of 1 second, the job is then run again. The user will see it as their progress bar being more complete.
- Otherwise, the value of
@is_long_job_runningis set to false which will notify all other parts of the application that the job has finished running. The user will see this as their progress bar converting into a button again.
You can try this example at https://fie.eranpeer.co/showcase#long-running-job.