{"id":17472456,"url":"https://github.com/sleeplessbyte/resumable_job","last_synced_at":"2026-03-06T06:31:44.732Z","repository":{"id":32532045,"uuid":"136024752","full_name":"SleeplessByte/resumable_job","owner":"SleeplessByte","description":":arrow_right_hook: Make any ActiveJob resumable.","archived":false,"fork":false,"pushed_at":"2024-08-21T04:23:49.000Z","size":29,"stargazers_count":0,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-18T19:46:16.522Z","etag":null,"topics":["activejob","rails"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/resumable_job","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/SleeplessByte.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-04T12:53:27.000Z","updated_at":"2021-12-22T20:00:36.000Z","dependencies_parsed_at":"2024-11-11T06:18:21.661Z","dependency_job_id":"79c63b52-23b9-4e88-8fd5-345a12f4a119","html_url":"https://github.com/SleeplessByte/resumable_job","commit_stats":{"total_commits":39,"total_committers":2,"mean_commits":19.5,"dds":0.3076923076923077,"last_synced_commit":"c9a9a0b5a690ea0205b6f44c0ed09d0978475c0c"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SleeplessByte%2Fresumable_job","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SleeplessByte%2Fresumable_job/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SleeplessByte%2Fresumable_job/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SleeplessByte%2Fresumable_job/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SleeplessByte","download_url":"https://codeload.github.com/SleeplessByte/resumable_job/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233824409,"owners_count":18736001,"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":["activejob","rails"],"created_at":"2024-10-18T17:17:53.948Z","updated_at":"2025-09-22T04:31:38.604Z","avatar_url":"https://github.com/SleeplessByte.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ResumableJob\n\n[![Build Status: master](https://travis-ci.com/SleeplessByte/resumable_job.svg)](https://travis-ci.com/SleeplessByte/resumable_job) \n[![Gem Version](https://badge.fury.io/rb/resumable_job.svg)](https://badge.fury.io/rb/resumable_job)\n[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)\n[![Maintainability](https://api.codeclimate.com/v1/badges/0e24799041c87852d8c0/maintainability)](https://codeclimate.com/repos/5b15471aef14fd7333000014/maintainability)\n\nMake any `ActiveJob` resumable.\n\nUse exception flow to make jobs exceptionally resumable, whilst retaining other state, with automatic exponential\nbackoff handling. `ActiveJob` is *not* a dependency, so this could be used with \"anything\". Adds a `module` to `include`\nsomewhere that adds a method which yields a block. During this block, you can throw a `ResumableJob::ResumeLater` to \ncall the following:\n\n```Ruby\nself.class\n    .set(wait_until: resume_at || ResumableJob::Backoff.to_time(attempt))\n    .perform_later(pause(state).merge(attempt: attempt + 1))\n````\n\nState is passed through `pause` and when `pause` is not overridden will be all the arguments you passed to your job plus\nan `attempt` argument that is steadily increased in order to to exponential backoff.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'resumable_job'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install resumable_job\n\n## Usage\n\n### Make a job resumable\n\nSimple example to implement pagination that resumes later if you receive a \"Rate Limit Exceeded\".\n\n```Ruby\nclass FetchDataJob \u003c ApplicationJob\n  include ResumableJob::Resumable\n  \n  def perform(state)\n    page = state.fetch(:page) { 1 }\n    \n    resumable(state) do\n      loop do\n        result = DataFetcher.call(page: page)\n        raise ResumableJob::ResumeLater(state: state.merge(page: page)) if result.status == 429\n        break unless result.next_page?\n          \n        page = result.next_page\n      end\n    end\n  end\nend\n```\n\n### Turn inner exception into resumable\n\nWhen the exception has more information (for example a \"rate limit reset\" value), it can be turned into a resume later.\nAdditionally, the `state` of the resume later exception will me merged into the original state, and then into the pause\nstate.\n \n\n```Ruby\nclass FetchDataJob \u003c ApplicationJob\n  include ResumableJob::Resumable\n\n  def perform(state)\n    resumable(state) do\n      fetch_data(state)\n    end\n  end\n\n  private\n\n  def fetch_data(state)\n    RateLimitableFetcher.call(state)\n  rescue RateLimitableFetcher::RateLimited =\u003e ex\n    raise ResumableJob::ResumeLater.new(state: state, utc: ex.retry_at, message: ex.message)\n  end\nend\n```\n\n### Filter out keys from the state\nSome state is not serializable. You may be calling your job with `perform_now`, but when it resumes later, some\narguments can not be serialized. Use the `pause` override to include state not originally present, modify state that is\nnot passed by your exception (ResumeLater exception state), or remove state. \n\n```Ruby\nclass FetchDataJob \u003c ApplicationJob\n  include ResumableJob::Resumable\n  \n  def pause(state)\n    state.slice(:attempt, :page, :token)\n  end\nend\n```\n  \n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can\nalso run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the\nversion number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,\npush git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/SleeplessByte/resumable_job.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsleeplessbyte%2Fresumable_job","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsleeplessbyte%2Fresumable_job","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsleeplessbyte%2Fresumable_job/lists"}