acidic_job 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82e7abf5e57a4a6b68691e3c6c69ee8a6bbc2eabcedb69a3ef29e91ac0e99e8c
4
- data.tar.gz: b73584561b8a558529b2e057e449896dedfff2c21ab859dacf82b070f75c7910
3
+ metadata.gz: 943a9bc87b07b1e4a2ec7d0a83ffa8a23917124386cfc601780f95d36c62d23a
4
+ data.tar.gz: 2f534e9ebd0d533ae4b7572fb1da1451df9a1a2ed68fc231bc4f4de323de9e84
5
5
  SHA512:
6
- metadata.gz: 6e42251e87faff4790ecfba572eca0dd70251fc58a4293b906560b526efa4bc14db00e01ee0e90c43a0d3f3dc355b36b8db3a4be5129529c071711f3a0b2506f
7
- data.tar.gz: 167aa9f07dd033a5a3646edcff171a8dffea485d9cbb943c230526214c6d4eaa68761418321cc4e7795a0326361699181bd142377516ceadaa430d286628aea5
6
+ metadata.gz: 59f4ecc2e773d72bbef2d386a223e9859ee6f14c657e706c593e7a3f4c994c86aae885ef0b2993cbb916a1380daf3231882255685b6ffa360ffc4bf5dee591d8
7
+ data.tar.gz: 2a8d540fabb8528a9c305b9d7cf7fe12588c14760557f08f4c380dfb6b45fdafec1232767234809101d6aa0e8f44f8ac895b120664dec8e533d180900ad06c6f
data/Gemfile CHANGED
@@ -26,3 +26,5 @@ gem "database_cleaner"
26
26
  gem "simplecov"
27
27
 
28
28
  gem "pry"
29
+
30
+ gem "sidekiq"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acidic_job (0.4.0)
4
+ acidic_job (0.5.0)
5
5
  activerecord (>= 4.0.0)
6
6
  activesupport
7
7
 
@@ -39,6 +39,7 @@ GEM
39
39
  builder (3.2.4)
40
40
  coderay (1.1.3)
41
41
  concurrent-ruby (1.1.9)
42
+ connection_pool (2.2.5)
42
43
  crass (1.0.6)
43
44
  database_cleaner (2.0.1)
44
45
  database_cleaner-active_record (~> 2.0.0)
@@ -86,6 +87,7 @@ GEM
86
87
  thor (~> 1.0)
87
88
  rainbow (3.0.0)
88
89
  rake (13.0.4)
90
+ redis (4.4.0)
89
91
  regexp_parser (2.1.1)
90
92
  rexml (3.2.5)
91
93
  rubocop (1.18.3)
@@ -104,6 +106,10 @@ GEM
104
106
  rubocop-rake (0.6.0)
105
107
  rubocop (~> 1.0)
106
108
  ruby-progressbar (1.11.0)
109
+ sidekiq (6.2.2)
110
+ connection_pool (>= 2.2.2)
111
+ rack (~> 2.0)
112
+ redis (>= 4.2.0)
107
113
  simplecov (0.21.2)
108
114
  docile (~> 1.1)
109
115
  simplecov-html (~> 0.11)
@@ -133,6 +139,7 @@ DEPENDENCIES
133
139
  rubocop (~> 1.7)
134
140
  rubocop-minitest
135
141
  rubocop-rake
142
+ sidekiq
136
143
  simplecov
137
144
  sqlite3
138
145
 
