readymade 0.4.3 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76b10ba5747f0f419f053e590e091190bac60bbf3abf7b56b6281d39a65206c5
4
- data.tar.gz: eb540a076609b60cff82538e0cb99273bdde57919efd4d29f9f1f681b39b15e6
3
+ metadata.gz: 7b3d5fedc87f2028115618494f4b1e4af9cb79fe9f82f7059bb9bb4d1705b3da
4
+ data.tar.gz: 841cbdc4364c34a52706011db59158a041a5edd575db5083133b1eb2faffb6ee
5
5
  SHA512:
6
- metadata.gz: 2eda9e59123b8730797c9fcefaa093bc9b1bcac092a5299acd7d40022c95567fd52df3981ed1e07615647f97513a56f36fd6ebfb7ab5c5dbb3b5e1ff42ddf5d8
7
- data.tar.gz: d32d324a671e0104e91edd6e5a6e06147fe47fe34c6a5e2beb6b9f698ff5713be6775196e27e7c275f2f1be7e470c09b2d3b0852252ac56522037b1cb43b6881
6
+ metadata.gz: 6251e1a6304385b8bdd5d421d300c4d92ff24d21d374b18430938f6ebbf65c59cef58d3709da984b3f3e71945cb13fc6e6cedd0fa7235083d70e20dfb6cef095
7
+ data.tar.gz: 2bbdb741c5b1bef74ad7e56efb946cf24675b25a17490fcee416ec7f49bd8ee4fdf3f601f9d44d345f1ad55f452f929114ff2df2d80aa34cfe299e162dab6cdb
data/.gitignore CHANGED
@@ -6,7 +6,10 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .idea/
10
+ .byebug_history
9
11
 
10
12
  # rspec failure tracking
11
13
  .rspec_status
12
14
  spec/.DS_Store
15
+
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+
5
+ ## [0.4.4] - 2025-09-22
6
+
7
+ * Add initializer generator
8
+ * Add `ActiveJob::Uniqueness` support
9
+
10
+ ## [0.4.3] - 2025-07-31
11
+
12
+ * Remove job_options.discard_on` argument to `Readymade::BackgroundJob` to discard job on specific exceptions due to incorrect behaviour on different Rails versions
13
+
4
14
  ## [0.4.2] - 2025-05-26
5
15
 
6
16
  * Add `job_options.discard_on` argument to `Readymade::BackgroundJob` to discard job on specific exceptions
data/Gemfile.lock CHANGED
@@ -1,19 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- readymade (0.4.3)
4
+ readymade (0.4.4)
5
5
  activejob
6
6
  activemodel
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activejob (8.0.2)
12
- activesupport (= 8.0.2)
11
+ activejob (8.0.2.1)
12
+ activesupport (= 8.0.2.1)
13
13
  globalid (>= 0.3.6)
14
- activemodel (8.0.2)
15
- activesupport (= 8.0.2)
16
- activesupport (8.0.2)
14
+ activemodel (8.0.2.1)
15
+ activesupport (= 8.0.2.1)
16
+ activesupport (8.0.2.1)
17
17
  base64
18
18
  benchmark (>= 0.3)
19
19
  bigdecimal
@@ -28,10 +28,10 @@ GEM
28
28
  uri (>= 0.13.1)
29
29
  base64 (0.3.0)
30
30
  benchmark (0.4.1)
31
- bigdecimal (3.2.2)
31
+ bigdecimal (3.2.3)
32
32
  byebug (11.1.3)
33
33
  concurrent-ruby (1.3.5)
34
- connection_pool (2.5.3)
34
+ connection_pool (2.5.4)
35
35
  diff-lcs (1.4.4)
36
36
  drb (2.2.3)
37
37
  globalid (1.2.1)
data/README.md CHANGED
@@ -6,6 +6,7 @@ This gems contains basic components to follow [ABDI architecture](https://github
6
6
 
7
7
  ### Tested with ruby:
8
8
 
9
+ - 3.3
9
10
  - 3.1
10
11
  - 3.0
11
12
  - 2.7
@@ -35,6 +36,8 @@ Inherit your components from:
35
36
  * `Readymade::Action`
36
37
  * `Readymade::Operation`
37
38
 
39
+ ---
40
+
38
41
  ### Readymade::Response
39
42
 
40
43
  ```ruby
