Cron Jobs and the Use of Rufus Scheduler Gem
Tags: webdev, ruby, cron, tutorial
Published: 2023-04-28 20:15:53


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](https://github.com/william-luck/blue-rate-tracker), 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](https://github.com/jmettraux/rufus-scheduler). 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
```

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
```

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](https://crontab.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

```

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

```

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
```
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
```



My full code for updating the prices of the products in my database looks like this: 
![code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjo34gzgo5pz4nuyd8qu.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yspgchrhls3ekcq263o2.png)