Рассылка оповещений по расписанию при помощи sidekiq и cron
Не всегда удаётся сфокусировано учиться каждый день. Периодически фокус теряется аж на несколько дней и начинается этап активной прокрастинации -- к занятиям никак не получается вернуться. Начинается рационализация безделья -- нет времени, куча дела и т.д. В mkdev менторы пинают прокрастинаторов при любой возможности. Но, признаюсь честно, не всегда получается вовремя заметить, что тот или иной ученик отбился от рук.
Я очень люблю автоматизировать всё, что можно автоматизировать, поэтому в этой статьей мы автоматизируем пинки для дорогих учеников при помощи cron, гема whenever. Прежде чем продолжить чтение, убедитесь что вводная статья по whenever, написанная Евгением Бурмакиным для mkdev уже изучена и общее представление о том, что мы сейчас будем делать в пределах кода mkdev уже имеется.
Когда был активен пользователь?
Итак, нам нужно отправлять оповещения тем ученикам, которые не появлялись на mkdev больше двух дней. В Devise, используемый нами гем для аутенфикации, уже встроено отслеживание даты последнего логина, которая хранится в колонке last_sign_in_at
. Тем не менее, есть вероятность что ученик просто напросто не выходил с сайта с тех пор, поэтому основываться на этой дате будет не разумно.
Придётся отслеживать самим. Для этого добавим поле last_active_at
, которое будем обновлять каждый раз, когда ученик открывает страницу, связанную с заданиями. Решается такая "задача" элементарно -- добавлением before_action
в UserTasksController
:
def update_last_active
current_user.touch :last_active_at
end
Настраиваем whenever
Теперь нам необходимо добавить новую cron задачу: раз в день брать всех учеников, которые неактивны больше двух дней и отправлять им письмо. Следуя инструкциям из нашей статьи про whenever, добавляем гем в Gemfile и выполняем в папке с проектом wheneverize .
. В свежесозданном файле config/schedule.rb
пропишем следующее правило:
every 1.day do
runner "User.notify_inactive"
end
Рассылаем оповещения
Теперь осталось лишь написать метод notify_inactive
в модели User
. Код простейший:
# app/models/user.rb
# ...
def self.notify_inactive
customers.where("last_active_at < ?", 2.days.ago).each do |user|
NotificationsMailer.delay.inactive_notice(user)
end
end
# ...
Конечно, необходимо теперь добавить NotificationsMailer
, реализовать в нём метод inactive_notice
, который и будет рассылать оповещения. Обратите внимание на метод delay
: мы используем sidekiq для рассылки почты на фоне, который предоставляет этот удобный метод.
Не буду приводить здесь код Mailer'a -- там нет ничего особенного (честно). Лучше расскажу ещё, как деплоить этот новый код.
Деплоим
Внимание! Текст дальше будет сложен для понимания, если у вас нет представления о том, что такое chef. Не волнуйтесь. Это очень advanced случай, который, тем не менее, нельзя было совсем опустить в данной статье.
whenever
очень удобно деплоить при помощи capistrano. Но мы не используем capistrano в mkdev, мы используем chef. Это значит, что нам необходимо обновить наш кукбук для деплоя приложения. Я сделал это добавлением в блок before_restart
нужной команды, теперь он выглядит так:
# ...
before_restart do
execute "cd #{release_path} && bundle exec whenever --update-crontab '#{node['app']['name']}_production'"
end
# ...
before_restart
это часть ресурса deploy, включённого в Chef по-умолчанию и деплоящего приложения по той же схеме что и capistrano.
Чему мы научились
Теперь вы знаете, насколько просто создавать периодические задачи в Rails при помощи whenever. Пожалуй, делать это даже слишком просто и в отдельной статье, возможно, не было необходимости. Тем не менее, я хотел показать пример использования в конкретном приложении, а так же показать все задействованные в процессе технологии.