DEV Community

william-luck
william-luck

Posted on

Cron Jobs and the Use of Rufus Scheduler Gem

I recently completed a project for my final phase of Flatiron school. It's a software to keep track of the Blue Dollar Rate in Argentina, automatically update prices of food items included on a menu, and recommend pricing.

The GitHub repo can be found here, and the project is also live at https://blue-rate-tracker.onrender.com/

This project depends on the use of a scheduler, and I used the Rufus Scheduler Gem. Schedulers were completely new to me when I first started this project, but the gem helped me to understand cron jobs and how they can be used in my applications.

To update the prices of my menu items, I needed code that could make a call to an API every morning before the business day begins, and update all prices accordingly. I know the code necessary to update the price data, but I was missing code to actually run in the background and update automatically as a background process.

After installing the gem, I added a scheduler file in under initializers in 'config/initializers/scheduler.rb'. Initializers in Rails help to configure your application, and are ran after the framework and gems are loaded. It is crucial to include the scheduling processes in the initializers because I want to the scheduler to run and be set in place before the actual application starts running, since I want recurrent changes everyday by default.

In the newly created file, I first require the gem using require 'rufus-scheduler' and then initiate a Rufus scheduler instance:

require 'rufus-scheduler'

scheduler = Rufus::Scheduler.new
Enter fullscreen mode Exit fullscreen mode

Next I need to write a cron job to execute everyday at a certain time. For the purposes of this application, I want to update the prices everyday at 6:00 AM Argentina Standard Time, which equates to 9:00 AM UTC:

scheduler.cron '00 09 * * *' do


end
Enter fullscreen mode Exit fullscreen mode

Put simply, cron jobs are used for scheduling certain tasks at a specified date (or time, a in this case). In my futile attempts to follow the installation and setup instructions to get this working, I did not fully understand the cron format in its elusive series of stars, blanks, zeros, and numbers.

I used Cron Guru to help me understand the format, where I played with the format until I had a more solid understanding.

To summarize, the format accepts five digits, each used to specify a different time.

  • The first digit accepts a minute, from 0-59.
  • The second digit accepts an hour, from 0-23.
  • The third digits accepts a day of the month, from 1-31
  • The fourth digit accepts a month, from 1-12
  • The last digit works a little differently, accepting a day of the week, from 0-6. 0 refers to Sunday, while 6 refers to Saturday.

Asterisks / stars are important to tell the job to completely disregard any specifications to that day, week, or month. But in my case above, I need to specify "00 09 * * ', because if otherwise, putting " 09 * * *" will result in the cron job executing every minute during the 9:00 AM hour (9:00 - 9:59 AM).

Other schedules accepted by the Rufus Scheduler include in and at jobs, which only trigger once, during a specified time. For example, after initiating a Rufus scheduler instance:

scheduler.in '12h' do

end

// or

scheduler.at '2023/04/29 16:00:00' do

end

Enter fullscreen mode Exit fullscreen mode

In each case, the task only executes once, in 12 hours from the time of initialization, and on April 29 at 4:00 PM, respectively.

More useful jobs concerning my application are every and interval, as well as chron. These tasks trigger repeatedly.

scheduler.every '12' do

end

// Or

scheduler.interval '12' do

end

Enter fullscreen mode Exit fullscreen mode

The difference between every and interval is that interval will immediately execute the task, then repeat those tasks at the specified interval. Every omits the initial trigger, unless specified using first_in and first_at.

For example, using first_in, I could have something as follows:

scheduler.every '12h`, first_in '12h' do

end
Enter fullscreen mode Exit fullscreen mode

While the initial trigger is still omitted from every, I can customize a time to call the first execution.

I can also use first_in for the purposes of my application:

scheduler.cron '00 09 * * *', first_in: '5d'  do


end
Enter fullscreen mode Exit fullscreen mode

My full code for updating the prices of the products in my database looks like this:
code

Notice that the cron job accepts an argument |job|. While I do not have any expanded use of this argument in my application, it comes with useful methods that I can use in conditional logic if there are considerable changes to the blue rate. For example, I can use job.unschedule, 'job.pause', and job.resume if there are changes above 10% a day, for example. This is not too common, but there have been recent jumps such as these. This could be useful to temporarily pause the cron tasks until the price stabilizes, as the rate is likely to fall back down after considerable hikes.

Other useful methods that I could integrate into the application are original, which returns the cron format of when the updates are scheduled; paused?, which returns a boolean telling me if the scheduler is active; and count, which returns the number of times the database has updated since starting. This could be useful to then pause the scheduler for a manual review of any changes to the prices of raw items every quarter, if the prices in markets have either not caught up with or exceeded the blue rate.

examples

Top comments (0)