@@ -47,6 +50,8 @@ response.status # 'fail'
47
50
  response.data # { errors: { some: 'errors' } }
48
51
  ```
49
52
 
53
+ ---
54
+
50
55
  ### Readymade::Form
51
56
 
52
57
  Check more form features examples in `lib/readymade/form.rb`
@@ -73,7 +78,7 @@ order_form.valid? # true
73
78
  class MyForm < Readymade::Form
74
79
  PERMITTED_ATTRIBUTES = %i[email name category country]
75
80
  REQUIRED_ATTRIBUTES = %i[email]
76
-
81
+
77
82
  def form_options
78
83
  {
79
84
  categories: args[:company].categories,
@@ -100,6 +105,8 @@ end
100
105
  = f.text_field :name, required: @form.required?(:name) # false
101
106
  ```
102
107
 
108
+ ---
109
+
103
110
  ### Readymade::InstantForm
104
111
 
105
112
  Permit params and validates presence inline
@@ -108,6 +115,8 @@ Permit params and validates presence inline
108
115
  Readymade::InstantForm.new(my_params, permitted: %i[name phone], required: %i[email]) # permits: name, phone, email; validates on presence: email
109
116
  ```
110
117
 
118
+ ---
119
+
111
120
  ### Readymade::Action
112
121
 
113
122
  ```ruby
@@ -146,7 +155,7 @@ class Orders::Actions::SendNotifications < Readymade::Action
146
155
  end
147
156
 
148
157
  Orders::Actions::SendNotifications.call_async(order: order)
149
- Orders::Actions::SendNotifications.call_async!(order: order, job_options: { queue_as: :my_queue, discard_on: ActiveJob::DeserializationError})
158
+ Orders::Actions::SendNotifications.call_async!(order: order, job_options: { queue_as: :my_queue })
150
159
  ```
151
160
 
152
161
  ```ruby
@@ -210,6 +219,53 @@ Orders::Actions::SendNotifications.call_async!(order: order) # job will be faile
210
219
 
211
220
  ```
212
221
 
222
+
223
+ ---
224
+
225
+ ### Locking Duplicate Jobs (triggered by `call_async`)
226
+
227
+ To enable job locking, first generate the initializer:
228
+
229
+ ```bash
230
+ rails generate readymade:install
231
+ ```
232
+
233
+ Add `gem 'activejob-uniqueness', '~> 0.4.0'` to your Gemfile. (not included by default to reduce dependencies)
234
+
235
+ Then follow the instructions inside the generated file.
236
+
237
+
238
+
239
+ #### Default behavior
240
+
241
+ * All `call_async` actions run in the `:default` queue.
242
+ * By default, you can configure Readymade to **lock all jobs in `:default`** so that duplicate jobs won’t be enqueued.
243
+
244
+
245
+ #### Customizing which jobs are locked
246
+
247
+ You have two main strategies:
248
+
249
+ 1. **Use `job_options` in your call**
250
+ If you don’t want a job to be locked, run it in a different queue:
251
+
252
+ ```ruby
253
+ MyAction.call_async(user_id: 42, job_options: { queue_as: :critical })
254
+ ```
255
+
256
+ 2. **Configure specific queues to lock**
257
+ In your initializer, define which queues should enforce locking:
258
+
259
+ ```ruby
260
+ Readymade.configure do |config|
261
+ config.lock_jobs = true
262
+ config.locked_queues = [:mailers, :anatytics] # only these queues will lock duplicates
263
+ end
264
+ ```
265
+
266
+ ---
267
+
268
+
213
269
  ### Readymade::Operation
214
270
 
215
271
  Provides set of help methods like: `build_form`, `form_valid?`, `validation_fail`, `save_record`, etc.
@@ -230,6 +286,8 @@ class Orders::Operations::Create < Readymade::Operation
230
286
  end
231
287
  ```
