performs 0.3.4
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 +7 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +79 -0
- data/LICENSE.txt +21 -0
- data/README.md +345 -0
- data/Rakefile +3 -0
- data/lib/active_job/performs/version.rb +7 -0
- data/lib/active_job/performs.rb +82 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 58f4522465e497385cd7d88c3b8bf68eb83505addcbfc23024b4af4c7d21e855
|
|
4
|
+
data.tar.gz: d446c5a77683f7c6e5c757fcdfabecd80a0556f46fd5fc247107a38077f1e6fd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a1af1460078f19f76d980701f9ab0f3cf09c567e291e54399f8ed7b6580373e2de8d29db08b42d4aeaabf0ee5ca135d0670538f95b508b6affa50c4e3803d6b6
|
|
7
|
+
data.tar.gz: f29b84a433c8b9ce582353af9b79f7b624f7686b93c210b30065ad10fd92f7412df1c355482f44ee8fc07b1a145db8075b5ce0069b08d9570954067db9ef93d8
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.3.4] - 2025-11-28
|
|
4
|
+
|
|
5
|
+
- Breaking change. `performs` return only the later method, not an array.
|
|
6
|
+
|
|
7
|
+
## [0.2.0] - 2023-01-12
|
|
8
|
+
|
|
9
|
+
- Supports `private performs :some_method`
|
|
10
|
+
|
|
11
|
+
Which then generates a private `some_method_later` method.
|
|
12
|
+
|
|
13
|
+
This was already technically supported but a test was added to declare it expected.
|
|
14
|
+
|
|
15
|
+
- Support method suffixes ! and ?
|
|
16
|
+
|
|
17
|
+
You can call `performs :some_method!` and have `some_method_later!` generated. Same for `?`.
|
|
18
|
+
|
|
19
|
+
- Support `performs` on private methods
|
|
20
|
+
|
|
21
|
+
Method jobs will now call methods with `send`, in case you only want to expose the generated later method to the outside world.
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
class Post < ActiveRecord::Base
|
|
25
|
+
performs :something_low_level
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# We don't want other objects to call this, they should always use the generated later method.
|
|
30
|
+
def something_low_level
|
|
31
|
+
# …
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Here, the generated `Post#something_low_level_later` is public and available but can still call into the immediate version of `something_low_level`.
|
|
37
|
+
|
|
38
|
+
## [0.1.1] - 2022-09-27
|
|
39
|
+
|
|
40
|
+
- Fixed: extend ActiveRecord::Base with ActiveJob::Performs as the README says.
|
|
41
|
+
|
|
42
|
+
## [0.1.0] - 2022-09-27
|
|
43
|
+
|
|
44
|
+
- Initial release
|
data/Gemfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in active_job-performs.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem "rake", "~> 13.0"
|
|
9
|
+
|
|
10
|
+
gem "minitest", "~> 5.0"
|
|
11
|
+
gem "minitest-sprint"
|
|
12
|
+
|
|
13
|
+
gem "debug"
|
|
14
|
+
|
|
15
|
+
# Fetch latest Active Job to test `ActiveJob.perform_all_later`
|
|
16
|
+
gem "activejob", ">= 7.1"
|
|
17
|
+
gem "activerecord", ">= 7.1"
|
|
18
|
+
|
|
19
|
+
gem "sqlite3", "~> 1.4"
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
active_job-performs (0.3.3)
|
|
5
|
+
activejob (>= 6.1)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
activejob (7.1.3.4)
|
|
11
|
+
activesupport (= 7.1.3.4)
|
|
12
|
+
globalid (>= 0.3.6)
|
|
13
|
+
activemodel (7.1.3.4)
|
|
14
|
+
activesupport (= 7.1.3.4)
|
|
15
|
+
activerecord (7.1.3.4)
|
|
16
|
+
activemodel (= 7.1.3.4)
|
|
17
|
+
activesupport (= 7.1.3.4)
|
|
18
|
+
timeout (>= 0.4.0)
|
|
19
|
+
activesupport (7.1.3.4)
|
|
20
|
+
base64
|
|
21
|
+
bigdecimal
|
|
22
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
23
|
+
connection_pool (>= 2.2.5)
|
|
24
|
+
drb
|
|
25
|
+
i18n (>= 1.6, < 2)
|
|
26
|
+
minitest (>= 5.1)
|
|
27
|
+
mutex_m
|
|
28
|
+
tzinfo (~> 2.0)
|
|
29
|
+
base64 (0.2.0)
|
|
30
|
+
bigdecimal (3.1.8)
|
|
31
|
+
concurrent-ruby (1.3.3)
|
|
32
|
+
connection_pool (2.4.1)
|
|
33
|
+
debug (1.9.2)
|
|
34
|
+
irb (~> 1.10)
|
|
35
|
+
reline (>= 0.3.8)
|
|
36
|
+
drb (2.2.1)
|
|
37
|
+
globalid (1.2.1)
|
|
38
|
+
activesupport (>= 6.1)
|
|
39
|
+
i18n (1.14.5)
|
|
40
|
+
concurrent-ruby (~> 1.0)
|
|
41
|
+
io-console (0.7.2)
|
|
42
|
+
irb (1.13.2)
|
|
43
|
+
rdoc (>= 4.0.0)
|
|
44
|
+
reline (>= 0.4.2)
|
|
45
|
+
minitest (5.24.0)
|
|
46
|
+
minitest-sprint (1.2.2)
|
|
47
|
+
path_expander (~> 1.1)
|
|
48
|
+
mutex_m (0.2.0)
|
|
49
|
+
path_expander (1.1.1)
|
|
50
|
+
psych (5.1.2)
|
|
51
|
+
stringio
|
|
52
|
+
rake (13.2.1)
|
|
53
|
+
rdoc (6.7.0)
|
|
54
|
+
psych (>= 4.0.0)
|
|
55
|
+
reline (0.5.9)
|
|
56
|
+
io-console (~> 0.5)
|
|
57
|
+
sqlite3 (1.7.3-arm64-darwin)
|
|
58
|
+
sqlite3 (1.7.3-x86_64-linux)
|
|
59
|
+
stringio (3.1.1)
|
|
60
|
+
timeout (0.4.1)
|
|
61
|
+
tzinfo (2.0.6)
|
|
62
|
+
concurrent-ruby (~> 1.0)
|
|
63
|
+
|
|
64
|
+
PLATFORMS
|
|
65
|
+
arm64-darwin
|
|
66
|
+
x86_64-linux
|
|
67
|
+
|
|
68
|
+
DEPENDENCIES
|
|
69
|
+
active_job-performs!
|
|
70
|
+
activejob (>= 7.1)
|
|
71
|
+
activerecord (>= 7.1)
|
|
72
|
+
debug
|
|
73
|
+
minitest (~> 5.0)
|
|
74
|
+
minitest-sprint
|
|
75
|
+
rake (~> 13.0)
|
|
76
|
+
sqlite3 (~> 1.4)
|
|
77
|
+
|
|
78
|
+
BUNDLED WITH
|
|
79
|
+
2.5.14
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Kasper Timm Hansen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# ActiveJob::Performs
|
|
2
|
+
|
|
3
|
+
`ActiveJob::Performs` adds a `performs` class method to make the model + job loop vastly more conventional. You use it like this:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
class Post < ApplicationRecord
|
|
7
|
+
performs :publish
|
|
8
|
+
# Or `performs def publish`!
|
|
9
|
+
|
|
10
|
+
def publish
|
|
11
|
+
# Some logic to publish a post
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Here's what `performs` generates under the hood:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
class Post < ApplicationRecord
|
|
20
|
+
class Job < ApplicationJob; end # We build a general Job class to share configuration between method jobs.
|
|
21
|
+
|
|
22
|
+
# Individual method jobs inherit from the `Post::Job` defined above.
|
|
23
|
+
class PublishJob < Job
|
|
24
|
+
# We generate the required `perform` method passing in the `post` and calling `publish` on it.
|
|
25
|
+
def perform(post, *, **) = post.publish(*, **)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# On Rails 7.1+, where `ActiveJob.perform_all_later` exists, we also generate
|
|
29
|
+
# a bulk method to enqueue many jobs at once. So you can do this:
|
|
30
|
+
#
|
|
31
|
+
# Post.unpublished.in_batches.each(&:publish_later_bulk)
|
|
32
|
+
def self.publish_later_bulk(set = all)
|
|
33
|
+
ActiveJob.perform_all_later set.map { PublishJob.new(_1) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# We generate `publish_later` to wrap the job execution forwarding arguments and options.
|
|
37
|
+
def publish_later(*, **) = PublishJob.perform_later(self, *, **)
|
|
38
|
+
|
|
39
|
+
def publish
|
|
40
|
+
# Some logic to publish a post.
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Benefits
|
|
46
|
+
|
|
47
|
+
1. Conventional Jobs: they'll now mostly call instance methods like `publish_later` -> `publish`.
|
|
48
|
+
1. Follows Rails' internal conventions: this borrows from `ActionMailbox::InboundEmail#process_later` calling `process` and `ActionMailer::Base#deliver_later` calling `deliver`.
|
|
49
|
+
1. Clarity & less guess work: the `_later` methods standardize how you call jobs throughout your app, so you can instantly tell what's happening.
|
|
50
|
+
1. Less tedium: getting an instance method run in the background is just now a `performs` call with some potential configuration.
|
|
51
|
+
1. Fewer files to manage: you don't have to dig up something in `app/jobs` just to learn almost nothing from the boilerplate in there.
|
|
52
|
+
1. Remaining jobs stand out: `app/jobs` is way lighter, so any jobs in there that don't fit the `performs` pattern now stand out way more.
|
|
53
|
+
1. More consolidated logic: sometimes Job classes house model-level logic, but now it's all the way out in `app/jobs` instead of `app/models`, huh?
|
|
54
|
+
|
|
55
|
+
> [!TIP]
|
|
56
|
+
> On that last point, `performs` does put more logic back within your Active Records, so if you need further encapsulation to prevent them growing too large, consider checking out [active_record-associated_object](https://github.com/kaspth/active_record-associated_object).
|
|
57
|
+
|
|
58
|
+
### Used in production & praise from people
|
|
59
|
+
|
|
60
|
+
The https://www.rubyevents.org team uses `ActiveJob::Performs` quite a bit:
|
|
61
|
+
|
|
62
|
+
- [See `performs` calls in RubyEvents](https://github.com/search?q=repo%3Arubyevents%2Frubyevents+performs+language%3ARuby&type=code&l=Ruby)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Here's what [@claudiob](https://github.com/claudiob) had to say after using `ActiveJob::Performs`:
|
|
66
|
+
|
|
67
|
+
> I’ve been using active_job-performs for the last month and I love it love it love it!!
|
|
68
|
+
>
|
|
69
|
+
> Your thought process behind it is so thorough. I have a bunch of jobs now attached to models and my app/jobs folder… is empty!!
|
|
70
|
+
>
|
|
71
|
+
> This saves me a lot of mental hoops, I don’t have to switch between files anymore, everything is self-contained. Thank you!!!
|
|
72
|
+
|
|
73
|
+
From [@andycroll](https://github.com/andycroll) in a [writeup](https://andycroll.com/ruby/launching-usingrails) about launching [UsingRails](https://usingrails.com):
|
|
74
|
+
|
|
75
|
+
> I’ve also adopted a couple of gems—with exceptional Rails-level taste and author pedigree—that I hadn’t used in anger before, including `active_job-performs` from Kasper […]. Would recommend both.
|
|
76
|
+
|
|
77
|
+
And [@nshki](https://github.com/nshki) after trying it:
|
|
78
|
+
|
|
79
|
+
> Spent some time playing with [@kaspth](https://github.com/kaspth)'s [`ActiveRecord::AssociatedObject`](https://github.com/kaspth/active_record-associated_object) and `ActiveJob::Performs` and wow! The conventions these gems put in place help simplify a codebase drastically. I particularly love `ActiveJob::Performs`—it helped me refactor out all `ApplicationJob` classes I had and keep important context in the right domain model.
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
### with `ActiveRecord::Base` & other `GlobalID::Identification` objects
|
|
83
|
+
|
|
84
|
+
`ActiveJob::Performs` works with any object that has `include GlobalID::Identification` and responds to that interface.
|
|
85
|
+
|
|
86
|
+
`ActiveRecord::Base` implements this, so here's how that looks:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
class Post < ActiveRecord::Base
|
|
90
|
+
extend ActiveJob::Performs # We technically auto-extend ActiveRecord::Base, but other object hierarchies need this.
|
|
91
|
+
|
|
92
|
+
# `performs` builds a `Post::PublishJob` and routes configs over to it.
|
|
93
|
+
performs :publish, queue_adapter: :sidekiq, queue_as: :important, discard_on: SomeError do
|
|
94
|
+
retry_on TimeoutError, wait: :polynomially_longer
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def publish
|
|
98
|
+
…
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Here's what `performs` generates under the hood:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class Post < ActiveRecord::Base
|
|
107
|
+
# We setup a general Job class that's shared between method jobs.
|
|
108
|
+
class Job < ApplicationJob; end
|
|
109
|
+
|
|
110
|
+
# Individual method jobs inherit from the `Post::Job` defined above.
|
|
111
|
+
class PublishJob < Job
|
|
112
|
+
self.queue_adapter = :sidekiq
|
|
113
|
+
queue_as :important
|
|
114
|
+
discard_on SomeError
|
|
115
|
+
retry_on TimeoutError, wait: :polynomially_longer
|
|
116
|
+
|
|
117
|
+
# We generate `perform` passing in the `post` and calling `publish` on it.
|
|
118
|
+
def perform(post, *arguments, **options)
|
|
119
|
+
post.publish(*arguments, **options)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# On Rails 7.1, where `ActiveJob.perform_all_later` exists, we also generate
|
|
124
|
+
# a bulk method to enqueue many jobs at once. So you can do this:
|
|
125
|
+
#
|
|
126
|
+
# Post.unpublished.in_batches.each(&:publish_later_bulk)
|
|
127
|
+
#
|
|
128
|
+
# Or pass in a subset of posts as an argument:
|
|
129
|
+
#
|
|
130
|
+
# Post.publish_later_bulk Post.unpublished
|
|
131
|
+
def self.publish_later_bulk(set = all)
|
|
132
|
+
ActiveJob.perform_all_later set.map { PublishJob.new(_1) }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# We generate `publish_later` to wrap the job execution.
|
|
136
|
+
def publish_later(*arguments, **options)
|
|
137
|
+
PublishJob.perform_later(self, *arguments, **options)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def publish
|
|
141
|
+
…
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> [!NOTE]
|
|
147
|
+
> We prefer & call `{name}=` setter methods, but fall back to getters. That's how we support `self.queue_adapter=`, but also `queue_as` which is not configured via `queue_as=`.
|
|
148
|
+
|
|
149
|
+
We generate the `Post::Job` class above to share configuration between method level jobs. E.g. if you had a `retract` method that was setup very similar, you could do:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
class Post < ActiveRecord::Base
|
|
153
|
+
performs queue_as: :important
|
|
154
|
+
performs :publish
|
|
155
|
+
performs :retract
|
|
156
|
+
|
|
157
|
+
def publish
|
|
158
|
+
…
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def retract(reason:)
|
|
162
|
+
…
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Which would then become:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
class Post < ActiveRecord::Base
|
|
171
|
+
class Job < ApplicationJob
|
|
172
|
+
queue_as :important
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
class PublishJob < Job
|
|
176
|
+
…
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
class RetractJob < Job
|
|
180
|
+
…
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
…
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Establishing patterns across your app
|
|
188
|
+
|
|
189
|
+
If there's an Active Record method that you'd like any model to be able to run from a background job, you can set them up in your `ApplicationRecord`:
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
class ApplicationRecord < ActiveRecord::Base
|
|
193
|
+
self.abstract_class = true
|
|
194
|
+
|
|
195
|
+
# We're passing specific queues for monitoring, but you may not need or want them.
|
|
196
|
+
performs :touch, queue_as: "active_record.touch"
|
|
197
|
+
performs :update, queue_as: "active_record.update"
|
|
198
|
+
performs :destroy, queue_as: "active_record.destroy"
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Then a model could now run things like:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
record.touch_later
|
|
206
|
+
record.touch_later :reminded_at, time: 5.minutes.from_now # Pass supported arguments to `touch`
|
|
207
|
+
|
|
208
|
+
record.update_later reminded_at: 1.year.ago
|
|
209
|
+
|
|
210
|
+
# Particularly handy to use on a record with many `dependent: :destroy` associations.
|
|
211
|
+
# Plus if anything fails, the transaction will rollback and the job fails, so you can retry it later!
|
|
212
|
+
record.destroy_later
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
You may not want this for `touch` and `update`, and maybe you'd rather architect your system in such a way that they don't have so many side-effects, but having the option can be handy!
|
|
216
|
+
|
|
217
|
+
Also, I haven't tested all the Active Record methods, so please file an issue if you encounter any.
|
|
218
|
+
|
|
219
|
+
#### Method suffixes
|
|
220
|
+
|
|
221
|
+
`ActiveJob::Performs` supports Ruby's stylistic method suffixes, i.e. ? and ! respectively.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
class Post < ActiveRecord::Base
|
|
225
|
+
performs :publish! # Generates `publish_later!` which calls `publish!`.
|
|
226
|
+
performs :retract? # Generates `retract_later?` which calls `retract?`.
|
|
227
|
+
|
|
228
|
+
def publish!
|
|
229
|
+
…
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def retract?
|
|
233
|
+
…
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Private methods
|
|
239
|
+
|
|
240
|
+
`ActiveJob::Performs` also works with private methods in case you only want to expose the generated `_later` method.
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
class Post < ActiveRecord::Base
|
|
244
|
+
performs :publish # Generates the public `publish_later` instance method.
|
|
245
|
+
|
|
246
|
+
# Private implementation, only call `publish_later` please!
|
|
247
|
+
private def publish
|
|
248
|
+
…
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Additionally, in case the job is meant to be internal to the object, `performs :some_method` returns `:some_method_later` which you can pass to `private`.
|
|
254
|
+
|
|
255
|
+
E.g. `private performs :some_method` will generate a private `some_method_later` method.
|
|
256
|
+
|
|
257
|
+
#### Overriding the generated instance `_later` method
|
|
258
|
+
|
|
259
|
+
The instance level `_later` methods, like `publish_later` above, are generated into an included module. So in case you have a condition where you'd like to prevent the enqueue, you can override the method and call `super`:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
class Post < ApplicationRecord
|
|
263
|
+
performs def publish
|
|
264
|
+
# …
|
|
265
|
+
end
|
|
266
|
+
def publish_later = some_condition? && super
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Usage with `ActiveRecord::AssociatedObject`
|
|
271
|
+
|
|
272
|
+
The [`ActiveRecord::AssociatedObject`](https://github.com/kaspth/active_record-associated_object) gem also implements `GlobalID::Identification`, so you use `performs` exactly like you would on Active Records:
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
class Post::Publisher < ActiveRecord::AssociatedObject
|
|
276
|
+
extend ActiveJob::Performs # We technically auto-extend ActiveRecord::AssociatedObject, but other object hierarchies need this.
|
|
277
|
+
|
|
278
|
+
performs queue_as: :important
|
|
279
|
+
performs :publish
|
|
280
|
+
performs :retract
|
|
281
|
+
|
|
282
|
+
def publish
|
|
283
|
+
…
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def retract(reason:)
|
|
287
|
+
…
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> [!NOTE]
|
|
293
|
+
> There's one difference with Active Record: you must pass in a set to `_later_bulk` methods. Like so:
|
|
294
|
+
>
|
|
295
|
+
> `Post::Publisher.publish_later_bulk Post::Publisher.first(10)`
|
|
296
|
+
|
|
297
|
+
### Passing `wait` to `performs`
|
|
298
|
+
|
|
299
|
+
If there's a job you want to defer, `performs` can set it for each invocation:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
class Post < ActiveRecord::Base
|
|
303
|
+
mattr_reader :config, default: Rails.application.config_for(:posts)
|
|
304
|
+
|
|
305
|
+
performs :social_media_boost, wait: config.social_media_boost_after
|
|
306
|
+
performs :social_media_boost, wait: 5.minutes # Alternatively, this works too.
|
|
307
|
+
|
|
308
|
+
# Additionally, a block can be passed to have access to the `post`:
|
|
309
|
+
performs :social_media_boost, wait: -> post { post.social_media_boost_grace_period }
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Now, `social_media_boost_later` can be called immediately, but automatically run after the grace period.
|
|
314
|
+
|
|
315
|
+
`wait_until` is also supported:
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
class Post < ActiveRecord::Base
|
|
319
|
+
performs :publish, wait_until: -> post { Date.tomorrow.noon if post.graceful? }
|
|
320
|
+
end
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Installation
|
|
324
|
+
|
|
325
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
326
|
+
|
|
327
|
+
$ bundle add active_job-performs
|
|
328
|
+
|
|
329
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
330
|
+
|
|
331
|
+
$ gem install active_job-performs
|
|
332
|
+
|
|
333
|
+
## Development
|
|
334
|
+
|
|
335
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
336
|
+
|
|
337
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kaspth/active_job-performs.
|
|
342
|
+
|
|
343
|
+
## License
|
|
344
|
+
|
|
345
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "performs/version"
|
|
4
|
+
|
|
5
|
+
module ActiveJob; end
|
|
6
|
+
module ActiveJob::Performs
|
|
7
|
+
module Waiting
|
|
8
|
+
attr_reader :wait, :wait_until
|
|
9
|
+
|
|
10
|
+
def wait=(value)
|
|
11
|
+
@wait = value.respond_to?(:call) ? value : proc { value }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def wait_until=(value)
|
|
15
|
+
@wait_until = value.respond_to?(:call) ? value : proc { value }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def scoped_by_wait(record)
|
|
19
|
+
if waits = { wait: wait&.call(record), wait_until: wait_until&.call(record) }.compact and waits.any?
|
|
20
|
+
set(waits)
|
|
21
|
+
else
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def performs(method = nil, **configs, &block)
|
|
28
|
+
@job ||= safe_define("Job") { ApplicationJob }.tap { _1.extend Waiting }
|
|
29
|
+
|
|
30
|
+
if method.nil?
|
|
31
|
+
apply_performs_to(@job, **configs, &block)
|
|
32
|
+
else
|
|
33
|
+
method = method.to_s.dup
|
|
34
|
+
suffix = $1 if method.gsub!(/([!?])$/, "")
|
|
35
|
+
|
|
36
|
+
job = safe_define("#{method}_job".classify) { @job }
|
|
37
|
+
apply_performs_to(job, **configs, &block)
|
|
38
|
+
|
|
39
|
+
job.class_eval <<~RUBY, __FILE__, __LINE__ + 1 unless job.instance_method(:perform).owner == job
|
|
40
|
+
def perform(object, *arguments, **options)
|
|
41
|
+
object.send(:#{method}#{suffix}, *arguments, **options)
|
|
42
|
+
end
|
|
43
|
+
RUBY
|
|
44
|
+
|
|
45
|
+
if ActiveJob.respond_to?(:perform_all_later)
|
|
46
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
47
|
+
def self.#{method}_later_bulk#{suffix}(set#{" = all" if respond_to?(:all)})
|
|
48
|
+
ActiveJob.perform_all_later set.map { #{job}.scoped_by_wait(_1).new(_1) }
|
|
49
|
+
end
|
|
50
|
+
RUBY
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
performs_later_methods.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
54
|
+
def #{method}_later#{suffix}(*arguments, **options)
|
|
55
|
+
#{job}.scoped_by_wait(self).perform_later(self, *arguments, **options)
|
|
56
|
+
end
|
|
57
|
+
RUBY
|
|
58
|
+
|
|
59
|
+
:"#{method}_later#{suffix}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
def safe_define(name)
|
|
65
|
+
name.safe_constantize || const_set(name, Class.new(yield))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def apply_performs_to(job, **configs, &block)
|
|
69
|
+
job.class_exec(&block) if block_given?
|
|
70
|
+
|
|
71
|
+
configs.each do |name, value|
|
|
72
|
+
name = "#{name}=".then.find { job.respond_to? _1 } || name
|
|
73
|
+
job.public_send name, value
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def performs_later_methods
|
|
78
|
+
@performs_later_methods ||= Module.new.tap { include _1 }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
ActiveSupport.on_load(:active_record) { extend ActiveJob::Performs }
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: performs
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kasper Timm Hansen
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activejob
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.1'
|
|
26
|
+
email:
|
|
27
|
+
- hey@kaspth.com
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- CHANGELOG.md
|
|
33
|
+
- Gemfile
|
|
34
|
+
- Gemfile.lock
|
|
35
|
+
- LICENSE.txt
|
|
36
|
+
- README.md
|
|
37
|
+
- Rakefile
|
|
38
|
+
- lib/active_job/performs.rb
|
|
39
|
+
- lib/active_job/performs/version.rb
|
|
40
|
+
homepage: https://github.com/claudiob/active_job-performs
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
metadata:
|
|
44
|
+
homepage_uri: https://github.com/claudiob/active_job-performs
|
|
45
|
+
source_code_uri: https://github.com/claudiob/active_job-performs
|
|
46
|
+
changelog_uri: https://github.com/claudiob/active_job-performs/blob/main/CHANGELOG.md
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 3.0.0
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 3.6.9
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: ActiveJob::Performs adds the `performs` macro to set up jobs by convention.
|
|
64
|
+
test_files: []
|