angry_batch 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8363c90cc908a4ae3fa64957ebfc422580dd91041dfd32d0d2eb7607e9130a62
4
- data.tar.gz: b83a95d6938a4ad2e2cc20d3e7fc9edbd0f998e7c1256749d7c2ba6c90f22a04
3
+ metadata.gz: c1f0b7ce33e9b643c38801a188cb1a5734f30d652a8e3cd7cdd0b3b65f88340a
4
+ data.tar.gz: fbace43e4e2a6740786caa4e04117bbdf62b40191e6834e9d5eb1e5db3e37692
5
5
  SHA512:
6
- metadata.gz: 1ab00df9aef51980652bb56151d43ce16af63aa58a2e41f31badcb90f50b2687c9e74e119f6d77fa9f3113f6fb88aa441e2e0988275ef732942ab20f8f73f2e0
7
- data.tar.gz: bba22c7ff6de71cdd1bfe285aeaead5163140536a19e2142e1e163b57dcec50ec0262519b2148a7ebed2db30cc4274d16200c6d0e576372684bf3633568e150a
6
+ metadata.gz: 72de753a921085e998d060d0cffa1018b3914def85adff93c9b7adbb61009f9d0f7bb295060519b2bbcd0c72db041b03e334f6d6ff170d19a9daf4d062ffb691
7
+ data.tar.gz: adfa172d9219a8f86ba12838c15426a5a12b725dec24a815bccf93b5423afe550e3a2b062cb0f8ad1e3230c82199d62c08ee0ec713011536e98bb323daf3cbe1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## Version 1.1.0 - 2026-09-05
4
+
5
+ - New migration — run `rails generate angry_batch:install` and `rails db:migrate` when upgrading
6
+ - Removed the `scheduling` state and the `AngryBatch::Batch.scheduling` scope; new batches start as `pending`
7
+ - Jobs and handlers can access their batch via `batch`
8
+ - Add jobs to a running batch with `batch.enqueue`
9
+ - Attach `metadata:` to a batch, readable via `batch.metadata`
10
+ - Track progress with `progress`, `pending_jobs_count`, `completed_jobs_count` and `failed_jobs_count`
11
+ - `perform_later` returns the `AngryBatch::Batch` record
12
+ - Jobs are enqueued after the surrounding transaction commits (Rails 7.2+)
13
+ - Bug fixes
14
+
3
15
  ## Version 1.0.1 - 2026-05-11
4
16
 
5
17
  - Handle job failures via `after_discard` hook — marks job as `failed` and stores error message
data/README.md CHANGED
@@ -33,6 +33,17 @@ rails generate angry_batch:install
33
33
  rails db:migrate
34
34
  ```
35
35
 
36
+ ### Upgrading from 1.0
37
+
38
+ Version 1.1 adds columns to the batches table. Run the generator again; it detects the existing install and only creates the upgrade migration:
39
+
40
+ ```
41
+ rails generate angry_batch:install
42
+ rails db:migrate
43
+ ```
44
+
45
+ The migration backfills the new counters from the existing job records. Jobs that finish on 1.0 code after the migration has run are not counted, so run the migration while your workers are stopped or drained, and restart them on 1.1 before enqueuing new batches.
46
+
36
47
  ## Usage
37
48
 
38
49
  ```ruby
@@ -42,7 +53,7 @@ class SomeJob
42
53
  end
43
54
 
44
55
  # Step 2: Create new batch queue
45
- queue = AngryBatch.new(label: 'Debug label')
56
+ queue = AngryBatch.new(label: 'Debug label', metadata: { account: account })
46
57
 
47
58
  # Step 3: Add completion handler
48
59
  # `on_complete` job will be called when all other queue jobs have completed
@@ -58,9 +69,94 @@ queue.enqueue SomeJob, argument2
58
69
  queue.enqueue SomeJob, argument3
59
70
 
60
71
  # Step 5: Trigger all jobs in the queue
