DEV Community

deliaconstantino
deliaconstantino

Posted on • Updated on

Creating a monthly updating calendar in Rails with Whenever gem and a Cron job

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.

Calendar Component

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

When working in development, add the development flag --set environment='development' and run:

bundle exec whenever --update-crontab --set environment='development'
Enter fullscreen mode Exit fullscreen mode

Now the cron job is all set to run the rake task once a month to update the calendar.

Discussion (0)