232
288
 
289
+ ---
290
+
233
291
  ### Readymade::Controller::Serialization
234
292
 
235
293
  ```ruby
@@ -244,6 +302,8 @@ Dependencies that must be installed on your own:
244
302
  - [pagy](https://rubygems.org/gems/pagy)
245
303
  - [api-pagination](https://rubygems.org/gems/api-pagination)
246
304
 
305
+ ---
306
+
247
307
  ### Readymade::Model::ApiAttachable
248
308
 
249
309
  Add base64 attachments format for your models
@@ -274,12 +334,14 @@ let(:avatar) { Rack::Test::UploadedFile.new(Rails.root.join('spec/support/assets
274
334
  let(:params) { { user: attributes_for(:user).merge!(avatar: to_api_file(avatar)) } }
275
335
  ```
276
336
 
337
+ ---
338
+
277
339
  ### Readymade::Model::Filterable
278
340
 
279
341
  ```ruby
280
342
  class User < ApplicationRecord
281
343
  include Readyamde::Model::Filterable
282
-
344
+
283
345
  scope :by_status, ->(status) { where(status: status) }
284
346
  scope :by_role, ->(role) { where(role: role) }
285
347
  end
@@ -290,6 +352,8 @@ User.all.filter_collection({ by_status: 'active', by_role: 'manager' })
290
352
  User.all.filter_collection({ by_status: 'active', by_role: 'manager' }, chain_with: :or) # active OR manager
291
353
  ```
292
354
 
355
+ ---
356
+
293
357
  ### Readymade::Model::ValidatableEnum
294
358
 
295
359
  Instead of raised error when enum value is not valid, it adds error to the record
@@ -310,6 +374,8 @@ user.validate # false
310
374
  user.errors.full_messages # ["Role 'superadmin' is not a valid role", "Status 'archived' is not a valid status"]
