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 +4 -4
 - data/.gitignore +3 -0
 - data/CHANGELOG.md +10 -0
 - data/Gemfile.lock +8 -8
 - data/README.md +69 -3
 - data/lib/generators/readymade/install_generator.rb +14 -0
 - data/lib/generators/readymade/templates/config/initializers/readymade.rb +23 -0
 - data/lib/readymade/background_job.rb +44 -0
 - data/lib/readymade/version.rb +1 -1
 - data/lib/readymade.rb +52 -1
 - metadata +4 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 7b3d5fedc87f2028115618494f4b1e4af9cb79fe9f82f7059bb9bb4d1705b3da
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 841cbdc4364c34a52706011db59158a041a5edd575db5083133b1eb2faffb6ee
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 6251e1a6304385b8bdd5d421d300c4d92ff24d21d374b18430938f6ebbf65c59cef58d3709da984b3f3e71945cb13fc6e6cedd0fa7235083d70e20dfb6cef095
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 2bbdb741c5b1bef74ad7e56efb946cf24675b25a17490fcee416ec7f49bd8ee4fdf3f601f9d44d345f1ad55f452f929114ff2df2d80aa34cfe299e162dab6cdb
         
     | 
    
        data/.gitignore
    CHANGED
    
    
    
        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. 
     | 
| 
      
 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. 
     | 
| 
      
 31 
     | 
    
         
            +
                bigdecimal (3.2.3)
         
     | 
| 
       32 
32 
     | 
    
         
             
                byebug (11.1.3)
         
     | 
| 
       33 
33 
     | 
    
         
             
                concurrent-ruby (1.3.5)
         
     | 
| 
       34 
     | 
    
         
            -
                connection_pool (2.5. 
     | 
| 
      
 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 
     | 
| 
      
 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
         
     | 
    
        data/lib/readymade/version.rb
    CHANGED
    
    
    
        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 
     | 
    
         
            -
             
     | 
| 
      
 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. 
     | 
| 
      
 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- 
     | 
| 
      
 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
         
     |