marj 2.0.0 → 2.1.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: c4feb80230bafd2d67bdb0316598f4c46eabd01ca86c35fb8b01e04c865f91a8
4
- data.tar.gz: be461234b8359c9f974dd4160d896cc048d0dcc1f4b7d74b57042d62f9950a18
3
+ metadata.gz: '078c3d48b27784e09425ba8eda2207038d6ce5c7a711953e8567c76aac689435'
4
+ data.tar.gz: c129b4848de1e2fe55de0de3a4893586a052cd814df798879dc196a81c1a94d5
5
5
  SHA512:
6
- metadata.gz: 31735c1c143a47ab490ed08eca12af6d8fce73b551637ed92e37bcb42e8b2560135ad674dfc832353988f4246c10f9ff2822f624bb10ba256070632dd35c209c
7
- data.tar.gz: 7f19dee3c8b0510d27ca10eb8f9d64ecfc2e1174ae1cea71e17abf42f0ce8ef07be4f84e1eff9cf0150e6162bb3a790e066e36f17db131056e30ebbeada1bdc3
6
+ metadata.gz: 4cef48fb7c862123f4a5ad7ed5bf7a9d95eb3d7036831db79ad3dc9cf94f074a112ce58f0189484f991a52185cdf8e8ceed48dd9237bf4a04d5e3bbe30b2d274
7
+ data.tar.gz: 7631b1a0fef1ece1edcf15541dc85d25b2d5d3ba1cb8ca716133975126e5ce9bb4f28d627a4c2a5d751029ce8f67ba4db47c6fa9d197467fade4c09d20a04716
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # Marj
1
+ # Marj - Minimal ActiveRecord Jobs
2
2
 
3
- Marj is a Minimal ActiveRecord-based Jobs library.
3
+ The simplest database-backed ActiveJob queueing backend.
4
+
5
+ ## Quick Links
4
6
 
5
7
  API docs: https://www.rubydoc.info/github/nicholasdower/marj <br>
6
8
  RubyGems: https://rubygems.org/gems/marj <br>
@@ -8,27 +10,52 @@ Changelog: https://github.com/nicholasdower/marj/releases <br>
8
10
  Issues: https://github.com/nicholasdower/marj/issues <br>
9
11
  Development: https://github.com/nicholasdower/marj/blob/master/CONTRIBUTING.md
10
12
 
11
- For more information on ActiveJob, see:
13
+ ## Features
12
14
 
13
- - https://edgeguides.rubyonrails.org/active_job_basics.html
14
- - https://www.rubydoc.info/gems/activejob
15
- - https://github.com/nicholasdower/marj/blob/master/README.md#activejob-cheatsheet
15
+ - Enqueued jobs are written to the database.
16
+ - Successfully executed jobs are deleted from the database.
17
+ - Failed jobs which should be retried are updated in the database.
18
+ - Failed jobs which should not be retried are deleted from the database.
19
+
20
+ ## Features Not Provided
21
+
22
+ - Workers
23
+ - Timeouts
24
+ - Concurrency Controls
25
+ - Observability
26
+ - A User Interace
27
+ - Anything else you might dream up.
28
+
29
+ ## Interface
30
+
31
+ - `Marj` - An ActiveRecord model class
32
+ - `Marj.ready` - Used to retrieve jobs ready to be executed
33
+ - `Marj#execute` - Used to execute jobs retrieved from the database
34
+ - `MarjConfig.table_name=` - Used to override the default table name
16
35
 
17
36
  ## Setup
18
37
 
19
38
  ### 1. Install
20
39
 
21
- Add the following to your Gemfile:
40
+ ```shell
41
+ bundle add marj
22
42
 
23
- ```ruby
24
- gem 'marj', '~> 1.0'
43
+ # or
44
+
45
+ gem install marj
25
46
  ```
26
47
 
27
- ```shell
28
- bundle install
48
+ ### 2. Configure
49
+
50
+ By default, the database table is named "jobs". To use a different table name:
51
+
52
+ ```ruby
53
+ require 'marj'
54
+
55
+ MarjConfig.table_name = 'some_name'
29
56
  ```
30
57
 
31
- ### 2. Create the jobs table
58
+ ### 3. Create the database table
32
59
 
33
60
  ```ruby
34
61
  class CreateJobs < ActiveRecord::Migration[7.1]
@@ -57,15 +84,7 @@ class CreateJobs < ActiveRecord::Migration[7.1]
57
84
  end
58
85
  ```
59
86
 
60
- To use a different table name:
61
-
62
- ```ruby
63
- require 'marj'
64
-
65
- MarjConfig.table_name = 'some_name'
66
- ```
67
-
68
- ### 3. Configure the queue adapter
87
+ ### 4. Configure the queue adapter
69
88
 
