https://github.com/blocknotes/active_job_store
Store jobs' state and custom data on DB
https://github.com/blocknotes/active_job_store
Last synced: 7 months ago
JSON representation
Store jobs' state and custom data on DB
- Host: GitHub
- URL: https://github.com/blocknotes/active_job_store
- Owner: blocknotes
- License: mit
- Created: 2022-11-09T21:46:51.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2023-10-21T07:05:38.000Z (over 2 years ago)
- Last Synced: 2025-06-24T22:13:12.500Z (7 months ago)
- Language: Ruby
- Homepage:
- Size: 95.7 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE.txt
Awesome Lists containing this project
README
# ActiveJob Store
[](https://badge.fury.io/rb/active_job_store)
[](https://github.com/blocknotes/active_job_store/actions/workflows/specs_61.yml)
[](https://github.com/blocknotes/active_job_store/actions/workflows/specs_70.yml)
[](https://github.com/blocknotes/active_job_store/actions/workflows/specs_71.yml)
[](https://github.com/blocknotes/active_job_store/actions/workflows/linters.yml)
Persist job execution information on a support model `ActiveJobStore::Record`.
It can be useful to:
- store the job's state / set progress value / add custom data to the jobs;
- query historical data about job executions / extract job's statistical data;
- improve jobs' instrumentation / logging capabilities.
By default gem's internal errors are sent to stderr without compromising the job's execution.
Please ⭐ if you like it.
## Installation
- Add to your Gemfile `gem 'active_job_store'` (and execute: `bundle`)
- Install the gem's migrations: `bundle exec rails active_job_store:install:migrations`
- Apply the migrations: `bundle exec rails db:migrate`
- Add to your job `include ActiveJobStore` (or to your `ApplicationJob` class if you prefer)
- Access to the job executions data using the class method `job_executions` on your job (ex. `YourJob.job_executions`)
## API
attr_accessor on the jobs:
- `active_job_store_custom_data`: to set / manipulate job's custom data
Instance methods on the jobs:
- `active_job_store_format_result(result) => result2`: to format / manipulate / serialize the job result
- `active_job_store_internal_error(context, exception)`: handler for internal errors
- `active_job_store_record => store record`: returns the store's record
- `save_job_custom_data(custom_data = nil)`: to persist custom data while the job is performing
Class methods on the jobs:
- `job_executions => relation`: query the list of job executions for the specific job class (returns an ActiveRecord Relation)
## Usage examples
```rb
SomeJob.perform_now(123)
SomeJob.perform_later(456)
SomeJob.set(wait: 1.minute).perform_later(789)
SomeJob.job_executions.first
# => #"default", "priority"=>nil, "executions"=>1, "exception_executions"=>{}, "timezone"=>"UTC"},
# result: "some_result",
# exception: nil,
# enqueued_at: nil,
# started_at: Wed, 09 Nov 2022 21:09:50.611355000 UTC +00:00,
# completed_at: Wed, 09 Nov 2022 21:09:50.622797000 UTC +00:00,
# created_at: Wed, 09 Nov 2022 21:09:50.611900000 UTC +00:00>
```
Query jobs in a specific range of time:
```rb
SomeJob.job_executions.where(started_at: 16.minutes.ago...).pluck(:job_id, :result, :started_at)
# => [["02beb3d6-a4eb-442c-8d78-29103ab894dc", "some_result", Wed, 09 Nov 2022 21:20:57.576018000 UTC +00:00],
# ["267e087e-cfa7-4c88-8d3b-9d40f912733f", "some_result", Wed, 09 Nov 2022 21:13:18.011484000 UTC +00:00]]
```
Some statistics on completed jobs:
```rb
SomeJob.job_executions.completed.map { |job| { id: job.id, execution_time: job.completed_at - job.started_at, started_at: job.started_at } }
# => [{:id=>6, :execution_time=>1.005239, :started_at=>Wed, 09 Nov 2022 21:20:57.576018000 UTC +00:00},
# {:id=>4, :execution_time=>1.004485, :started_at=>Wed, 09 Nov 2022 21:13:18.011484000 UTC +00:00},
# {:id=>1, :execution_time=>0.011442, :started_at=>Wed, 09 Nov 2022 21:09:50.611355000 UTC +00:00}]
```
Extract some logs:
```rb
puts ::ActiveJobStore::Record.order(id: :desc).pluck(:created_at, :job_class, :arguments, :state, :completed_at).map { _1.join(', ') }
# 2022-11-09 21:20:57 UTC, SomeJob, 123, completed, 2022-11-09 21:20:58 UTC
# 2022-11-09 21:18:26 UTC, AnotherJob, another test 2, completed, 2022-11-09 21:18:26 UTC
# 2022-11-09 21:13:18 UTC, SomeJob, Some test 3, completed, 2022-11-09 21:13:19 UTC
# 2022-11-09 21:12:18 UTC, SomeJob, Some test 2, error,
# 2022-11-09 21:10:13 UTC, AnotherJob, another test, completed, 2022-11-09 21:10:13 UTC
# 2022-11-09 21:09:50 UTC, SomeJob, Some test, completed, 2022-11-09 21:09:50 UTC
```
Query information from a job (even while performing):
```rb
job = SomeJob.perform_later 123
job.active_job_store_record.slice(:job_id, :job_class, :arguments)
# => {"job_id"=>"b009f7c7-a264-4fb5-a1f8-68a8141f323b", "job_class"=>"SomeJob", "arguments"=>[123]}
job = AnotherJob.perform_later 456
job.active_job_store_record.custom_data
# => {"progress"=>0.5}
### After a while:
job.active_job_store_record.reload.custom_data
# => {"progress"=>1.0}
```
## Setup examples
Store some custom data during the perform (ex. a progress value):
```rb
class AnotherJob < ApplicationJob
include ActiveJobStore
def perform
# do something...
save_job_custom_data(progress: 0.5)
# do something else...
save_job_custom_data(progress: 1.0)
'some_result'
end
end
# Usage example:
AnotherJob.perform_later(456)
AnotherJob.job_executions.last.custom_data['progress'] # 1.0 (after the job's execution)
```
Prepare the custom data but it gets stored only at the end of the job's execution:
```rb
class AnotherJob < ApplicationJob
include ActiveJobStore
def perform(some_id)
self.active_job_store_custom_data = []
active_job_store_custom_data << { time: Time.current, message: 'SomeJob step 1' }
sleep 1
active_job_store_custom_data << { time: Time.current, message: 'SomeJob step 2' }
'some_result'
end
end
# Usage example:
AnotherJob.perform_now(123)
AnotherJob.job_executions.last.custom_data
# => [{"time"=>"2022-11-09T21:20:57.580Z", "message"=>"SomeJob step 1"}, {"time"=>"2022-11-09T21:20:58.581Z", "message"=>"SomeJob step 2"}]
```
Process the job's result before storing it (ex. for serialization):
```rb
class AnotherJob < ApplicationJob
include ActiveJobStore
def perform(some_id)
21
end
def active_job_store_format_result(result)
result * 2
end
end
# Usage example:
AnotherJob.perform_now(123)
AnotherJob.job_executions.last.result
# => 42
```
To raise an exception also when there is a gem's internal error:
```rb
class AnotherJob < ApplicationJob
include ActiveJobStore
# ...
def active_job_store_internal_error(context, exception)
# Handle the exception (for example using services like Sentry/Honeybadger) and / or raise it again:
raise exception
end
end
```
## Do you like it? Star it!
If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
Or consider offering me a coffee, it's a small thing but it is greatly appreciated: [about me](https://www.blocknot.es/about-me).
## Contributors
- [Mattia Roccoberton](https://blocknot.es/): author
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).