delayable 0.4.1 → 0.5.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: c001bbe027f6b3996e3fc497c5b72a7e32a9f53fca0a6a6f4f04c928231289bc
4
- data.tar.gz: 9978c0fa7bce102c9105abfb72d54f674e4e8795fc0331a4181d776ef4e92dbd
3
+ metadata.gz: fbc0e3130501e50157a5dd2807576126022d145f89a3a005eb596b41c3456ecf
4
+ data.tar.gz: 729e2cf6548e11b220d7f8f19da46e55c1e520de3046ced844c5b858e4b1b2e5
5
5
  SHA512:
6
- metadata.gz: ab775ae0fe6c43a1e9c59547f8837c80240d5c5ddda4cef7e7f404496b3f89dde80c838bce19350837ef7ed23eb6cf11a4f5e0741a6dc568e30a1b5fccea0964
7
- data.tar.gz: 30e3510fa4f70edb0400fa469ada55255bf9552d6302d490b36b2878994ad58c3199b5d7ef1af86fe17dd0e3a1e3689edda6f7bd1a814de12fec95c30588474f
6
+ metadata.gz: 3bd0b541a0d948c9c320a0ed7caf4e8f5585a147ed551c7e1b1c397c119cf4a23969ac510888c0f985e1d7c00997fac896fbdc3aa850d9ba0dc0865670336bf2
7
+ data.tar.gz: c81b5c272398ea577e9030852410a7813a8abcad24d7ec3ea3843e36b1529a0dfd79647d80cfe12edc093dc298ea2963c45680894376405ee15d4c8a94e58fa0
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.5.0 - 2026-08-03
6
+
7
+ ### Added
8
+
9
+ - Added class-level `_all_later` helpers for delayed instance methods.
10
+ - Added bulk enqueue support through `ActiveJob.perform_all_later`, with individual enqueue fallback for Rails 7.0 and adapters without bulk support.
data/README.md CHANGED
@@ -1,10 +1,19 @@
1
1
  # Delayable
2
- Short description and motivation.
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ A Ruby gem that makes it easy to convert any method call into a background job using ActiveJob. Simply include the `Delayable` module and use the `delay` class method to mark methods that should be available for background execution.
4
+
5
+ ## Features
6
+
7
+ - **Easy Background Jobs**: Convert any instance or class method into a background job with minimal setup
8
+ - **Current Attributes Preservation**: Automatically preserves Rails `Current` attributes across job boundaries
9
+ - **Flexible Configuration**: Configure queue names, delays, and concurrency limits per method
10
+ - **Method Parameter Support**: Full support for regular arguments, keyword arguments, and blocks
11
+ - **Bang Method Handling**: Automatic support for methods ending with `!`
12
+ - **Bulk Enqueuing**: Enqueue the same instance method for many receivers at once
13
+ - **ActiveJob Integration**: Seamlessly integrates with your existing ActiveJob setup
6
14
 
7
15
  ## Installation
16
+
8
17
  Add this line to your application's Gemfile:
9
18
 
10
19
  ```ruby
@@ -13,7 +22,7 @@ gem "delayable"
13
22
 
14
23
  And then execute:
15
24
  ```bash
16
- $ bundle
25
+ $ bundle install
17
26
  ```
18
27
 
19
28
  Or install it yourself as:
@@ -21,8 +30,463 @@ Or install it yourself as:
21
30
  $ gem install delayable
22
31
  ```
23
32
 