70
89
  ```ruby
71
90
  require 'marj'
@@ -93,28 +112,40 @@ job.perform_now
93
112
 
94
113
  # Enqueue, retrieve and manually run a job:
95
114
  SomeJob.perform_later('foo')
96
- record = Marj.first
97
- record.execute
115
+ Marj.first.execute
98
116
 
99
117
  # Run all ready jobs:
100
- while (record = Marj.ready.first)
101
- record.execute
102
- end
118
+ Marj.ready.each(&:execute)
119
+
120
+ # Run all ready jobs, querying each time:
121
+ loop { Marj.ready.first&.tap(&:execute) || break }
103
122
 
104
123
  # Run all ready jobs in a specific queue:
105
- while (record = Marj.where(queue_name: 'foo').ready.first)
106
- record.execute
107
- end
124
+ loop { Marj.where(queue_name: 'foo').ready.first&.tap(&:execute) || break }
108
125
 
109
126
  # Run jobs as they become ready:
110
127
  loop do
111
- while (record = Marj.ready.first)
112
- record.execute
113
- end
128
+ loop { Marj.ready.first&.tap(&:execute) || break }
129
+ rescue Exception => e
130
+ logger.error(e)
131
+ ensure
114
132
  sleep 5.seconds
115
133
  end
116
134
  ```
117
135
 
136
+ ## Testing
137
+
138
+ By default, jobs enqeued during tests will be written to the database. Enqueued jobs can be executed via:
139
+
140
+ ```ruby
141
+ Marj.ready.each(&:execute)
142
+ ```
143
+
144
+ Alternatively, to use [ActiveJob::QueueAdapters::TestAdapter](https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/TestAdapter.html):
145
+ ```ruby
146
+ ActiveJob::Base.queue_adapter = :test
147
+ ```
148
+
118
149
  ## Extension Examples
119
150
 
120
151
  ### Timeouts
@@ -176,6 +207,12 @@ end
176
207
 
177
208
  ## ActiveJob Cheatsheet
178
209
 
210
+ For more information on ActiveJob, see:
211
+
212
+ - https://edgeguides.rubyonrails.org/active_job_basics.html
213
+ - https://www.rubydoc.info/gems/activejob
214
+ - https://github.com/nicholasdower/marj/blob/master/README.md#activejob-cheatsheet
215
+
179
216
  ### Configuring a Queue Adapter
180
217
 
