Background Cron Jobs in Rails Using Sidekiq, Redis, and Whenever gems.

Muwonge Nicholus
4 min readApr 5, 2021

--

Earlier this week, I was tasked to add a feature that sent out notifications when a certain task didn’t check in from a third-party API. I had to come up with a background job that did this heavy lifting in intervals to keep the users informed of certain failures in certain transactions. This is how I solved this challenge and I hope my approach helps you implement your own solution.

NB: This tutorial is based on a fictional scenario that you could easily learn from and implement in your own project. This tutorial could be useful for scenarios like sending out scheduled marketing emails, app administration, etc.

For this, I needed to work with the Whenever gem for running the scheduled checks/jobs in the background and Sidekiq for executing the jobs(also visualizing the queued jobs ) at the same time in form of threads and Redis memory database to store this data in the memory as the app ran.

So let’s dive into it already.

Add the whenever gem to your Gemfile.

gem 'whenever', require: false

then run bundle install to install the gem.

Next we Wheneverize the project by running bundle exec wheneverize . . Read more about whenever gem options from https://github.com/javan/whenever

A schedule.rb the file will be generated in the project Config folder. In this file, you can add your scheduled task runners. In this example, we shall be working with rake tasks. Read more about rake tasks here https://github.com/ruby/rake.

config/schedule.rb

// set where you want to see the logs for jobs at.
set :output, { error: '/log/error.log', standard: '/log/cron.log'}
every 2.hours do
rake 'shopping_cart_notifier:pending_checkout_clearance'
end

then at this point, let’s go ahead and create the rake tasks because what we have up there is just a placeholder. To be honest you could do this first and add it to the schedule.rb later.

lib/tasks/shopping_cart_notifier.rake

require 'rake'
namespace :shopping_cart_notifier do
desc 'Sends notification about items in the cart that haven't been paid for and checked out.'
task pending_checkout_clearance: :environment do
NotifyPendingCheckoutJob.perform_later
end
end

Let's go ahead and create the actual function that does the job NotifyPendingCheckoutJob . Now this will require Sidekiq and Redis installed.

Add these gems to your Gemfile and run bundle install.

gem ‘sidekiqgem ‘redis’, ‘~> 4.0’gem ‘letter_opener’ // you may need this to preview emails locally in case its ehat you're using emails as the form of notification.

then go ahead and place this line in the Config/application.rb to set up Sidekiq to work within the application. Add at the end right before the end keyword.

config.active_job.queue_adapter = :sidekiq

Go to the initializers and configure Sidekiq to use Redis. config/initializers/sidekiq.rb

if Rails.env.production?
redis_host = '127.0.0.1'
redis_port = 6379
redis_db = 1
else
redis_host = 'localhost'
redis_port = 6379
redis_db = 0
end

Sidekiq.configure_server do |config|
config.redis = { url: "redis://#{redis_host}:#{redis_port}/#{redis_db}" }
end

Sidekiq.configure_client do |config|
config.redis = { url: "redis://#{redis_host}:#{redis_port}/#{redis_db}" }
end

Then let’s go to the routes and set up a route to view the Sidekiq user interface by adding these two lines.

require 'sidekiq/web'
authenticate :[user(for example the ordinary user)] do
mount Sidekiq::Web => '/sidekiq'
end

Then lastly, let's configure our app to use the letter opener for testing to check if the letters were sent through. Add these two lines to the config/environments/development.rb file.

config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true

Then lets create a job file in app/jobs/notify_pending_checkout_job.rb

class NotifyPendingCheckoutJob < ApplicationJob
sidekiq_options retry: 5
queue_as :default

def perform(*_args)
$pending_checkouts = ShoppingCart.where(status: :not_checked_out).where(['created_at < ?', 2.hours.ago]).all
AdminMailer.notify_admin_pending_checkouts($pending_checkouts).deliver! if $pending_checkouts.any? // this is for sending out email notifications
end
end

Let’s create the mailer functions now. This can be done in app/mailers/admin_mailer.rb

class AdminMailer<ApplicationMailer
default from: '[some email]@[some_domain].com'
layout 'mailer'

def notify_admin_pending_checkouts(pending_checkout)
@pending_checkout=pending_checkout
mail(
to: '[some admin mail]@domain.com',
subject: "#{@pending_checkout.count} cart items have not been checked out for the past two hours."
)
end
end

Let’s create an html.erb template to display this data to the admin in the tables. Better we do in a table. Create it in app/views/admin_mailer/notify_admin_pending_checkouts.html.erb.

<h1>These users need to be reminded to checkout their cart items. </h1>

// add your display logic here
<table style="width:100%">
<tr style="text-align:center; ">
<th>User</th>
<th>cart Id</th>
</tr>
<%= $pending_checkouts.each do |item|%>
<tr>
do something
</tr>
<% end %>
</table>

To view Sidekiq in the browser, you have to ensure that Redis is installed and running by running redis_server in the terminal and then accessing yourapp.com/sidekiq.. To do this in your app template on a nav link or button, you can also use the Sidekiq path since it's already added to the routes.

<%= link_to sidekiq_web_path,  class: "[your class]", target: "_blank" do%>
Sidekiq
<% end %>

Looks like we’re done with this. So, let’s test it out locally. Go ahead and create a seeder to create mock data. Alternatively, go ahead and open up the rails console and achieve the same result.

Go to the rake task and change the perform_later to perform_now to be able to see the task in action when you run the rake task in the terminal for instance.

rake 'shopping_cart_notifier:pending_checkout_clearance'

Please remember to revert it after testing. Also, test out the scenario for delivering later by reducing the schedule times in the schedule.rb.

I think that will be all for this post. I will look into writing about using Sidekiq in production managed by Systemd and Redis in my next post.

Read more about Sidekiq: https://github.com/mperham/sidekiq

Thank you for reading.

--

--

Muwonge Nicholus
Muwonge Nicholus

Written by Muwonge Nicholus

I am a Javascript and Ruby engineer. I love designing user interfaces and currently working in payment systems.

Responses (1)