311
375
  ```
312
376
 
377
+ ---
378
+
313
379
  ## Development
314
380
 
315
381
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Readymade
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ desc 'Copy Readymade default files'
7
+ source_root File.expand_path('templates', __dir__)
8
+
9
+ def copy_config
10
+ template 'config/initializers/readymade.rb'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ Readymade.configure do |config|
4
+ # Change to true to enable job locking
5
+ config.lock_jobs = false
6
+
7
+ # Define lock strategy
8
+ # Strategy The job is locked The job is unlocked
9
+ #
10
+ # until_executing when pushed to the queue when processing starts
11
+ # until_executed when pushed to the queue when the job is processed successfully
12
+ # until_expired when pushed to the queue when the lock is expired
13
+ # until_and_while_executing when pushed to the queue when processing starts a runtime lock is acquired to prevent simultaneous jobs
14
+ # while_executing when processing starts when the job is processed with any result including an error
15
+
16
+ # config.lock_type = :until_executed
17
+
18
+ # new jobs with the same args will be logged within 1.day or until existing one is being executing
19
+ # config.lock_ttl = 1.day
20
+
21
+ # Array of queues to apply job locking to
22
+ # config.locked_queues = [:default]
23
+ end
@@ -6,6 +6,44 @@ module Readymade
6
6
  class BackgroundJob < ::ActiveJob::Base
7
7
  queue_as { self.arguments[0].dig(:queue_as) || self.arguments[0].dig(:job_options, :queue_as) || :default }
8
8
 
9
+ class << self
10
+ def apply_uniqueness!
11
+ return unless Readymade.config&.lock_jobs?
12
+
13
+ begin
14
+ require "active_job/uniqueness"
15
+
16
+ unique Readymade.config.lock_type,
17
+ lock_ttl: Readymade.config.lock_ttl,
18
+ on_conflict: ->(job) { handle_duplication(job) }
19
+ rescue LoadError
20
+ warn uniqueness_not_loaded_warning
21
+ end
22
+ end
23
+
24
+ def handle_duplication(job)
25
+ return if Readymade.config.locked_queues.include?(job.queue_name.to_sym)
26
+
27
+ ActiveJob::Uniqueness.unlock!(job_class_name: job.class.name)
28
+ end
29
+
30
+ def uniqueness_not_loaded_warning
31
+ <<~MSG
32
+
33
+ ======== READYMADE WARNING ========
34
+
35
+ The `activejob-uniqueness` gem is not installed, but `lock_jobs` is enabled.
36
+ Please add the following to your Gemfile:
37
+
38
+ gem "activejob-uniqueness", "~> 0.4.0"
39
+
40
+ ===================================
41
+
42
+ MSG
43
+ end
44
+
45
+ end
46
+
9
47
  def perform(**args)
10
48
  args.delete(:class_name).to_s.constantize.send(callable_method, **args)
11
49
  end
@@ -17,3 +55,9 @@ module Readymade
17
55
  end
18
56
  end
19
57
  end
58
+
59
+ if defined?(::ActiveSupport)
60
+ ActiveSupport.on_load(:after_initialize) do
61
+ Readymade::BackgroundJob.apply_uniqueness!
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Readymade
4
- VERSION = '0.4.3'
4
+ VERSION = '0.4.4'
5
5
  end
data/lib/readymade.rb CHANGED
@@ -14,5 +14,56 @@ require 'readymade/version'
14
14
 
15
15
  module Readymade
16
16
  class Error < StandardError; end
17
- # Your code goes here...
17
+
18
+ class << self
19
+ attr_accessor :config
20
+
21
+ def configure
22
+ self.config ||= Config.new
23
+ yield(config)
24
+ end
25
+ end
26
+
27
+ class Config
28
+ attr_reader :lock_jobs, :lock_type, :lock_ttl, :locked_queues
29
+
30
+ ALLOWED_LOCK_TYPES = %i[until_executing until_executed until_expired until_and_while_executing while_executing].freeze
31
+
32
+ def initialize
33
+ @lock_jobs = false
34
+ @lock_type = :until_executed
35
+ @lock_ttl = 1.days
36
+ @locked_queues = [:default]
37
+ end
38
+
39
+ def lock_jobs=(bool)
40
+ raise ArgumentError, 'Lock jobs must be a boolean' unless [TrueClass, FalseClass, NilClass].include?(bool.class)
41
+
42
+ @lock_jobs = bool
43
+ end
44
+
45
+ def lock_type=(lock_type)
46
+ raise ArgumentError, 'Lock type must be a symbol' unless lock_type.is_a?(Symbol)
47
+ raise ArgumentError, "Lock type must be one of: #{ALLOWED_LOCK_TYPES}" unless ALLOWED_LOCK_TYPES.include?(lock_type)
48
+
49
+ @lock_type = lock_type
50
+ end
51
+
52
+ def lock_ttl=(ttl)
53
+ raise ArgumentError, 'Lock ttl must be an integer' unless ttl.is_a?(Integer)
54
+
55
+ @lock_ttl = ttl
56
+ end
57
+
58
+ def locked_queues=(queues)
59
+ raise ArgumentError, 'Locked queues must be an array' unless queues.is_a?(Array)
60
+
61
+ @locked_queues = queues
62
+ end
63
+
64
+ def lock_jobs?
65
+ lock_jobs
66
+ end
67
+ end
18
68
  end
69
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: readymade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - OrestF
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-31 00:00:00.000000000 Z
11
+ date: 2025-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -99,6 +99,8 @@ files:
99
99
  - Rakefile
100
100
  - bin/console
101
101
  - bin/setup
102
+ - lib/generators/readymade/install_generator.rb
103
+ - lib/generators/readymade/templates/config/initializers/readymade.rb
102
104
  - lib/readymade.rb
103
105
  - lib/readymade/action.rb
104
106
  - lib/readymade/background_bang_job.rb