181
218
  ```ruby
@@ -253,6 +290,8 @@ SomeJob.perform_now(args)
253
290
  SomeJob.new(args).enqueue
254
291
  SomeJob.new(args).enqueue(options)
255
292
 
293
+ SomeJob.perform_later(SomeJob.new(args))
294
+
256
295
  SomeJob.perform_later(args)
257
296
  SomeJob.set(options).perform_later(args)
258
297
 
data/lib/marj.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # See https://github.com/nicholasdower/marj
4
+
3
5
  require_relative 'marj_adapter'
4
6
  require_relative 'marj_config'
5
7
 
6
- Kernel.autoload(:Marj, File.expand_path('../app/models/marj.rb', __dir__))
8
+ Kernel.autoload(:Marj, File.expand_path('marj_record.rb', __dir__))
data/lib/marj_adapter.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # ActiveJob queue adapter for Marj.
4
+ #
5
+ # See https://github.com/nicholasdower/marj
4
6
  class MarjAdapter
5
7
  # Enqueue a job for immediate execution.
6
8
  #
data/lib/marj_config.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Marj configuration.
4
+ #
5
+ # See https://github.com/nicholasdower/marj
4
6
  class MarjConfig
5
7
  @table_name = 'jobs'
6
8
 
@@ -2,14 +2,14 @@
2
2
 
3
3
  require 'active_job'
4
4
  require 'active_record'
5
- require_relative '../../lib/marj_config'
5
+ require_relative 'marj_config'
6
6
 
7
- # Marj is a Minimal ActiveRecord-based Jobs library.
7
+ # The Marj ActiveRecord model class.
8
8
  #
9
9
  # See https://github.com/nicholasdower/marj
10
10
  class Marj < ActiveRecord::Base
11
11
  # The Marj version.
12
- VERSION = '2.0.0'
12
+ VERSION = '2.1.0'
13
13
 
14
14
  # Executes the job associated with this record and returns the result.
15
15
  def execute
@@ -29,13 +29,9 @@ class Marj < ActiveRecord::Base
29
29
 
30
30
  # ActiveJob::Base#deserialize expects dates to be strings rather than Time objects.
31
31
  job_data = job_data.to_h { |k, v| [k, %w[enqueued_at scheduled_at].include?(k) ? v&.iso8601 : v] }
32
- job.deserialize(job_data)
33
32
 
34
- new_executions = executions + 1
35
- job.perform_now.tap do
36
- # If no error was raised, the job should either be destroyed (success) or updated (retryable failure).
37
- raise "job #{job_id} not destroyed or updated" unless destroyed? || (executions == new_executions && !changed?)
38
- end
33
+ job.deserialize(job_data)
34
+ job.perform_now
39
35
  end
40
36
  end
41
37
 
@@ -97,15 +93,20 @@ class Marj < ActiveRecord::Base
97
93
  # @param job [ActiveJob::Base]
98
94
  # @return [ActiveJob::Base]
99
95
  def self.register_callbacks(job, record)
100
- raise 'callbacks already registered' if job.singleton_class.instance_variable_get(:@__marj)
96
+ if job.singleton_class.instance_variable_get(:@__marj)
97
+ # Callbacks already registered. We just need to update the record.
98
+ job.singleton_class.instance_variable_set(:@__marj, record)
99
+ return
100
+ end
101
101
 
102
102
  # We need to detect three cases:
103
103
  # - If a job succeeds, after_perform will be called.
104
104
  # - If a job fails and should be retried, enqueue will be called. This is handled by the enqueue method.
105
105
  # - If a job exceeds its max attempts, after_discard will be called.
106
- job.singleton_class.after_perform { |_j| record.destroy! }
107
- job.singleton_class.after_discard { |_j, _exception| record.destroy! }
106
+ job.singleton_class.after_perform { |_j| job.singleton_class.instance_variable_get(:@__marj).destroy! }
107
+ job.singleton_class.after_discard { |_j, _exception| job.singleton_class.instance_variable_get(:@__marj).destroy! }
108
108
  job.singleton_class.instance_variable_set(:@__marj, record)
109
+
109
110
  job
110
111
  end
111
112
  private_class_method :register_callbacks
@@ -124,19 +125,28 @@ class Marj < ActiveRecord::Base
124
125
  # registered on the job instance so that when the job is executed, the database record is deleted or updated
125
126
  # (depending on the result).
126
127
  #
127
- # There are three cases:
128
+ # There are two normal cases:
128
129
  # - The first time a job is enqueued, we need to create the record and register callbacks.
129
130
  # - If a previously enqueued job instance is re-enqueued, for instance after execution fails, callbacks have
130
131
  # already been registered. In this case we only need to update the record.
131
- # - It is also possible for new job instance to be created for a job that is already in the database. In this case
132
- # we need to update the record and register callbacks.
133
132
  #
134
133
  # We keep track of whether callbacks have been registered by setting the @__marj instance variable on the job's
135
134
  # singleton class. This holds a reference to the record. This allows us to update the record without re-fetching it
136
135
  # and also ensures that if execute is called on a record any updates to the database are reflected on that record
137
136
  # instance.
137
+ #
138
+ # There are also two edge cases:
139
+ # - It is possible for new job instance to be created for a job that is already in the database. In this case
140
+ # we need to update the record and register callbacks.
141
+ # - It is possible for the underlying row corresponding to an existing job to have been deleted. In this case we
142
+ # need to create a new record and update the reference on the job's singleton class.
138
143
  if (record = job.singleton_class.instance_variable_get(:@__marj))
139
- record.update!(serialized)
144
+ if Marj.exists?(job_id: job.job_id)
145
+ record.update!(serialized)
146
+ else
147
+ record = Marj.create!(serialized)
148
+ register_callbacks(job, record)
149
+ end
140
150
  else
141
151
  record = Marj.find_or_create_by!(job_id: job.job_id) { _1.assign_attributes(serialized) }
142
152
  register_callbacks(job, record)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marj
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-22 00:00:00.000000000 Z
11
+ date: 2024-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -38,7 +38,8 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.1'
41
- description: Minimal ActiveRecord-based Jobs library
41
+ description: Marj (Minimal ActiveRecord Jobs) is the simplest database-backed ActiveJob
42
+ queueing backend.
42
43
  email: nicholasdower@gmail.com
43
44
  executables: []
44
45
  extensions: []
@@ -46,17 +47,17 @@ extra_rdoc_files: []
46
47
  files:
47
48
  - LICENSE.txt
48
49
  - README.md
49
- - app/models/marj.rb
50
50
  - lib/marj.rb
51
51
  - lib/marj_adapter.rb
52
52
  - lib/marj_config.rb
53
+ - lib/marj_record.rb
53
54
  homepage: https://github.com/nicholasdower/marj
54
55
  licenses:
55
56
  - MIT
56
57
  metadata:
57
58
  bug_tracker_uri: https://github.com/nicholasdower/marj/issues
58
- changelog_uri: https://github.com/nicholasdower/marj/releases/tag/v2.0.0
59
- documentation_uri: https://www.rubydoc.info/github/nicholasdower/marj/v2.0.0
59
+ changelog_uri: https://github.com/nicholasdower/marj/releases/tag/v2.1.0
60
+ documentation_uri: https://www.rubydoc.info/github/nicholasdower/marj/v2.1.0
60
61
  homepage_uri: https://github.com/nicholasdower/marj
61
62
  rubygems_mfa_required: 'true'
62
63
  source_code_uri: https://github.com/nicholasdower/marj
@@ -78,5 +79,5 @@ requirements: []
78
79
  rubygems_version: 3.5.3
79
80
  signing_key:
80
81
  specification_version: 4
81
- summary: An ActiveJob queuing backend backed by ActiveRecord.
82
+ summary: Minimal ActiveRecord Jobs
82
83
  test_files: []