33
+ ## Prerequisites
34
+
35
+ Before using Delayable, ensure you have the following defined in your Rails application:
36
+
37
+ 1. **ApplicationJob**: Your base job class
38
+ 2. **Current**: A CurrentAttributes class for maintaining context
39
+
40
+ ```ruby
41
+ # app/jobs/application_job.rb
42
+ class ApplicationJob < ActiveJob::Base
43
+ # Your job configuration
44
+ end
45
+
46
+ # app/models/current.rb
47
+ class Current < ActiveSupport::CurrentAttributes
48
+ attribute :user, :request_id, :user_agent
49
+ # Add other attributes as needed
50
+ end
51
+ ```
52
+
53
+ ## Basic Usage
54
+
55
+ ### Instance Methods
56
+
57
+ Include `Delayable` in your class and use the `delay` method to mark methods for background execution:
58
+
59
+ ```ruby
60
+ class DataProcessor
61
+ include Delayable
62
+
63
+ def process_csv_import(file_path, user_id)
64
+ user = User.find(user_id)
65
+ CSV.foreach(file_path, headers: true) do |row|
66
+ user.records.create!(row.to_h)
67
+ end
68
+ end
69
+ delay :process_csv_import
70
+
71
+ def sync_external_data(api_endpoint, last_sync_time)
72
+ # Fetch and process data from external API
73
+ ExternalApiClient.new(api_endpoint).sync_data_since(last_sync_time)
74
+ end
75
+ delay :sync_external_data
76
+ end
77
+
78
+ # Usage
79
+ processor = DataProcessor.new
80
+ processor.process_csv_import_later("/tmp/users.csv", 123)
81
+ processor.sync_external_data_later("https://api.example.com/data", 1.hour.ago)
82
+ ```
83
+
84
+ ### Bulk Instance Methods
85
+
86
+ Every delayed instance method also gets a class-level `_all_later` helper. It creates one job per receiver and passes the same positional and keyword arguments to each:
87
+
88
+ ```ruby
89
+ class Unit
90
+ include Delayable
91
+
92
+ def sync_channex_availability(date_from, date_to, channel:)
93
+ # Sync availability for this unit
94
+ end
95
+ delay :sync_channex_availability, queue: :integrations
96
+ end
97
+
98
+ Unit.sync_channex_availability_all_later(
99
+ units,
100
+ Date.current,
101
+ 1.year.from_now.to_date,
102
+ channel: :channex
103
+ )
104
+ ```
105
+
106
+ The helper preserves `Current.attributes` and all generated job configuration. A configured wait applies to every job and can be overridden per bulk call:
107
+
108
+ ```ruby
109
+ Unit.sync_channex_availability_all_later(
110
+ units,
111
+ date_from,
112
+ date_to,
113
+ channel: :channex,
114
+ wait: 30.seconds
115
+ )
116
+ ```
117
+
118
+ Bang methods use `_all_later!`, such as `Unit.refresh_all_later!(units)`. Delayable does not remove duplicate receivers; use `units.uniq` when needed. An empty collection returns without enqueuing anything.
119
+
120
+ Bulk helpers are only generated for instance methods. Delayed class methods still use their regular `_later` helper.
121
+
122
+ Delayable uses `ActiveJob.perform_all_later` when available (Rails 7.1+). A true bulk queue operation requires an ActiveJob adapter that implements `enqueue_all`; otherwise ActiveJob enqueues each job individually. On Rails 7.0, Delayable also falls back to individual enqueues.
123
+
124
+ ### Class Methods
125
+
126
+ You can also delay class methods by setting the `class_method: true` option:
127
+
128
+ ```ruby
129
+ class ReportGenerator
130
+ include Delayable
131
+
132
+ def self.generate_monthly_report(month, year)
133
+ # Generate report logic
134
+ end
135
+ delay :generate_monthly_report, class_method: true
136
+
137
+ def self.cleanup_old_reports!
138
+ # Cleanup logic
139
+ end
140
+ delay :cleanup_old_reports!, class_method: true
141
+ end
142
+
143
+ # Usage
144
+ ReportGenerator.generate_monthly_report_later(12, 2023)
145
+ ReportGenerator.cleanup_old_reports_later!
146
+ ```
147
+
148
+ ### Methods with Bang (!)
149
+
150
+ Delayable automatically handles methods ending with `!`:
151
+
152
+ ```ruby
153
+ class DataProcessor
154
+ include Delayable
155
+
156
+ def process_data!
157
+ # Processing logic
158
+ end
159
+ delay :process_data!
160
+ end
161
+
162
+ # Usage - note the bang is preserved in the delayed method name
163
+ processor = DataProcessor.new
164
+ processor.process_data_later! # Creates ProcessDataBangJob
165
+ ```
166
+
167
+ ## Advanced Configuration
168
+
169
+ ### Queue Configuration
170
+
171
+ Specify which queue to use for the background job:
172
+
173
+ ```ruby
174
+ class DataProcessor
175
+ include Delayable
176
+
177
+ def process_csv_import(file_path)
178
+ # Large CSV processing logic
179
+ end
180
+ delay :process_csv_import, queue: :data_processing
181
+
182
+ def sync_external_api(endpoint)
183
+ # API synchronization logic
184
+ end
185
+ delay :sync_external_api, queue: :integrations
186
+ end
187
+ ```
188
+
189
+ ### Delay Execution
190
+
191
+ Set a default delay for job execution:
192
+
193
+ ```ruby
194
+ class CacheManager
195
+ include Delayable
196
+
197
+ def warm_cache(cache_key)
198
+ # Pre-populate cache logic
199
+ end
200
+ delay :warm_cache, wait: 5.minutes
201
+
202
+ def cleanup_expired_entries
203
+ # Cache cleanup logic
204
+ end
205
+ delay :cleanup_expired_entries, wait: 1.hour
206
+ end
207
+
208
+ # You can also override the delay at call time
209
+ manager = CacheManager.new
210
+ manager.warm_cache_later("user_stats", wait: 30.seconds) # Overrides the 5.minutes default
211
+ ```
212
+
213
+ ### Concurrency Limits
214
+
215
+ Limit how many jobs of this type can run concurrently using SolidQueue's concurrency controls:
216
+
217
+ ```ruby
218
+ class ResourceIntensiveTask
219
+ include Delayable
220
+
221
+ def process_large_file(file_path)
222
+ # Heavy processing that should be limited
223
+ end
224
+ delay :process_large_file, limits_concurrency: { to: 2 }
225
+
226
+ def generate_report(report_type)
227
+ # Report generation that should run one at a time per report type
228
+ end
229
+ delay :generate_report, limits_concurrency: {
230
+ to: 1,
231
+ key: ->(run_data, report_type) { "report_#{report_type}" }
232
+ }
233
+
234
+ def process_batch(batch_id, options = {})
235
+ # Process a batch with concurrency control per batch
236
+ end
237
+ delay :process_batch, limits_concurrency: {
238
+ to: 3,
239
+ key: ->(_run_data, batch_id) { "batch_#{batch_id}" }
240
+ }
241
+ end
242
+ ```
243
+
244
+ **Important:** When using the `key` proc with `limits_concurrency`, the proc receives the delayed job arguments. The first argument is Delayable's internal `run_data` hash (`current_attributes` and `record`), followed by the arguments passed to your delayed method.
245
+
246
+ **Note:** The `limits_concurrency` feature requires SolidQueue as your ActiveJob backend. Other job backends (Sidekiq, Resque, etc.) do not support this feature and will ignore the setting.
247
+
248
+ ## Current Attributes
249
+
250
+ Delayable automatically preserves Rails `Current` attributes across job boundaries:
251
+
252
+ ```ruby
253
+ class AuditLogger
254
+ include Delayable
255
+
256
+ def log_action(action, resource_id)
257
+ AuditLog.create!(
258
+ action: action,
259
+ resource_id: resource_id,
260
+ user: Current.user, # This will be preserved from the original request
261
+ request_id: Current.request_id
262
+ )
263
+ end
264
+ delay :log_action
265
+ end
266
+
267
+ # In a controller
268
+ class PostsController < ApplicationController
269
+ def create
270
+ Current.user = current_user
271
+ Current.request_id = request.id
272
+
273
+ post = Post.create!(post_params)
274
+
275
+ # The job will have access to Current.user and Current.request_id
276
+ AuditLogger.new.log_action_later("create", post.id)
277
+ end
278
+ end
279
+ ```
280
+
281
+ ## Working with ActiveRecord Models
282
+
283
+ Delayable works seamlessly with ActiveRecord models:
284
+
285
+ ```ruby
286
+ class User < ApplicationRecord
287
+ include Delayable
288
+
289
+ def update_profile_completeness
290
+ score = calculate_completeness_score
291
+ update!(profile_completeness: score)
292
+ end
293
+ delay :update_profile_completeness
294
+
295
+ def calculate_metrics!
296
+ update!(
297
+ total_posts: posts.count,
298
+ total_comments: comments.count,
299
+ last_activity: Time.current
300
+ )
301
+ end
302
+ delay :calculate_metrics!
303
+
304
+ def archive_old_data!
305
+ # Archive user's old posts, comments, etc.
306
+ posts.where('created_at < ?', 1.year.ago).update_all(archived: true)
307
+ comments.where('created_at < ?', 1.year.ago).delete_all
308
+ end
309
+ delay :archive_old_data!
310
+ end
311
+
312
+ # Usage
313
+ user = User.find(123)
314
+ user.update_profile_completeness_later
315
+ user.calculate_metrics_later!
316
+ user.archive_old_data_later!
317
+ ```
318
+
319
+ ## Job Naming Convention
320
+
321
+ Delayable automatically generates job class names based on your method names to ensure logging and debugging remain clear and meaningful:
322
+
323
+ | Method Name | Method Type | Generated Job Class |
324
+ |-------------|-------------|-------------------|
325
+ | `process_data` | instance | `YourClass::ProcessDataJob` |
326
+ | `process_data!` | instance | `YourClass::ProcessDataBangJob` |
327
+ | `generate_report` | class | `YourClass::ClassGenerateReportJob` |
328
+ | `cleanup!` | class | `YourClass::ClassCleanupBangJob` |
329
+
330
+ This naming convention ensures that when you're monitoring job queues, reading logs, or debugging failed jobs, you can immediately understand what method was being executed and in what context. The job name directly corresponds to your original method, making it easy to trace issues back to your application code.
331
+
332
+ ## Error Handling
333
+
334
+ Since Delayable creates standard ActiveJob jobs, you can use all the standard ActiveJob error handling mechanisms. Put defaults on `ApplicationJob`, or pass a block to `delay` to configure the generated job class:
335
+
336
+ ```ruby
337
+ # In your ApplicationJob
338
+ class ApplicationJob < ActiveJob::Base
339
+ retry_on StandardError, wait: :exponentially_longer, attempts: 5
340
+ discard_on ActiveRecord::RecordNotFound
341
+ end
342
+
343
+ class BookingSync
344
+ include Delayable
345
+
346
+ def sync_channex_bookings
347
+ # Sync bookings
348
+ end
349
+ delay :sync_channex_bookings,
350
+ limits_concurrency: {
351
+ to: 1,
352
+ key: ->(run_data) { "#{run_data[:record].to_global_id}-channex-bookings" }
353
+ } do
354
+ retry_on Channex::Api::ConnectionError,
355
+ wait: :polynomially_longer,
356
+ attempts: 5
357
+ end
358
+ end
359
+ ```
360
+
361
+ ## Testing
362
+
363
+ Delayable integrates with ActiveJob's testing helpers:
364
+
365
+ ```ruby
366
+ require 'test_helper'
367
+
368
+ class DataProcessorTest < ActiveSupport::TestCase
369
+ include ActiveJob::TestHelper
370
+
371
+ test "CSV import is enqueued" do
372
+ processor = DataProcessor.new
373
+
374
+ assert_enqueued_jobs 1, only: DataProcessor::ProcessCsvImportJob do
375
+ processor.process_csv_import_later("/tmp/data.csv", 123)
376
+ end
377
+ end
378
+
379
+ test "CSV data is imported" do
380
+ processor = DataProcessor.new
381
+
382
+ perform_enqueued_jobs do
383
+ processor.process_csv_import_later("/tmp/data.csv", 123)
384
+ end
385
+
386
+ # Assert data was imported
387
+ user = User.find(123)
388
+ assert user.records.count > 0
389
+ end
390
+
391
+ test "current attributes are preserved" do
392
+ Current.user = users(:admin)
393
+
394
+ perform_enqueued_jobs do
395
+ AuditLogger.new.log_action_later("test", 123)
396
+ end
397
+
398
+ log = AuditLog.last
399
+ assert_equal users(:admin), log.user
400
+ end
401
+ end
402
+ ```
403
+
404
+ ## Best Practices
405
+
406
+ 1. **Keep Jobs Idempotent**: Design your delayed methods to be safely retried
407
+ 2. **Use Appropriate Queues**: Separate different types of work into different queues
408
+ 3. **Handle Failures Gracefully**: Use ActiveJob's retry and error handling features
409
+ 4. **Monitor Job Performance**: Use tools like Sidekiq's web UI or similar for your job backend
410
+ 5. **Test Background Behavior**: Always test both the enqueuing and execution of your jobs
411
+
412
+ ## Configuration Examples
413
+
414
+ ### Multiple Options Combined
415
+
416
+ ```ruby
417
+ class ComplexProcessor
418
+ include Delayable
419
+
420
+ def process_batch(batch_id, options = {})
421
+ # Complex processing logic
422
+ end
423
+ delay :process_batch,
424
+ queue: :batch_processing,
425
+ wait: 30.seconds,
426
+ limits_concurrency: { to: 3, key: ->(_run_data, batch_id) { "batch_#{batch_id}" } }
427
+ end
428
+ ```
429
+
430
+ ### Dynamic Delays
431
+
432
+ ```ruby
433
+ class NotificationSender
434
+ include Delayable
435
+
436
+ def send_reminder(user_id, urgency = :normal)
437
+ # Send reminder logic
438
+ end
439
+ delay :send_reminder
440
+
441
+ def send_reminder_with_delay(user_id, urgency = :normal)
442
+ delay_time = case urgency
443
+ when :urgent then 0.seconds
444
+ when :normal then 1.hour
445
+ when :low then 1.day
446
+ end
447
+
448
+ send_reminder_later(user_id, urgency, wait: delay_time)
449
+ end
450
+ end
451
+ ```
452
+
453
+ ## Troubleshooting
454
+
455
+ ### Debugging
456
+
457
+ To see what jobs are being created, you can inspect the generated job classes:
458
+
459
+ ```ruby
460
+ # In rails console
461
+ DataProcessor::ProcessCsvImportJob.new.class.name
462
+ # => "DataProcessor::ProcessCsvImportJob"
463
+
464
+ DataProcessor::ProcessCsvImportJob.queue_name
465
+ # => "default"
466
+ ```
467
+
468
+ ## Requirements
469
+
470
+ - Ruby >= 3.4.0
471
+ - Rails >= 7.0.4.3
472
+ - ActiveJob configured with a backend (Sidekiq, Resque, etc.)
473
+ - An ActiveJob adapter with `enqueue_all` support for a true bulk queue operation (optional)
474
+ - **SolidQueue** (optional) - Required only if using `limits_concurrency` feature
475
+
24
476
  ## Contributing
