{"id":13879866,"url":"https://github.com/simplymadeapps/simple_scheduler","last_synced_at":"2025-07-16T16:30:26.691Z","repository":{"id":14056665,"uuid":"75750981","full_name":"simplymadeapps/simple_scheduler","owner":"simplymadeapps","description":"An enhancement for Heroku Scheduler + Sidekiq for scheduling jobs at specific times.","archived":false,"fork":false,"pushed_at":"2022-05-02T18:23:05.000Z","size":154,"stargazers_count":131,"open_issues_count":1,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-28T15:01:23.400Z","etag":null,"topics":["cron","heroku","scheduler","sidekiq"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simplymadeapps.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.MD","support":null}},"created_at":"2016-12-06T16:41:24.000Z","updated_at":"2025-01-21T03:50:11.000Z","dependencies_parsed_at":"2022-08-07T07:16:13.479Z","dependency_job_id":null,"html_url":"https://github.com/simplymadeapps/simple_scheduler","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/simplymadeapps/simple_scheduler","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymadeapps%2Fsimple_scheduler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymadeapps%2Fsimple_scheduler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymadeapps%2Fsimple_scheduler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymadeapps%2Fsimple_scheduler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplymadeapps","download_url":"https://codeload.github.com/simplymadeapps/simple_scheduler/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymadeapps%2Fsimple_scheduler/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265524594,"owners_count":23782007,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cron","heroku","scheduler","sidekiq"],"created_at":"2024-08-06T08:02:36.686Z","updated_at":"2025-07-16T16:30:26.255Z","avatar_url":"https://github.com/simplymadeapps.png","language":"Ruby","funding_links":[],"categories":["Ruby","Scheduling"],"sub_categories":[],"readme":"# Simple Scheduler\n\n[![tests](https://github.com/simplymadeapps/simple_scheduler/actions/workflows/tests.yml/badge.svg)](https://github.com/simplymadeapps/simple_scheduler/actions/workflows/tests.yml)\n[![Code Climate](https://codeclimate.com/github/simplymadeapps/simple_scheduler/badges/gpa.svg)](https://codeclimate.com/github/simplymadeapps/simple_scheduler)\n[![Test Coverage](https://codeclimate.com/github/simplymadeapps/simple_scheduler/badges/coverage.svg)](https://codeclimate.com/github/simplymadeapps/simple_scheduler/coverage)\n[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/simplymadeapps/simple_scheduler/)\n\nSimple Scheduler is a scheduling add-on that is designed to be used with\n[Sidekiq](http://sidekiq.org) and\n[Heroku Scheduler](https://elements.heroku.com/addons/scheduler). It\ngives you the ability to **schedule tasks at any interval** without adding\na clock process. Heroku Scheduler only allows you to schedule tasks every 10 minutes,\nevery hour, or every day.\n\n## Production Ready?\n\n**Yes.** We are using Simple Scheduler in production for scheduling tasks that run\nhourly, nightly, weekly, and every 10 minutes in [Simple In/Out](https://www.simpleinout.com).\n\n### Why did we need to create yet another job scheduler?\n\nEvery option we evaluated seems to have the same flaw: **If your server is down, your job won't run.**\n\n[Check out our intro blog post to learn more](http://www.simplymadeapps.com/blog/2017/01/simple-scheduler-schedule-recurring-tasks-to-run-at-any-interval/).\n\n## Requirements\n\nYou must be using:\n\n- Rails 5.0+\n- ActiveJob\n- [Sidekiq](http://sidekiq.org)\n- [Heroku Scheduler](https://elements.heroku.com/addons/scheduler)*\n\nBoth Active Job and Sidekiq::Worker classes can be queued by the scheduler.\n\n*\\* Not actually required, but you're on your own for scheduling the `rake simple_scheduler` task to run every 10 minutes.*\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"simple_scheduler\"\n```\n\nAnd then execute:\n\n```bash\n$ bundle\n```\n\n## Getting Started\n\n### Create the Configuration File\n\nCreate the file `config/simple_scheduler.yml` in your Rails project:\n\n```yml\n# Global configuration options. The `queue_ahead` and `tz` options can also be set on each task.\nqueue_ahead: 360 # Number of minutes to queue jobs into the future\nqueue_name: \"default\" # The Sidekiq queue name used by SimpleScheduler::FutureJob\ntz: \"America/Chicago\" # The application time zone will be used by default if not set\n\n# Runs once every 2 minutes starting at the top of the hour\nsimple_task:\n  class: \"SomeActiveJob\"\n  every: \"2.minutes\"\n  at: \"*:00\"\n\n# Runs once every day at 4:00 AM. The job will expire after 23 hours, which means the\n# job will not run if 23 hours passes (server downtime) before the job is actually run\novernight_task:\n  class: \"SomeSidekiqWorker\"\n  every: \"1.day\"\n  at: \"4:00\"\n  expires_after: \"23.hours\"\n\n# Runs once every half hour, starting on the 30 min mark\nhalf_hour_task:\n  class: \"HalfHourTask\"\n  every: \"30.minutes\"\n  at: \"*:30\"\n\n# Runs once every week on Saturdays at 12:00 AM\nweekly_task:\n  class: \"WeeklyJob\"\n  every: \"1.week\"\n  at: \"Sat 0:00\"\n  tz: \"America/New_York\"\n```\n\n### Set up Heroku Scheduler\n\nAdd the rake task to Heroku Scheduler and set it to run every 10 minutes:\n\n```\nrake simple_scheduler\n```\n\n![Heroku Scheduler](https://cloud.githubusercontent.com/assets/124570/21104523/6d733d1a-c04c-11e6-89af-590e7d234cdf.gif)\n\nIt may be useful to point to a specific configuration file in non-production environments.\nUse a custom configuration file by setting the `SIMPLE_SCHEDULER_CONFIG` environment variable.\n\n```\nSIMPLE_SCHEDULER_CONFIG=config/simple_scheduler.staging.yml\n```\n\n### Task Options\n\n#### :class\n\nThe class name of the ActiveJob or Sidekiq::Worker. Your job or\nworker class should accept the expected run time as a parameter\non the `perform` method.\n\n#### :every\n\nHow frequently the task should be performed as an ActiveSupport duration definition.\n\n```yml\n\"1.day\"\n\"5.days\"\n\"12.hours\"\n\"20.minutes\"\n\"1.week\"\n```\n\n#### :at\n\nThis is the starting point\\* for the `:every` duration. This must be set so the expected\nrun times in the future can be determined without duplicating jobs already in the queue.\n\nValid string formats/examples:\n\n```yml\n\"18:00\"\n\"3:30\"\n\"**:00\"\n\"*:30\"\n\"Sun 2:00\" # [Sun|Mon|Tue|Wed|Thu|Fri|Sat]\n```\n\n\\* If you specify the hour of the day the job should run, it will only run in that hour,\nso if you specify an `:every` duration of less than `1.day`, the job will only run\nwhen the run time hour matches the `:at` time's hour.\nSee [#17](https://github.com/simplymadeapps/simple_scheduler/issues/17) for more info.\n\n#### :expires_after (optional)\n\nIf your worker process is down for an extended period of time, you may not want certain jobs\nto execute when the server comes back online. The `:expires_after` value will be used\nto determine if it's too late to run the job at the actual run time.\n\nAll jobs are scheduled in the future using the `SimpleScheduler::FutureJob`. This\nwrapper job does the work of evaluating the current time and determining if the\nscheduled job should run. See [Handling Expired Jobs](#handling-expired-jobs).\n\nThe string should be in the form of an ActiveSupport duration.\n\n```yml\n\"59.minutes\"\n\"23.hours\"\n```\n\n#### :queue_ahead (optional)\n\nThe `:queue_ahead` is the number of minutes that the job should be scheduled into the future.\nThe default value is `360`, so Simple Scheduler will make sure you have scheduled jobs for\nthe next 6 hours. This allows for a major outage without losing track of jobs that were\nsupposed to run during that time.\n\n**There are always a minimum of 2 scheduled jobs for each scheduled task.** This ensures there\nis always one job in the queue that can be used to determine the next run time, even if one of\nthe two was executed during the 10 minute Heroku Scheduler wait time.\n\nThe `:queue_ahead` can be set as a global configuration option or for each individual task.\n\n#### :tz (optional)\n\nUse `:tz` to specify the time zone of your `:at` time. If no time zone is specified, the Time.zone\ndefault in your Rails app will be used when parsing the `:at` time. A list of all the available\ntimezone identifiers can be obtained using `TZInfo::Timezone.all_identifiers`.\n\nThe `:tz` can be set as a global configuration option or for each individual task.\n\n## Writing Your Jobs\n\nThere is no guarantee that the job will run at the exact time given in the\nconfiguration, so the time the job was scheduled to run will be passed to\nthe job. This allows you to handle situations where the current time doesn't\nmatch the time it was expected to run. The `scheduled_time` argument is optional.\n\n```ruby\nclass ExampleJob \u003c ActiveJob::Base\n  # @param scheduled_time [Integer] The epoch time for when the job was scheduled to be run\n  def perform(scheduled_time)\n    puts Time.at(scheduled_time)\n  end\nend\n```\n\n#### Determine if the Job Should Run\n\nSimple Scheduler doesn't support specifying multiple days of the week or a specific day of the month.\nYou can use the `scheduled_time` with a guard clause to ensure the job only runs on specific days.\n\nSet up the jobs to run every day, even though we don't actually want them to run every day:\n\n```yml\n# config/simple_scheduler.yml\npayroll:\n  class: \"PayrollJob\"\n  every: \"1.day\"\n  at: \"0:00\"\n\nweekday_reports:\n  class: \"WeekdayReportsJob\"\n  every: \"1.day\"\n  at: \"18:00\"\n```\n\nUse a guard clause in each of our jobs to exit the job if it shouldn't run that day:\n\n```ruby\n# A job that runs only on the 1st and 15th of the month.\nclass PayrollJob \u003c ActiveJob::Base\n  # @param scheduled_time [Integer] The epoch time for when the job was scheduled to be run\n  def perform(scheduled_time)\n    # Exit the job unless it's the 1st or 15th day of the month\n    day_of_the_month = Time.at(scheduled_time).day\n    return unless day_of_the_month == 1 || day_of_the_month == 15\n\n    # Perform the job...\n  end\nend\n\n# A job that runs only on weekdays, Monday - Friday.\nclass WeekdayReportsJob \u003c ActiveJob::Base\n  # @param scheduled_time [Integer] The epoch time for when the job was scheduled to be run\n  def perform(scheduled_time)\n    # Exit the job unless it's Monday (1), Tuesday (2), Wednesday (3), Thursday (4), or Friday (5)\n    day_of_the_week = Time.at(scheduled_time).wday\n    return unless (1..5).include?(day_of_the_week)\n\n    # Perform the job...\n  end\nend\n```\n\n## Handling Expired Jobs\n\nIf you assign the `:expires_after` option to your task, you may want to know if\na job wasn't run because it expires. Add this block to an initializer file:\n\n```ruby\n# config/initializers/simple_scheduler.rb\n\n# Block for handling an expired task from Simple Scheduler\n# @param exception [SimpleScheduler::FutureJob::Expired]\n# @see http://www.rubydoc.info/github/simplymadeapps/simple_scheduler/master/SimpleScheduler/FutureJob/Expired\nSimpleScheduler.expired_task do |exception|\n  ExceptionNotifier.notify_exception(\n    exception,\n    data: {\n      task: exception.task.name,\n      scheduled: exception.scheduled_time,\n      actual: exception.run_time\n    }\n  )\nend\n```\n\n## Making Changes to Configuration File\n\nAny changes made to the YAML configuration file will be picked up by Simple Scheduler\nthe next time `rake simple_scheduler` is run. Depending on the changes, you may need\nto reset the current job queue.\n\nReasons you may need to reset the job queue:\n\n- Renaming a job's class\n- Deleting a scheduled task\n- Changing the task's run time interval\n- Changing the day of the week the job is run\n- Changing the `queue_name` in the config file\n\nA rake task exists to delete all existing scheduled jobs and queue them back up from scratch:\n\n```\nrake simple_scheduler:reset\n```\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## License\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplymadeapps%2Fsimple_scheduler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplymadeapps%2Fsimple_scheduler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplymadeapps%2Fsimple_scheduler/lists"}