data/blog_post.md ADDED
@@ -0,0 +1,28 @@
1
+ # ACIDic Operations in Rails
2
+
3
+ At the conceptual heart of basically any software are "operations"—the discrete actions the software performs. At the horizon of basically any software is the goal to make that sofware _robust_. Typically, one makes a software system robust by making each of its operations robust. Moreover, typically, robustness in software is considered as the software being "ACIDic"—atomic, consistent, isolated, durable.
4
+
5
+ In a loosely collected series of articles, Brandur Leach lays out the core techniques and principles required to make an HTTP API properly ACIDic:
6
+
7
+ 1. https://brandur.org/acid
8
+ 2. https://brandur.org/http-transactions
9
+ 3. https://brandur.org/job-drain
10
+ 4. https://brandur.org/idempotency-keys
11
+
12
+ With these techniques and principles in mind, our challenge is bring them into the world of a standard Rails application. This will require us to conceptually map the concepts of an HTTP request, an API server action, and an HTTP response into the world of a running Rails process.
13
+
14
+ We can begin to make this mapping by observing that an API server action is a specific instantiation of the general concept of an "operation". Like all operations, it has a "trigger" (the HTTP request) and a "response" (the HTTP response). So, what we need is a foundation upon which to build our Rails "operations".
15
+
16
+ In order to help us find that tool, let us consider the necessary characteristics we need. We need something that we can easily trigger from other Ruby code throughout our Rails application (controller actions, model methods, model callbacks, etc.). It should also be able to be run both synchronously (blocking execution and then returning its response to the caller) and asychronously (non-blocking and the caller doesn't know its response). It should then also be able to retry a specific operation (in much the way that an API consumer can "retry an operation" by hitting the same endpoint with the same request).
17
+
18
+ As we lay out these characteristics, I imagine your mind is going where mine went—`ActiveJob` gives us a solid foundation upon which we can build "ACIDic" operations.
19
+
20
+ So, our challenge to build tooling which will allow us to make "operational" jobs _robust_.
21
+
22
+ What we need primarily is to be able to make our jobs *idempotent*, and one of the simplest yet still most powerful tools for making an operation idempotent is the idempotency key. As laid out in the article linked above, an idempotency key is a record that we store in our database to uniquely identify a particular execution of an operation and a related "recovery point" for where we are in the process of that operation.
23
+
24
+
25
+
26
+
27
+
28
+
@@ -0,0 +1,15 @@
1
+ module AcidicJob
2
+ class Error < StandardError; end
3
+
4
+ class MismatchedIdempotencyKeyAndJobArguments < Error; end
5
+
6
+ class LockedIdempotencyKey < Error; end
7
+
8
+ class UnknownRecoveryPoint < Error; end
9
+
10
+ class UnknownAtomicPhaseType < Error; end
11
+
12
+ class SerializedTransactionConflict < Error; end
13
+
14
+ class UnknownJobAdapter < Error; end
15
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record"
4
+
3
5
  module AcidicJob
4
6
  class Key < ActiveRecord::Base
5
7
  RECOVERY_POINT_FINISHED = "FINISHED"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module AcidicJob
6
+ module PerformTransactionallyExtension
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def perform_transactionally(*args)
11
+ attributes = if self < ActiveJob::Base
12
+ {
13
+ adapter: "activejob",
14
+ job_name: self.name,
15
+ job_args: job_or_instantiate(*args).serialize
16
+ }
17
+ elsif self.include? Sidekiq::Worker
18
+ {
19
+ adapter: "sidekiq",
20
+ job_name: self.name,
21
+ job_args: args
22
+ }
23
+ else
24
+ raise UnknownJobAdapter
25
+ end
26
+
27
+ AcidicJob::Staged.create!(attributes)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ module PerformWrapper
5
+ def perform(*args, **kwargs)
6
+ # store arguments passed into `perform` so that we can later persist
7
+ # them to `AcidicJob::Key#job_args` for both ActiveJob and Sidekiq::Worker
8
+ @arguments_for_perform = if args.any? && kwargs.any?
9
+ args + [kwargs]
10
+ elsif args.any? && kwargs.none?
11
+ args
12
+ elsif args.none? && kwargs.any?
13
+ [kwargs]
14
+ else
15
+ []
16
+ end
17
+
18
+ super
19
+ end
20
+ end
21
+ end
@@ -11,6 +11,7 @@ module AcidicJob
11
11
  end
12
12
 
13
13
  def call(key:)
14
+ # Skip AR callbacks as there are none on the model
14
15
  key.update_column(:recovery_point, name)
15
16
  end
16
17
  end
@@ -6,7 +6,8 @@
6
6
  module AcidicJob
7
7
  class Response
8
8
  def call(key:)
9
- key.update!(
9
+ # Skip AR callbacks as there are none on the model
10
+ key.update_columns(
10
11
  locked_at: nil,
11
12
  recovery_point: Key::RECOVERY_POINT_FINISHED
12
13
  )
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+
5
+ module AcidicJob
6
+ class Staged < ActiveRecord::Base
7
+ self.table_name = "staged_acidic_jobs"
8
+
9
+ validates :adapter, presence: true
10
+ validates :job_name, presence: true
11
+ validates :job_args, presence: true
12
+
13
+ serialize :job_args
14
+
15
+ after_create_commit :enqueue_job
16
+
17
+ def enqueue_job
18
+ if adapter == "activejob"
19
+ job = ActiveJob::Base.deserialize(job_args)
20
+ job.enqueue
21
+ elsif adapter == "sidekiq"
22
+ Sidekiq::Client.push("class" => job_name, "args" => job_args)
23
+ else
24
+ raise UnknownJobAdapter.new(adapter: adapter)
25
+ end
26
+
27
+ # TODO: ensure successful enqueuing before deletion
28
+ delete
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/acidic_job.rb CHANGED
@@ -1,46 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "acidic_job/version"
4
+ require_relative "acidic_job/errors"
4
5
  require_relative "acidic_job/no_op"
5
6
  require_relative "acidic_job/recovery_point"
6
7
  require_relative "acidic_job/response"
7
8
  require_relative "acidic_job/key"
8
- require_relative "acidic_job/staging"
9
+ require_relative "acidic_job/staged"
10
+ require_relative "acidic_job/perform_wrapper"
11
+ require_relative "acidic_job/perform_transactionally_extension"
9
12
  require "active_support/concern"
10
13
 
11
14
  # rubocop:disable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
12
15
  module AcidicJob
13
- class MismatchedIdempotencyKeyAndJobArguments < StandardError; end
14
-
15
- class LockedIdempotencyKey < StandardError; end
16
-
17
- class UnknownRecoveryPoint < StandardError; end
18
-
19
- class UnknownAtomicPhaseType < StandardError; end
20
-
21
- class SerializedTransactionConflict < StandardError; end
22
-
23
16
  extend ActiveSupport::Concern
24
17
 
25
- module ActiveJobExtension
26
- extend ActiveSupport::Concern
27
-
28
- class_methods do
29
- def perform_transactionally(*args)
30
- AcidicJob::Staging.create!(
31
- serialized_params: job_or_instantiate(*args).serialize
32
- )
33
- end
34
- end
35
- end
36
-
37
18
  included do
38
19
  attr_reader :key
20
+ attr_accessor :arguments_for_perform
39
21
 
40
- # Extend ActiveJob only once it has been loaded
41
- ActiveSupport.on_load(:active_job) do
42
- send(:include, ActiveJobExtension)
43
- end
22
+ # Extend ActiveJob with `perform_transactionally` class method
23
+ include PerformTransactionallyExtension
24
+
25
+ # Ensure our `perform` method always runs first to gather parameters
26
+ prepend PerformWrapper
44
27
  end
45
28
 
46
29
  # Number of seconds passed which we consider a held idempotency key lock to be
@@ -49,8 +32,7 @@ module AcidicJob
49
32
  # this might not happen 100% of the time, so this is a hedge against it.
50
33
  IDEMPOTENCY_KEY_LOCK_TIMEOUT = 90
51
34
 
52
- # &block
53
- # &block
35
+ # takes a block
54
36
  def idempotently(with:)
55
37
  # set accessors for each argument passed in to ensure they are available
56
38
  # to the step methods the job will have written
@@ -71,7 +53,7 @@ module AcidicJob
71
53
  # close proximity, one of the two will be aborted by Postgres because we're
72
54
  # using a transaction with SERIALIZABLE isolation level. It may not look
73
55
  # it, but this code is safe from races.
74
- ensure_idempotency_key_record(job_id, defined_steps.first)
56
+ ensure_idempotency_key_record(idempotency_key_value, defined_steps.first)
75
57
 
76
58
  # if the key record is already marked as finished, immediately return its result
77
59
  return @key.succeeded? if @key.finished?
@@ -101,10 +83,16 @@ module AcidicJob
101
83
  @_steps
102
84
  end
103
85
 
86
+ def safely_finish_acidic_job
87
+ # Short circuits execution by sending execution right to 'finished'.
88
+ # So, ends the job "successfully"
89
+ AcidicJob::Response.new
90
+ end
91
+
104
92
  private
105
93
 
106
94
  def atomic_phase(key, proc = nil, &block)
107
- error = false
95
+ rescued_error = false
108
96
  phase_callable = (proc || block)
109
97
 
110
98
  begin
@@ -114,13 +102,15 @@ module AcidicJob
114
102
  phase_result.call(key: key)
115
103
  end
116
104
  rescue StandardError => e
117
- error = e
105
+ rescued_error = e
118
106
  raise e
119
107
  ensure
108
+ return unless rescued_error
109
+
120
110
  # If we're leaving under an error condition, try to unlock the idempotency
121
- # key right away so that another request can try again.
111
+ # key right away so that another request can try again.3
122
112
  begin
123
- key.update_columns(locked_at: nil, error_object: error) if error.present?
113
+ key.update_columns(locked_at: nil, error_object: rescued_error)
124
114
  rescue StandardError => e
125
115
  # We're already inside an error condition, so swallow any additional
126
116
  # errors from here and just send them to logs.
@@ -136,7 +126,6 @@ module AcidicJob
136
126
  else
137
127
  :serializable
138
128
  end
139
- serialized_job_info = serialize
140
129
 
141
130
  ActiveRecord::Base.transaction(isolation: isolation_level) do
142
131
  @key = Key.find_by(idempotency_key: key_val)
@@ -144,11 +133,15 @@ module AcidicJob
144
133
  if @key
145
134
  # Programs enqueuing multiple jobs with different parameters but the
146
135
  # same idempotency key is a bug.
147
- raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != serialized_job_info["arguments"]
136
+ if @key.job_args != @arguments_for_perform
137
+ raise MismatchedIdempotencyKeyAndJobArguments
138
+ end
148
139
 
149
140
  # Only acquire a lock if the key is unlocked or its lock has expired
150
141
  # because the original job was long enough ago.
151
- raise LockedIdempotencyKey if @key.locked_at && @key.locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT
142
+ if @key.locked_at && @key.locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT
143
+ raise LockedIdempotencyKey
144
+ end
152
145
 
153
146
  # Lock the key and update latest run unless the job is already finished.
154
147
  @key.update!(last_run_at: Time.current, locked_at: Time.current) unless @key.finished?
@@ -158,8 +151,8 @@ module AcidicJob
158
151
  locked_at: Time.current,
159
152
  last_run_at: Time.current,
160
153
  recovery_point: first_step,
161
- job_name: serialized_job_info["job_class"],
162
- job_args: serialized_job_info["arguments"]
154
+ job_name: self.class.name,
155
+ job_args: @arguments_for_perform
163
156
  )
164
157
  end
165
158
  end
@@ -193,5 +186,13 @@ module AcidicJob
193
186
  end
194
187
  end
195
188
  end
189
+
190
+ def idempotency_key_value
191
+ return job_id if defined?(job_id) && !job_id.nil?
192
+ return jid if defined?(jid) && !jid.nil?
193
+
194
+ require 'securerandom'
195
+ SecureRandom.hex
196
+ end
196
197
  end
197
198
  # rubocop:enable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
@@ -3,11 +3,10 @@
3
3
  require "rails/generators"
4
4
  require "rails/generators/active_record"
5
5
 
6
- # This generator adds a migration for the {FriendlyId::History
7
- # FriendlyId::History} addon.
8
6
  class AcidicJobGenerator < ActiveRecord::Generators::Base
9
- # ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase which requires a NAME parameter for the
10
- # new table name. Our generator always uses 'acidic_job_keys', so we just set a random name here.
7
+ # ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase
8
+ # which requires a NAME parameter for the new table name.
9
+ # Our generator always uses "acidic_job_keys", so we just set a random name here.
11
10
  argument :name, type: :string, default: "random_name"
12
11
 
13
12
  source_root File.expand_path("templates", __dir__)
@@ -23,11 +22,16 @@ class AcidicJobGenerator < ActiveRecord::Generators::Base
23
22
  end
24
23
 
25
24
  # Copies the migration template to db/migrate.
26
- def copy_files
27
- migration_template "migration.rb.erb",
25
+ def copy_acidic_job_keys_migration_files
26
+ migration_template "create_acidic_job_keys_migration.rb.erb",
28
27
  "db/migrate/create_acidic_job_keys.rb"
29
28
  end
30
29
 
30
+ def copy_staged_acidic_jobs_migration_files
31
+ migration_template "create_staged_acidic_jobs_migration.rb.erb",
32
+ "db/migrate/create_staged_acidic_jobs.rb"
33
+ end
34
+
31
35
  protected
32
36
 
33
37
  def migration_class
@@ -13,9 +13,6 @@ class CreateAcidicJobKeys < <%= migration_class %>
13
13
  t.index %i[idempotency_key job_name job_args],
14
14
  unique: true,
15
15
  name: "idx_acidic_job_keys_on_idempotency_key_n_job_name_n_job_args"
16
-
17
- create_table :acidic_job_stagings do |t|
18
- t.text :serialized_params, null: false
19
16
  end
20
17
  end
21
18
  end
@@ -0,0 +1,9 @@
1
+ class CreateStagedAcidicJobs < <%= migration_class %>
2
+ def change
3
+ create_table :staged_acidic_jobs do |t|
4
+ t.string :adapter, null: false
5
+ t.string :job_name, null: false
6
+ t.text :job_args, null: true
7
+ end
8
+ end
9
+ end
data/slides.md ADDED
@@ -0,0 +1,65 @@
1
+ # ACIDic Jobs
2
+
3
+ ## A bit about me
4
+
5
+ - programming in Ruby for 6 years
6
+ - working for test IO / EPAM
7
+ - consulting for RCRDSHP
8
+ - building Smokestack QA on the side
9
+
10
+ ## Jobs are essential
11
+
12
+ - job / operation / work
13
+ - in every company, with every app, jobs are essential. Why?
14
+ - jobs are what your app *does*, expressed as a distinct unit
15
+ - jobs can be called from anywhere, run sync or async, and have retry mechanisms built-in
16
+
17
+ ## Jobs are internal API endpoints
18
+
19
+ - Like API endpoints, both are discrete units of work
20
+ - Like API endpoints, we should expect failure
21
+ - Like API endpoints, we should expect retries
22
+ - Like API endpoints, we should expect concurrency
23
+ - this symmetry allows us to port much of the wisdom built up over decades of building robust APIs to our app job infrastructure
24
+
25
+ ## ACIDic APIs
26
+
27
+ In a loosely collected series of articles, Brandur Leach lays out the core techniques and principles required to make an HTTP API properly ACIDic:
28
+
29
+ 1. https://brandur.org/acid
30
+ 2. https://brandur.org/http-transactions
31
+ 3. https://brandur.org/job-drain
32
+ 4. https://brandur.org/idempotency-keys
33
+
34
+ His central points can be summarized as follows:
35
+
36
+ - "ACID databases are one of the most important tools in existence for ensuring maintainability and data correctness in big production systems"
37
+ - "for a common idempotent HTTP request, requests should map to backend transactions at 1:1"
38
+ - "We can dequeue jobs gracefully by using a transactionally-staged job drain."
39
+ - "Implementations that need to make synchronous changes in foreign state (i.e. outside of a local ACID store) are somewhat more difficult to design. ... To guarantee idempotency on this type of endpoint we’ll need to introduce idempotency keys."
40
+
41
+ Key concepts:
42
+
43
+ - foreign state mutations
44
+ - The reason that the local vs. foreign distinction matters is that unlike a local set of operations where we can leverage an ACID store to roll back a result that we didn’t like, once we make our first foreign state mutation, we’re committed one way or another
45
+ - "An atomic phase is a set of local state mutations that occur in transactions between foreign state mutations."
46
+ - "A recovery point is a name of a check point that we get to after having successfully executed any atomic phase or foreign state mutation"
47
+ - "transactionally-staged job drain"
48
+ - "With this pattern, jobs aren’t immediately sent to the job queue. Instead, they’re staged in a table within the relational database itself, and the ACID properties of the running transaction keep them invisible until they’re ready to be worked. A secondary enqueuer process reads the table and sends any jobs it finds to the job queue before removing their rows."
49
+
50
+
51
+ https://github.com/mperham/sidekiq/wiki/Best-Practices#2-make-your-job-idempotent-and-transactional
52
+
53
+ 2. Make your job idempotent and transactional
54
+
55
+ Idempotency means that your job can safely execute multiple times. For instance, with the error retry functionality, your job might be half-processed, throw an error, and then be re-executed over and over until it successfully completes. Let's say you have a job which voids a credit card transaction and emails the user to let them know the charge has been refunded:
56
+
57
+ ```ruby
58
+ def perform(card_charge_id)
59
+ charge = CardCharge.find(card_charge_id)
60
+ charge.void_transaction
61
+ Emailer.charge_refunded(charge).deliver
62
+ end
63
+ ```
64
+
65
+ What happens when the email fails to render due to a bug? Will the void_transaction method handle the case where a charge has already been refunded? You can use a database transaction to ensure data changes are rolled back if there is an error or you can write your code to be resilient in the face of errors. Just remember that Sidekiq will execute your job at least once, not exactly once.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acidic_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fractaledmind
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-06 00:00:00.000000000 Z
11
+ date: 2021-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -71,15 +71,21 @@ files:
71
71
  - acidic_job.gemspec
72
72
  - bin/console
73
73
  - bin/setup
74
+ - blog_post.md
74
75
  - lib/acidic_job.rb
76
+ - lib/acidic_job/errors.rb
75
77
  - lib/acidic_job/key.rb
76
78
  - lib/acidic_job/no_op.rb
79
+ - lib/acidic_job/perform_transactionally_extension.rb
80
+ - lib/acidic_job/perform_wrapper.rb
77
81
  - lib/acidic_job/recovery_point.rb
78
82
  - lib/acidic_job/response.rb
79
- - lib/acidic_job/staging.rb
83
+ - lib/acidic_job/staged.rb
80
84
  - lib/acidic_job/version.rb
81
85
  - lib/generators/acidic_job_generator.rb
82
- - lib/generators/templates/migration.rb.erb
86
+ - lib/generators/templates/create_acidic_job_keys_migration.rb.erb
87
+ - lib/generators/templates/create_staged_acidic_jobs_migration.rb.erb
88
+ - slides.md
83
89
  homepage: https://github.com/fractaledmind/acidic_job
84
90
  licenses:
85
91
  - MIT
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcidicJob
4
- class Staging < ActiveRecord::Base
5
- self.table_name = "acidic_job_stagings"
6
-
7
- validates :serialized_params, presence: true
8
-
9
- serialize :serialized_params
10
-
11
- after_create_commit :enqueue_job
12
-
13
- def enqueue_job
14
- job = ActiveJob::Base.deserialize(serialized_params)
15
- job.enqueue
16
- delete
17
- end
18
- end
19
- end