25
- Contribution directions go here.
477
+
478
+ 1. Fork the repository
479
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
480
+ 3. Add tests for your changes
481
+ 4. Make sure all tests pass (`bundle exec rake test`)
482
+ 5. Commit your changes (`git commit -am 'Add some feature'`)
483
+ 6. Push to the branch (`git push origin my-new-feature`)
484
+ 7. Create a new Pull Request
26
485
 
27
486
  ## License
487
+
28
488
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
489
+
490
+ ## Changelog
491
+
492
+ See [CHANGELOG.md](https://github.com/trobrock/delayable/CHANGELOG.md) for details about changes in each version.
@@ -1,3 +1,3 @@
1
1
  module Delayable
2
- VERSION = '0.4.1'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
data/lib/delayable.rb CHANGED
@@ -12,28 +12,54 @@ module Delayable
12
12
  end
13
13
 
14
14
  class_methods do
15
- def delay(method_name, class_method: false, wait: nil, limits_concurrency: nil, queue: :default)
15
+ def delay(method_name, class_method: false, wait: nil, limits_concurrency: nil, queue: :default, &job_configuration)
16
16
  job = const_set(
17
17
  delayable_job_name(method_name, class_method:),
18
- delayable_job_class(method_name, class_method:, concurrency_limits: limits_concurrency, queue:)
18
+ delayable_job_class(
19
+ method_name,
20
+ class_method:,
21
+ concurrency_limits: limits_concurrency,
22
+ queue:,
23
+ &job_configuration
24
+ )
19
25
  )
20
26
 
21
27
  defined_wait = wait
28
+ build_job = method(:delayable_build_job)
29
+ extract_wait = method(:delayable_extract_wait)
30
+ enqueue_jobs = method(:delayable_enqueue_jobs)
22
31
  delayable_define_method(class_method:, method_name:) do |*arguments, **kwargs|
23
- wait = kwargs.key?(:wait) ? kwargs.delete(:wait) : defined_wait
24
- run_data = { current_attributes: Current.attributes, record: self }
25
- job.set(wait:).perform_later(run_data, *arguments, **kwargs)
32
+ job_wait = extract_wait.call(kwargs, defined_wait)
33
+ build_job.call(job, self, job_wait, arguments, kwargs, enqueue: true)
34
+ end
35
+
36
+ return if class_method
37
+
38
+ delayable_define_bulk_method(method_name) do |receivers, *arguments, **kwargs|
39
+ receivers = receivers.to_a
40
+ next if receivers.empty?
41
+
42
+ job_wait = extract_wait.call(kwargs, defined_wait)
43
+ jobs = receivers.map { |receiver| build_job.call(job, receiver, job_wait, arguments, kwargs) }
44
+ enqueue_jobs.call(jobs)
26
45
  end
27
46
  end
28
47
 
29
48
  private
30
49
 
31
- def delayable_job_class(method_name, class_method: false, concurrency_limits: nil, queue: :default)
50
+ def delayable_job_class(
51
+ method_name,
52
+ class_method: false,
53
+ concurrency_limits: nil,
54
+ queue: :default,
55
+ &job_configuration
56
+ )
32
57
  klass = self
33
58
  Class.new(ApplicationJob) do
34
59
  queue_as queue
35
60
 
36
- limits_concurrency(**concurrency_limits) if concurrency_limits.is_a?(Hash)
61
+ limits_concurrency(**concurrency_limits) if concurrency_limits.is_a?(Hash) && respond_to?(:limits_concurrency)
62
+ class_eval(&job_configuration) if job_configuration
37
63
 
38
64
  define_method(:perform) do |run_data = {}, *arguments, **kwargs|
39
65
  Current.set(run_data[:current_attributes] || {}) do
@@ -43,6 +69,24 @@ module Delayable
43
69
  end
44
70
  end
45
71
 
72
+ def delayable_build_job(job, receiver, wait, arguments, kwargs, enqueue: false)
73
+ run_data = { current_attributes: Current.attributes, record: receiver }
74
+ return job.set(wait:).perform_later(run_data, *arguments, **kwargs) if enqueue
75
+
76
+ job.new(run_data, *arguments, **kwargs).set(wait:)
77
+ end
78
+
79
+ def delayable_extract_wait(kwargs, defined_wait)
80
+ kwargs.key?(:wait) ? kwargs.delete(:wait) : defined_wait
81
+ end
82
+
83
+ def delayable_enqueue_jobs(jobs)
84
+ return ActiveJob.perform_all_later(*jobs) if ActiveJob.respond_to?(:perform_all_later)
85
+
86
+ jobs.each(&:enqueue)
87
+ nil
88
+ end
89
+
46
90
  def delayable_job_name(method_name, class_method: false)
47
91
  class_name = bang_method?(method_name) ? "#{method_name.to_s.chomp('!').camelize}Bang" : method_name.to_s.camelize
48
92
  :"#{'Class' if class_method}#{class_name}Job"
@@ -50,8 +94,16 @@ module Delayable
50
94
 
51
95
  def delayable_define_method(class_method:, method_name:, &)
52
96
  define_method_type = class_method ? :define_singleton_method : :define_method
53
- method_name = bang_method?(method_name) ? "#{method_name.to_s.chomp('!')}_later!" : "#{method_name}_later"
54
- public_send(define_method_type, method_name, &)
97
+ public_send(define_method_type, delayable_method_name(method_name), &)
98
+ end
99
+
100
+ def delayable_define_bulk_method(method_name, &)
101
+ define_singleton_method(delayable_method_name(method_name, bulk: true), &)
102
+ end
103
+
104
+ def delayable_method_name(method_name, bulk: false)
105
+ suffix = bulk ? '_all_later' : '_later'
106
+ bang_method?(method_name) ? "#{method_name.to_s.chomp('!')}#{suffix}!" : "#{method_name}#{suffix}"
55
107
  end
56
108
 
57
109
  def bang_method?(method_name)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Trae Robrock
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-25 00:00:00.000000000 Z
10
+ date: 2026-08-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -33,6 +33,7 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - ".rubocop.yml"
35
35
  - ".ruby-version"
36
+ - CHANGELOG.md
36
37
  - MIT-LICENSE
37
38
  - README.md
38
39
  - Rakefile