Creating a monthly updating calendar in Rails with Whenever gem and a Cron job
smilesforgood
Posted on August 2, 2021
For my final portfolio project with the Flatiron School, I updated an app I'd previously created with Sinatra into React and also added additional functionality. This app allows a user to track plant care in their home, particularly watering needs for their plants. The user saw a calendar (created with React Calendar) that rendered a watering schedule on the front end. Calendar data was maintained on the back end with a PlantEvent
model and database. To limit the amount of data that the database would be responsible for at any given time, the plant_events
table would be updated on the first of the month with the current month's schedule. Thus I needed to implement a recurring method to delete the past month's data and instantiate new database rows for the current month.
I followed this detailed tutorial for implementing my recurring job. The steps below show how I set a Cron job to run a Rake task once a month with the whenever
gem.
Step 1. Add whenever
gem and set up the config/schedule.rb
file
Add the whenever gem to your Gemfile with:
gem 'whenever', require: false
Then run bundle
in the terminal.
After bundling, run bundle exec wheneverize .
from the root of your Rails application. This creates a file called config/schedule.rb
where you can write the schedule.
Step 2. Create the whenever
schedule for your cron task in config/schedule.rb
The whenever
gem has several built-in shorthand calls for commonly used times. To run a task on the first of the month at midnight, you can simply use the built-in 1.month
shortcut:
# config/schedule.rb
every 1.month do
rake 'calendar:update_calendar'
end
This will call a Rake task called update_calendar
that is namespaced under calendar
.
Step 3. Create a Rake task that will perform the needed task when called
You can use a Rails generator to generate the Rake task file with:
rails g task calendar update_calendar
This creates a file called lib/tasks/calendar.rake
with the following boilerplate code:
# lib/tasks/calendar.rake
namespace :calendar do
desc "TODO"
task update_calendar: :environment do
end
end
Edit the desc
line to describe what your task does and add the code to perform the task inside the block. Be sure to keep the environment
line as that is what loads your environment (including ActiveRecord models) inside your task. The file now looks like:
namespace :calendar do
desc "delete previous month's calendar events and populate current month"
task update_calendar: :environment do
PlantEvent.delete_all
Plant.find_each do |plant|
if plant.watering_repeat_rate_days
plant.build_plant_events_collection(plant.watering_repeat_rate_days)
end
end
end
end
In this case, all existing plant_events
are deleted. Then each plant
instantiates a collection of plant_events
for the current month.
For reference, the Plant
model looks like:
# app/models/plant.rb
require 'date'
require 'active_support'
class Plant < ApplicationRecord
belongs_to :user
has_many :plant_events, dependent: :destroy
def build_plant_events_collection(watering_repeat_rate_days, start_date = Date.today)
event_date = start_date
if watering_repeat_rate_days
while event_date < start_date.at_beginning_of_month.next_month
self.plant_events.create(date: event_date, event_type: "water")
event_date = event_date.advance(days: watering_repeat_rate_days)
end
end
end
end
Step 4. Write the crontab file for your jobs
Finally, write the crontab file that will check for cron jobs and run them in the background. You can read more about cron here and here. Basically cron lets you run background jobs at specified intervals, and the crontab tracks these jobs by name.
For production environments (the default) run:
bundle exec whenever --update-crontab
When working in development, add the development flag --set environment='development'
and run:
bundle exec whenever --update-crontab --set environment='development'
Now the cron job is all set to run the rake task once a month to update the calendar.
Posted on August 2, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.