marj 2.0.0 → 2.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: 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: []