61
- in the queue.perform_later
72
+ # returns the AngryBatch::Batch record
73
+ batch = queue.perform_later
74
+ ```
75
+
76
+ ### Accessing the batch from a job
77
+
78
+ Every job that includes `AngryBatch::Batchable` can call `batch` while performing. It returns the `AngryBatch::Batch` record, or `nil` when the job was enqueued outside of a batch (or the batch record has already been cleaned up).
79
+
80
+ ```ruby
81
+ class SomeJob < ApplicationJob
82
+ include AngryBatch::Batchable
83
+
84
+ def perform(argument)
85
+ batch.label # => 'Debug label'
86
+ batch.metadata # => { account: #<Account> }
87
+ batch.progress # => 33
88
+ end
89
+ end
90
+ ```
91
+
92
+ Completion and failure handlers get the same access when they include `AngryBatch::Batchable`. Handlers are not part of the batch, so including the module in them does not affect the batch counters.
93
+
94
+ ```ruby
95
+ class ToBeCalledWhenAllOtherJobsAreCompletedJob < ApplicationJob
96
+ include AngryBatch::Batchable
97
+
98
+ def perform(argument)
99
+ batch.metadata[:account]
100
+ end
101
+ end
62
102
  ```
63
103
 
104
+ ### Metadata
105
+
106
+ `metadata:` accepts a hash and is stored using ActiveJob argument serialization. Symbols, dates, nested hashes, and ActiveRecord models (via GlobalID) round-trip as-is. Values ActiveJob cannot serialize raise `ActiveJob::SerializationError` when the queue is created.
107
+
108
+ Metadata is read-only after the batch is created. If a model referenced in metadata is deleted before a job reads it, `batch.metadata` raises `ActiveJob::DeserializationError`, the same way job arguments do.
109
+
110
+ ### Adding jobs from inside a job
111
+
112
+ A running job can add more jobs to its own batch. The batch does not complete until they finish, and jobs added this way can add jobs themselves.
113
+
114
+ ```ruby
115
+ class ExportProjectJob < ApplicationJob
116
+ include AngryBatch::Batchable
117
+
118
+ def perform(project)
119
+ project.files.find_each do |file|
120
+ batch.enqueue ExportFileJob, file
121
+ end
122
+ end
123
+ end
124
+ ```
125
+
126
+ `batch.enqueue` uses the same validation as the queue: the job must be an ActiveJob and include `AngryBatch::Batchable`. It can also be called on any pending `AngryBatch::Batch` record outside of a job.
127
+
128
+ Once a batch is completed or failed, `batch.enqueue` raises `AngryBatch::BatchFinishedError`. To run a second stage from a completion handler, create a new batch instead.
129
+
130
+ If a job raises after adding jobs, the added jobs still run. The batch ends `failed` once they are done, and the `on_failure` handlers run then. Note that a retried job (`retry_on`) runs `batch.enqueue` again and adds the jobs a second time.
131
+
132
+ If a job cannot be enqueued, whether a `before_enqueue` callback aborted it or your queue backend raised, the error is raised to the caller. A queue that cannot accept jobs is an infrastructure problem rather than a failed unit of work, so it belongs in your exception tracker and not in `on_failure`. The batch stays `pending` until you deal with it, and is reaped by `AngryBatch::CleanupCronJob` if you never do.
133
+
134
+ ### Transactions
135
+
136
+ Jobs are pushed to your queue after the surrounding database transaction commits, so a worker never sees a job before its batch record exists. If the transaction rolls back, the batch and its jobs are discarded together and nothing is pushed. This applies to `perform_later` and to `batch.enqueue` alike.
137
+
138
+ ```ruby
139
+ Account.transaction do
140
+ account.update! exporting: true
141
+
142
+ queue.perform_later # pushed once this transaction commits
143
+ end
144
+ ```
145
+
146
+ On Rails 7.1 there is no hook for this and jobs are pushed immediately, so avoid enqueuing inside your own transaction there.
147
+
148
+ ### Progress
149
+
150
+ Batches keep `jobs_count`, `completed_jobs_count`, and `failed_jobs_count` up to date as jobs finish. `pending_jobs_count` and `progress` (0 to 100) are derived from them.
151
+
152
+ The counters on a record are a snapshot. Call `batch.reload` to refresh them, for example after `batch.enqueue` on the same instance.
153
+
154
+ ### Errors
155
+
156
+ A job is marked `failed` when ActiveJob discards it: `discard_on`, exhausted `retry_on`, or an unhandled exception. The batch is `failed` as soon as one job failed and every job has finished. A job only transitions once; a later successful run of a job already marked `failed` does not change it.
157
+
158
+ Prefer `retry_on` over adapter-level retries. With adapter-level retries (for example Sidekiq's own retry), the first unhandled exception marks the job `failed` even if a later attempt succeeds.
159
+
64
160
  ### Cleaning completed jobs
65
161
 
66
162
  `AngryBatch` stores jobs in the database. You have to run `AngryBatch::CleanupCronJob` in a cron to clean the records.
@@ -4,15 +4,18 @@
4
4
  #
5
5
  # Table name: angry_batch_batches
6
6
  #
7
- # id :bigint(8) not null, primary key
8
- # complete_handlers :jsonb not null
9
- # failure_handlers :jsonb not null
10
- # finished_at :datetime
11
- # jobs_count :integer default(0), not null
12
- # label :string
13
- # state :string default("scheduling"), not null
14
- # created_at :datetime not null
15
- # updated_at :datetime not null
7
+ # id :bigint(8) not null, primary key
8
+ # complete_handlers :jsonb not null
9
+ # completed_jobs_count :integer default(0), not null
10
+ # failed_jobs_count :integer default(0), not null
11
+ # failure_handlers :jsonb not null
12
+ # finished_at :datetime
13
+ # jobs_count :integer default(0), not null
14
+ # label :string
15
+ # metadata :jsonb not null
16
+ # state :string default("pending"), not null
17
+ # created_at :datetime not null
18
+ # updated_at :datetime not null
16
19
  #
17
20
  # Indexes
18
21
  #
@@ -24,7 +27,6 @@ class AngryBatch::Batch < ActiveRecord::Base
24
27
  has_many :jobs, class_name: 'AngryBatch::Job', dependent: :delete_all
25
28
 
26
29
  enum :state, {
27
- scheduling: 'scheduling',
28
30
  pending: 'pending',
29
31
  completed: 'completed',
30
32
  failed: 'failed',
@@ -32,20 +34,58 @@ class AngryBatch::Batch < ActiveRecord::Base
32
34
 
33
35
  class << self
34
36
  def expired
35
- completed.where(updated_at: ...2.days.ago)
36
- .or(failed.where(updated_at: ...4.weeks.ago))
37
- .or(pending.where(updated_at: ...4.weeks.ago))
37
+ completed.where(updated_at: ...2.days.ago).or(failed.where(updated_at: ...4.weeks.ago)).or(pending.where(updated_at: ...4.weeks.ago))
38
38
  end
39
39
  end
40
40
 
41
+ def enqueue(job_class, *, **)
42
+ AngryBatch::Helper.assert_batchable(job_class)
43
+
44
+ job = job_class.new(*, **)
45
+
46
+ with_lock do
47
+ raise AngryBatch::BatchFinishedError, "Batch #{id} is #{state}" unless pending?
48
+
49
+ AngryBatch::Helper.add_job_to_batch(self, job)
50
+ end
51
+
52
+ AngryBatch::Helper.after_transaction do
53
+ raise ActiveJob::EnqueueError, ["#{job_class} was not enqueued", job.enqueue_error&.message].compact.join(': ') unless job.enqueue
54
+ end
55
+
56
+ job
57
+ end
58
+
59
+ def metadata
60
+ raw = read_attribute(:metadata)
61
+
62
+ unless defined?(@metadata) && @metadata_raw == raw
63
+ @metadata = ActiveJob::Arguments.deserialize([raw || {}]).first
64
+ @metadata_raw = raw
65
+ end
66
+
67
+ @metadata
68
+ end
69
+
70
+ def pending_jobs_count
71
+ jobs_count - completed_jobs_count - failed_jobs_count
72
+ end
73
+
74
+ def progress
75
+ return 0 if jobs_count.zero?
76
+
77
+ ((completed_jobs_count + failed_jobs_count) * 100 / jobs_count).clamp(0, 100)
78
+ end
79
+
41
80
  def check_status_of_jobs
42
81
  handlers_to_enqueue = with_lock do
43
82
  return unless pending?
44
- return unless jobs_count == jobs.finished.count
83
+ return if jobs_count.zero?
84
+ return unless pending_jobs_count <= 0
45
85
 
46
86
  self.finished_at = Time.current
47
87
 
48
- if jobs.failed.none?
88
+ if failed_jobs_count.zero?
49
89
  update! state: 'completed'
50
90
  complete_handlers
51
91
  else
@@ -60,8 +100,17 @@ class AngryBatch::Batch < ActiveRecord::Base
60
100
  private
61
101
 
62
102
  def enqueue_handlers(handlers)
103
+ error = nil
104
+
63
105
  handlers.each do |(job_class, job_arguments)|
64
- job_class.constantize.perform_later(*ActiveJob::Arguments.deserialize(job_arguments || []))
106
+ job = job_class.constantize.new(*ActiveJob::Arguments.deserialize(job_arguments || []))
107
+ job.angry_batch_id = id if job.is_a?(AngryBatch::Batchable)
108
+
109
+ raise ActiveJob::EnqueueError, "#{job_class} was not enqueued" unless job.enqueue
110
+ rescue StandardError => e
111
+ error ||= e
65
112
  end
113
+
114
+ raise error if error
66
115
  end
67
116
  end
@@ -10,4 +10,21 @@ module AngryBatch::Batchable
10
10
  AngryBatch::Handle.job_failed(job, exception)
11
11
  end
12
12
  end
13
+
14
+ attr_accessor :angry_batch_id
15
+
16
+ def serialize
17
+ super.merge('angry_batch_id' => angry_batch_id)
18
+ end
19
+
20
+ def deserialize(job_data)
21
+ super
22
+ self.angry_batch_id = job_data['angry_batch_id']
23
+ end
24
+
25
+ def batch
26
+ return @batch if defined?(@batch)
27
+
28
+ @batch = angry_batch_id && AngryBatch::Batch.find_by(id: angry_batch_id)
29
+ end
13
30
  end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class AngryBatch::Builder
4
- def initialize(label: nil)
4
+ def initialize(label: nil, metadata: {})
5
+ @metadata = ActiveJob::Arguments.serialize([metadata || {}]).first
5
6
  @batch = AngryBatch::Batch.new(
6
7
  label: label,
7
- state: 'scheduling',
8
+ metadata: @metadata,
8
9
  complete_handlers: [],
9
10
  failure_handlers: [],
10
11
  )
@@ -20,22 +21,24 @@ class AngryBatch::Builder
20
21
 
21
22
  def on_complete(job_class, *, **)
22
23
  raise AngryBatch::BatchArgumentError, 'Batch is already running' if performed?
23
- raise AngryBatch::BatchArgumentError, "#{job_class} must be a subclass of ActiveJob::Base" unless job_class.is_a?(Class) && job_class < ActiveJob::Base
24
+
25
+ AngryBatch::Helper.assert_job_class(job_class)
24
26
 
25
27
  @batch.complete_handlers << [job_class, job_class.new(*, **).serialize['arguments']]
26
28
  end
27
29
 
28
30
  def on_failure(job_class, *, **)
29
31
  raise AngryBatch::BatchArgumentError, 'Batch is already running' if performed?
30
- raise AngryBatch::BatchArgumentError, "#{job_class} must be a subclass of ActiveJob::Base" unless job_class.is_a?(Class) && job_class < ActiveJob::Base
32
+
33
+ AngryBatch::Helper.assert_job_class(job_class)
31
34
 
32
35
  @batch.failure_handlers << [job_class, job_class.new(*, **).serialize['arguments']]
33
36
  end
34
37
 
35
38
  def enqueue(job_class, *, **)
36
39
  raise AngryBatch::BatchArgumentError, 'Batch is already running' if performed?
37
- raise AngryBatch::BatchArgumentError, "#{job_class} must be a subclass of ActiveJob::Base" unless job_class.is_a?(Class) && job_class < ActiveJob::Base
38
- raise AngryBatch::BatchArgumentError, "#{job_class} must include AngryBatch::Batchable" unless job_class.included_modules.include?(AngryBatch::Batchable)
40
+
41
+ AngryBatch::Helper.assert_batchable(job_class)
39
42
 
40
43
  @jobs << job_class.new(*, **)
41
44
  end
@@ -47,25 +50,23 @@ class AngryBatch::Builder
47
50
  ActiveRecord::Base.transaction(requires_new: true) do
48
51
  @batch.save!
49
52
 
53
+ @jobs.each { |job| AngryBatch::Helper.add_job_to_batch(@batch, job) }
54
+ end
55
+
56
+ @performed = true
57
+
58
+ AngryBatch::Helper.after_transaction do
50
59
  @jobs.each do |job|
51
- @batch.jobs.create!(
52
- active_job_idx: job.job_id,
53
- active_job_class: job.class.name,
54
- active_job_arguments: job.serialize['arguments'],
55
- )
60
+ raise ActiveJob::EnqueueError, ["#{job.class.name} was not enqueued", job.enqueue_error&.message].compact.join(': ') unless job.enqueue
56
61
  end
57
-
58
- @batch.update!(state: 'pending')
59
62
  end
60
63
 
61
- @performed = true
62
- @jobs.each(&:enqueue)
63
- @batch.check_status_of_jobs
64
- rescue
64
+ @batch
65
+ rescue StandardError
65
66
  unless @performed
66
67
  @batch = AngryBatch::Batch.new(
67
68
  label: @batch.label,
68
- state: 'scheduling',
69
+ metadata: @metadata,
69
70
  complete_handlers: @batch.complete_handlers,
70
71
  failure_handlers: @batch.failure_handlers,
71
72
  )
@@ -4,30 +4,25 @@ module AngryBatch::Handle
4
4
  extend self
5
5
 
6
6
  def job_completed(job)
7
- record = AngryBatch::Job.find_by(active_job_idx: job.job_id)
8
-
9
- return if record.blank?
10
-
11
- record.with_lock do
12
- return if record.failed?
13
-
14
- record.update!(state: 'completed')
15
- end
16
-
17
- record.batch&.check_status_of_jobs
18
- rescue ActiveRecord::RecordNotFound
19
- nil
7
+ finish(job, state: 'completed')
20
8
  end
21
9
 
22
10
  def job_failed(job, exception = nil)
11
+ finish(job, state: 'failed', error_message: exception&.message)
12
+ end
13
+
14
+ private
15
+
16
+ def finish(job, state:, **attributes)
23
17
  record = AngryBatch::Job.find_by(active_job_idx: job.job_id)
24
18
 
25
19
  return if record.blank?
26
20
 
27
21
  record.with_lock do
28
- return if record.completed?
29
-
30
- record.update!(state: 'failed', error_message: exception&.message)
22
+ if record.pending?
23
+ record.update!(state: state, **attributes)
24
+ AngryBatch::Batch.increment_counter(:"#{state}_jobs_count", record.batch_id) # rubocop:disable Rails/SkipsModelValidations
25
+ end
31
26
  end
32
27
 
33
28
  record.batch&.check_status_of_jobs
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AngryBatch::Helper
4
+ extend self
5
+
6
+ def assert_job_class(job_class)
7
+ return if job_class.is_a?(Class) && job_class < ActiveJob::Base
8
+
9
+ raise AngryBatch::BatchArgumentError, "#{job_class} must be a subclass of ActiveJob::Base"
10
+ end
11
+
12
+ def assert_batchable(job_class)
13
+ assert_job_class(job_class)
14
+
15
+ return if job_class.included_modules.include?(AngryBatch::Batchable)
16
+
17
+ raise AngryBatch::BatchArgumentError, "#{job_class} must include AngryBatch::Batchable"
18
+ end
19
+
20
+ def add_job_to_batch(batch, job)
21
+ job.angry_batch_id = batch.id
22
+
23
+ batch.jobs.create!(
24
+ active_job_idx: job.job_id,
25
+ active_job_class: job.class.name,
26
+ active_job_arguments: job.serialize['arguments'],
27
+ )
28
+ end
29
+
30
+ # NOTE(rstankov): pushing after the commit keeps workers from seeing a job
31
+ # before its record. Rails < 7.2 has no hook for it and pushes immediately.
32
+ if ActiveRecord.respond_to?(:after_all_transactions_commit)
33
+ def after_transaction(&)
34
+ ActiveRecord.after_all_transactions_commit(&)
35
+ end
36
+ else
37
+ def after_transaction
38
+ yield
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AngryBatch
4
- VERSION = '1.0.1'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/angry_batch.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'angry_batch/version'
4
+ require_relative 'angry_batch/helper'
4
5
  require_relative 'angry_batch/job'
5
6
  require_relative 'angry_batch/batch'
6
7
  require_relative 'angry_batch/handle'
@@ -17,6 +18,9 @@ module AngryBatch
17
18
 
18
19
  class BatchArgumentError < ArgumentError
19
20
  end
21
+
22
+ class BatchFinishedError < StandardError
23
+ end
20
24
  end
21
25
 
22
26
  if defined?(Rails)
@@ -12,7 +12,16 @@ module AngryBatch
12
12
  end
13
13
 
14
14
  def copy_migrations
15
- migration_template 'create_angry_batch_tables.rb', 'db/migrate/create_angry_batch_tables.rb'
15
+ existing = self.class.migration_exists?(File.join(destination_root, 'db/migrate'), 'create_angry_batch_tables')
16
+
17
+ if existing.nil?
18
+ migration_template 'create_angry_batch_tables.rb', 'db/migrate/create_angry_batch_tables.rb'
19
+ return
20
+ end
21
+
22
+ return if File.read(existing).include?('completed_jobs_count')
23
+
24
+ migration_template 'add_metadata_and_counters_to_angry_batch_tables.rb', 'db/migrate/add_metadata_and_counters_to_angry_batch_tables.rb', skip: true
16
25
  end
17
26
  end
18
27
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMetadataAndCountersToAngryBatchTables < ActiveRecord::Migration[7.0]
4
+ def up
5
+ change_table :angry_batch_batches do |t|
6
+ begin
7
+ t.jsonb :metadata, null: false, default: {}
8
+ rescue NoMethodError
9
+ t.json :metadata, null: false, default: {}
10
+ end
11
+
12
+ t.integer :completed_jobs_count, null: false, default: 0
13
+ t.integer :failed_jobs_count, null: false, default: 0
14
+ end
15
+
16
+ change_column_default :angry_batch_batches, :state, from: 'scheduling', to: 'pending'
17
+
18
+ execute "UPDATE angry_batch_batches SET state = 'pending' WHERE state = 'scheduling'"
19
+
20
+ execute <<~SQL.squish
21
+ UPDATE angry_batch_batches SET
22
+ completed_jobs_count = (SELECT COUNT(*) FROM angry_batch_jobs WHERE angry_batch_jobs.batch_id = angry_batch_batches.id AND angry_batch_jobs.state = 'completed'),
23
+ failed_jobs_count = (SELECT COUNT(*) FROM angry_batch_jobs WHERE angry_batch_jobs.batch_id = angry_batch_batches.id AND angry_batch_jobs.state = 'failed')
24
+ SQL
25
+ end
26
+
27
+ def down
28
+ change_column_default :angry_batch_batches, :state, from: 'pending', to: 'scheduling'
29
+
30
+ remove_column :angry_batch_batches, :metadata
31
+ remove_column :angry_batch_batches, :completed_jobs_count
32
+ remove_column :angry_batch_batches, :failed_jobs_count
33
+ end
34
+ end
@@ -6,15 +6,19 @@ class CreateAngryBatchTables < ActiveRecord::Migration[7.0]
6
6
  begin
7
7
  t.jsonb :complete_handlers, null: false, default: []
8
8
  t.jsonb :failure_handlers, null: false, default: []
9
+ t.jsonb :metadata, null: false, default: {}
9
10
  rescue NoMethodError
10
11
  t.json :complete_handlers, null: false, default: []
11
12
  t.json :failure_handlers, null: false, default: []
13
+ t.json :metadata, null: false, default: {}
12
14
  end
13
15
 
14
16
  t.datetime :finished_at
15
17
  t.integer :jobs_count, null: false, default: 0
18
+ t.integer :completed_jobs_count, null: false, default: 0
19
+ t.integer :failed_jobs_count, null: false, default: 0
16
20
  t.string :label
17
- t.string :state, null: false, default: 'scheduling'
21
+ t.string :state, null: false, default: 'pending'
18
22
 
19
23
  t.timestamps
20
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: angry_batch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radoslav Stankov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-11 00:00:00.000000000 Z
11
+ date: 2026-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -255,9 +255,11 @@ files:
255
255
  - lib/angry_batch/builder.rb
256
256
  - lib/angry_batch/cleanup_cron_job.rb
257
257
  - lib/angry_batch/handle.rb
258
+ - lib/angry_batch/helper.rb
258
259
  - lib/angry_batch/job.rb
259
260
  - lib/angry_batch/version.rb
260
261
  - lib/generators/angry_batch/install_generator.rb
262
+ - lib/generators/angry_batch/templates/add_metadata_and_counters_to_angry_batch_tables.rb
261
263
  - lib/generators/angry_batch/templates/create_angry_batch_tables.rb
262
264
  homepage: https://github.com/RStankov/AngryBatch
263
265
  licenses: