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
Add the whenever gem to your Gemfile with:
gem 'whenever', require: false
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.
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
# 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
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
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
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.