active_interaction-extras 0.1.0 → 1.0.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
- SHA1:
3
- metadata.gz: 73c4e77c2e101656185ad654364e8a4a609fb440
4
- data.tar.gz: 7d2a25ed77a026f5257a6370f1c52998d716496a
2
+ SHA256:
3
+ metadata.gz: ebddd04a27db967f1796d98c4571c9a49233cb8aeb2c0af53124804eca42813d
4
+ data.tar.gz: 51cd957db282cf73fcdac076a7493430e40d329d15706565af67dbf9111c604a
5
5
  SHA512:
6
- metadata.gz: be0a3a10fcafe850259f2955997ee900e5433967242f85ff64ff54f2b4346773f24e4d3306f72113351b6702a51d13bde1c1aac922db2d6a7ef73d4cd4b4d594
7
- data.tar.gz: f2994d0a85ee3b11c736bcb9384949388f7a1636924da5e7fa4672c124b4df818553a2b666a062a588ebff11bee08f90d949edd1b35ec1ebc90c21103e511059
6
+ metadata.gz: ce26d9609ccde4305df772131058bd3181de9736b7be5028210aef8905c9f07baa6b9ed9280a8c2f2dd9e0519c7e3cd84650786a8e2582ea6161cccf66b849c7
7
+ data.tar.gz: 3ee722c05e8d86fc2781fe6ac3283e662d8321d47e9c15e346fa58facde4833c92465e5705d213993b77012e6e25235a7b91be36a2e0bb8e6181d9eda62fafd8
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Tests
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
28
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
29
+ # uses: ruby/setup-ruby@v1
30
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Run tests
35
+ run: bundle exec rake
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [1.0.0] - 2021-05-12
8
+
9
+ - Requires active_interaction v4
10
+ - New filters: `anything` and `uuid`
11
+ - New filter extensions
12
+ - object: support for multiple classes
13
+ - hash: disable auto strip when no keys are listed
14
+ - New extension:
15
+ - filter alias
16
+ - Changed `transaction` extension
17
+ - It requires new transaction by default
18
+ - Include order is important now
19
+ - Removed `active_interaction-active_job` gem dependency
20
+ - Added changelog
data/README.md CHANGED
@@ -1,50 +1,116 @@
1
1
  # ActiveInteraction::Extras
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/active_interaction-extras.svg)](https://badge.fury.io/rb/active_interaction-extras) ![CI build](https://github.com/antulik/active_interaction-extras/actions/workflows/ci.yml/badge.svg)
4
+
3
5
  This gem contains the collection of useful extensions to [active_interaction](https://github.com/AaronLasseigne/active_interaction) gem.
4
6
 
7
+ - [Installation](#installation)
8
+ - [Basic Usage](#basic-usage)
9
+ - [Filters](#filters)
10
+ - [Anything](#anything)
11
+ - [UUID](#uuid)
12
+ - [Filter Extensions](#filter-extensions)
13
+ - [Hash: auto strip](#hash-auto-strip)
14
+ - [Object: multiple classes](#object-multiple-classes)
15
+ - [Extensions](#extensions)
16
+ - [Filter alias](#filter-alias)
17
+ - [Halt](#halt)
18
+ - [ModelFields](#modelfields)
19
+ - [RunCallback](#runcallback)
20
+ - [StrongParams](#strongparams)
21
+ - [Transaction](#transaction)
22
+ - [Jobs](#jobs)
23
+ - [ActiveJob](#activejob)
24
+ - [Sidekiq](#sidekiq)
25
+ - [RSpec](#rspec)
26
+
5
27
  ## Installation
6
28
 
7
29
  ```ruby
8
30
  gem 'active_interaction-extras'
9
31
  ```
10
32
 
11
- ## Usage
12
-
13
- ### All
33
+ ## Basic Usage
14
34
 
15
35
  ```ruby
36
+ # app/services/application_interaction.rb
16
37
  class ApplicationInteraction < ActiveInteraction::Base
17
38
  include ActiveInteraction::Extras::All
18
- # same as
19
- # include ActiveInteraction::Extras::ActiveJob
20
- # include ActiveInteraction::Extras::Halt
21
- # include ActiveInteraction::Extras::ModelFields
22
- # include ActiveInteraction::Extras::RunCallback
23
- # include ActiveInteraction::Extras::StrongParams
24
- # include ActiveInteraction::Extras::Transaction
25
39
  end
26
40
  ```
27
41
 
28
- ### ActiveJob
42
+ ## Filters
43
+
44
+ These new filters are added automatically when gem is loaded.
45
+
46
+ ### Anything
47
+
48
+ Anything filter accepts as you guest it - anything.
29
49
 
30
50
  ```ruby
31
- class ApplicationInteraction < ActiveInteraction::Base
32
- include ActiveInteraction::Extras::ActiveJob
51
+ class Service < ActiveInteraction::Base
52
+ anything :model
53
+ end
54
+ ```
33
55
 
34
- class Job < ActiveJob::Base
35
- include ActiveInteraction::Extras::ActiveJob::Perform
56
+ ### UUID
57
+
58
+ ```ruby
59
+ class Service < ActiveInteraction::Base
60
+ uuid :id
61
+ end
62
+ ```
63
+
64
+ ## Filter Extensions
65
+
66
+ You can load all filter extensions with:
67
+
68
+ ```ruby
69
+ # config/initializers/active_interaction.rb
70
+ require 'active_interaction/extras/filter_extensions'
71
+ ```
72
+
73
+ ### Hash: auto strip
74
+
75
+ This small extensions allows to accept full hashes without explicit `strip` option.
76
+
77
+ ```ruby
78
+ class Service < ActiveInteraction::Base
79
+ hash :options_a, strip: false # (Before) Accept all keys
80
+
81
+ hash :options_b # (After) Accept all keys
82
+
83
+ hash :options_c do # (Before and After) Accept only specified keys
84
+ string :name
36
85
  end
37
86
  end
87
+ ```
38
88
 
39
- class DoubleService < ApplicationInteraction
40
- integer :x
89
+ ### Object: multiple classes
90
+
91
+ This extension allows using `object` filter with multiple classes.
92
+
93
+ ```ruby
94
+ class Service < ActiveInteraction::Base
95
+ object :user, class: [User, AdminUser]
96
+ end
97
+ ```
98
+
99
+
100
+ ## Extensions
101
+
102
+ ### Filter Alias
103
+
104
+ ```ruby
105
+ class Service < ActiveInteraction::Base
106
+ include ActiveInteraction::Extras::FilterAlias
107
+
108
+ hash :params, as: :user_attributes
41
109
 
42
110
  def execute
43
- x + x
111
+ user_attributes == params # => true
44
112
  end
45
113
  end
46
-
47
- DoubleService.delay.run(x: 2) # queues to run in background
48
114
  ```
49
115
 
50
116
  ### Halt
@@ -73,7 +139,7 @@ end
73
139
  class UserForm < ActiveInteraction::Base
74
140
  include ActiveInteraction::Extras::ModelFields
75
141
 
76
- interface :user
142
+ anything :user
77
143
 
78
144
  model_fields(:user) do
79
145
  string :first_name
@@ -119,7 +185,6 @@ end
119
185
 
120
186
  ### StrongParams
121
187
 
122
-
123
188
  ```ruby
124
189
  class UpdateUserForm < ActiveInteraction::Base
125
190
  include ActiveInteraction::Extras::StrongParams
@@ -143,6 +208,14 @@ form_params = ActionController::Parameters.new(
143
208
  )
144
209
 
145
210
  Service.run(params: form_params)
211
+
212
+ # OR
213
+ form_params = ActionController::Parameters.new(
214
+ first_name: 'Allowed',
215
+ last_name: 'Not allowed',
216
+ )
217
+
218
+ Service.run(form_params: form_params)
146
219
  ```
147
220
 
148
221
  ### Transaction
@@ -164,7 +237,109 @@ UpdateUserForm.run
164
237
  Comment.count # => 0
165
238
  ```
166
239
 
167
- ### Rspec
240
+ ## Jobs
241
+
242
+ You no longer need to create a separate Job class for the each interaction. This Job extension automatically converts interactions to background jobs. By convention each interaction will have a nested `Job` class which will be inherited from the parent interaction `Job` class (e.g. `ApplicationInteraction::Job`).
243
+
244
+ ### ActiveJob
245
+
246
+ ```ruby
247
+ class ApplicationInteraction < ActiveInteraction::Base
248
+ include ActiveInteraction::Extras::ActiveJob
249
+
250
+ class Job < ActiveJob::Base
251
+ include ActiveInteraction::Extras::ActiveJob::Perform
252
+ end
253
+ end
254
+
255
+ class DoubleService < ApplicationInteraction
256
+ integer :x
257
+
258
+ def execute
259
+ x + x
260
+ end
261
+ end
262
+
263
+ DoubleService.delay.run(x: 2) # queues to run in background
264
+ DoubleService.delay(queue: 'low_priority', wait: 1.minute).run(x: 2)
265
+ ```
266
+
267
+ In ActiveJob mode `delay` method accepts anything ActiveJob `set` [method](https://edgeapi.rubyonrails.org/classes/ActiveJob/Core/ClassMethods.html#method-i-set) does. (`wait`, `wait_until`, `queue`, `priority`)
268
+
269
+ ### Sidekiq
270
+
271
+ You can use sidekiq directly if you need more control. Sidekiq integration comes with default GlobalID support.
272
+
273
+ ```ruby
274
+ class ApplicationInteraction < ActiveInteraction::Base
275
+ include ActiveInteraction::Extras::Sidekiq
276
+
277
+ class Job
278
+ include Sidekiq::Worker
279
+ include ActiveInteraction::Extras::Sidekiq::Perform
280
+ end
281
+ end
282
+
283
+ class DoubleService < ApplicationInteraction
284
+ job do
285
+ sidekiq_options retry: 1 # configure sidekiq options
286
+ end
287
+
288
+ integer :x
289
+
290
+ def execute
291
+ x + x
292
+ end
293
+ end
294
+
295
+ DoubleService.delay.run(x: 2) # queues to run in background
296
+ DoubleService.delay(queue: 'low_priority', wait: 1.minute).run(x: 2)
297
+ ```
298
+
299
+ In Sidekiq mode `delay` method accepts anything sidekiq `set` [method](https://github.com/mperham/sidekiq/wiki/Advanced-Options#workers) does (`queue`, `retry`, `backtrace`, etc). Plus two additional `wait` and `wait_until`.
300
+
301
+ ```ruby
302
+ # Advance usage: retry based on given params
303
+ class DoubleService < ApplicationInteraction
304
+ job do
305
+ sidekiq_options(retry: ->(job) {
306
+ params = deserialize_active_job_args(job)
307
+ params[:x]
308
+ })
309
+ end
310
+
311
+ integer :x
312
+
313
+ def execute
314
+ x + x
315
+ end
316
+ end
317
+ ```
318
+
319
+ ```ruby
320
+ # Advance usage: Rescue the job but not service
321
+ class DoubleService < ApplicationInteraction
322
+ job do
323
+ def perform(*args)
324
+ super
325
+ rescue StandardError => e
326
+ params = deserialize_active_job_args(args)
327
+ params[:x]
328
+ end
329
+ end
330
+
331
+ integer :x
332
+
333
+ def execute
334
+ raise
335
+ end
336
+ end
337
+
338
+ DoubleService.run # => RuntimeError
339
+ DoubleService.delay.perform_now(x: 2) # => returns 2
340
+ ```
341
+
342
+ ## Rspec
168
343
 
169
344
  ```ruby
170
345
  class SomeService < ActiveInteraction::Base
@@ -193,7 +368,7 @@ RSpec.describe SomeService do
193
368
 
194
369
  # expect_to_run / expect_not_to_run / expect_to_not_run
195
370
  # expect_to_execute
196
- # expect_to_delay_run / expect_to_not_run_delayed
371
+ # expect_to_delay_run / expect_not_to_run_delayed / expect_to_not_run_delayed
197
372
  # expect_to_delay_execute
198
373
  end
199
374
  end
@@ -213,6 +388,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/antuli
213
388
 
214
389
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
215
390
 
391
+ ## Credits
392
+
393
+ ActiveInteraction::Extras is brought to you by [Anton Katunin](https://github.com/antulik) and was originally built at [CarNextDoor](https://www.carnextdoor.com.au/).
394
+
216
395
  ## Code of Conduct
217
396
 
218
397
  Everyone interacting in the ActiveInteraction::Extras project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/antulik/active_interaction-extras/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
- task :default => :spec
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+ rescue LoadError
9
+ # no rspec available
10
+ end
@@ -9,10 +9,13 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Anton Katunin"]
10
10
  spec.email = ["antulik@gmail.com"]
11
11
 
12
- spec.summary = %q{Extension for active_interaction gem}
13
- spec.description = %q{Extension for active_interaction gem}
14
- spec.homepage = "https://github.com/antulik/xxxx"
12
+ spec.summary = %q{Extensions for active_interaction gem}
13
+ spec.description = %q{Extensions for active_interaction gem}
14
+ spec.homepage = "https://github.com/antulik/active_interaction-extras"
15
15
  spec.license = "MIT"
16
+ spec.metadata = {
17
+ "changelog_uri" => "https://github.com/antulik/active_interaction-extras/blob/master/CHANGELOG.md",
18
+ }
16
19
 
17
20
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
21
  f.match(%r{^(test|spec|features)/})
@@ -21,11 +24,10 @@ Gem::Specification.new do |spec|
21
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
25
  spec.require_paths = ["lib"]
23
26
 
24
- spec.add_dependency "active_interaction", ">= 3.0.0"
25
- spec.add_dependency "active_interaction-active_job", ">= 0"
26
- spec.add_development_dependency "bundler", "~> 1.16"
27
- spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_dependency "active_interaction", ">= 4"
28
+ spec.add_dependency "rails", ">= 6.0"
29
+ spec.add_development_dependency "bundler", "~> 2.2"
30
+ spec.add_development_dependency "rake", ">= 12.3.3"
28
31
  spec.add_development_dependency "rspec", "~> 3.7"
29
- spec.add_development_dependency "rails", ">= 4.0"
30
32
  spec.add_development_dependency "pry"
31
33
  end
@@ -5,14 +5,40 @@ require 'active_interaction'
5
5
 
6
6
  module ActiveInteraction
7
7
  module Extras
8
+ module Filters
9
+ end
10
+
11
+ module FilterExtensions
12
+ end
13
+
14
+ module Jobs
15
+ autoload(:Core, "active_interaction/extras/jobs/core")
16
+ end
8
17
 
9
- autoload(:ActiveJob, "active_interaction/extras/active_job")
10
18
  autoload(:All, "active_interaction/extras/all")
19
+
20
+ autoload(:FilterAlias, "active_interaction/extras/filter_alias")
11
21
  autoload(:Halt, "active_interaction/extras/halt")
12
22
  autoload(:ModelFields, "active_interaction/extras/model_fields")
13
- autoload(:Rspec, "active_interaction/extras/rspec")
14
23
  autoload(:RunCallback, "active_interaction/extras/run_callback")
15
24
  autoload(:StrongParams, "active_interaction/extras/strong_params")
16
25
  autoload(:Transaction, "active_interaction/extras/transaction")
26
+
27
+ autoload(:TimezoneSupport, "active_interaction/extras/timezone_support")
28
+ autoload(:Rspec, "active_interaction/extras/rspec")
29
+
30
+ autoload(:ActiveJob, "active_interaction/extras/active_job")
31
+ autoload(:Sidekiq, "active_interaction/extras/sidekiq")
17
32
  end
18
33
  end
34
+
35
+ require 'active_interaction/extras/filters/anything_filter'
36
+ require 'active_interaction/extras/filters/uuid_filter'
37
+
38
+ I18n.load_path.unshift(
39
+ *Dir.glob(
40
+ File.expand_path(
41
+ File.join(%w[extras locale *.yml]), File.dirname(__FILE__)
42
+ )
43
+ )
44
+ )
@@ -1,11 +1,35 @@
1
+ require 'active_job'
2
+
1
3
  module ActiveInteraction::Extras::ActiveJob
2
4
  extend ActiveSupport::Concern
3
5
 
4
- include ActiveInteraction::ActiveJob::Core
6
+ include ActiveInteraction::Extras::Jobs::Core
7
+
8
+ class_methods do
9
+ def configured_job_class
10
+ ConfiguredJob
11
+ end
12
+ end
5
13
 
6
14
  module Perform
7
15
  extend ActiveSupport::Concern
8
16
 
9
- include ActiveInteraction::ActiveJob::JobHelper
17
+ def perform(*args)
18
+ if self.class.respond_to?(:module_parent)
19
+ self.class.module_parent.run!(*args)
20
+ else
21
+ self.class.parent.run!(*args)
22
+ end
23
+ end
24
+ end
25
+
26
+ class ConfiguredJob < ::ActiveJob::ConfiguredJob
27
+ def run(*args)
28
+ perform_later(*args)
29
+ end
30
+
31
+ def run!(*args)
32
+ perform_later(*args)
33
+ end
10
34
  end
11
35
  end
@@ -1,10 +1,12 @@
1
1
  module ActiveInteraction::Extras::All
2
2
  extend ActiveSupport::Concern
3
3
 
4
- include ActiveInteraction::Extras::ActiveJob
4
+ # order dependant, include first so around callback includes other modules
5
+ include ActiveInteraction::Extras::Transaction
6
+
7
+ include ActiveInteraction::Extras::FilterAlias
5
8
  include ActiveInteraction::Extras::Halt
6
9
  include ActiveInteraction::Extras::ModelFields
7
10
  include ActiveInteraction::Extras::RunCallback
8
11
  include ActiveInteraction::Extras::StrongParams
9
- include ActiveInteraction::Extras::Transaction
10
12
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add :as option, which is a read alias to filter
4
+ # hash :params, as: :account_attributes
5
+ #
6
+ module ActiveInteraction::Extras::FilterAlias
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def initialize_filter(filter)
11
+ super.tap do
12
+ if filter.options[:as]
13
+ alias_method filter.options[:as], filter.name
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,2 @@
1
+ require 'active_interaction/extras/filter_extensions/hash_auto_strip'
2
+ require 'active_interaction/extras/filter_extensions/object_classes'
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # If hash specified without structure automatically accept full hash
4
+ #
5
+ # @example Accept all keys
6
+ # hash :options
7
+ #
8
+ # @example Accept only specified keys
9
+ # hash :options do
10
+ # string :name
11
+ # end
12
+ #
13
+ # @example Accept all keys
14
+ # hash :options, strip: false do
15
+ # string :name
16
+ # end
17
+ #
18
+ module ActiveInteraction::Extras::FilterExtensions::HashAutoStrip
19
+ def initialize(*)
20
+ super
21
+ options[:strip] = false if !block_given? && !options.key?(:strip)
22
+ end
23
+ end
24
+
25
+ ActiveInteraction::HashFilter.prepend(ActiveInteraction::Extras::FilterExtensions::HashAutoStrip)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add support for polymorphic objects
4
+ # object :account, class: [Account, AnyoneAccount]
5
+ #
6
+ module ActiveInteraction::Extras::FilterExtensions::ObjectClasses
7
+ def class_list
8
+ class_names.map do |klass_name|
9
+ case klass_name
10
+ when Class
11
+ klass_name
12
+ else
13
+ begin
14
+ Object.const_get(klass_name.to_s.camelize)
15
+ rescue NameError
16
+ raise ActiveInteraction::InvalidNameError, "class #{klass_name.inspect} does not exist"
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def klass
23
+ if polymorphic?
24
+ class_list.first
25
+ else
26
+ super
27
+ end
28
+ end
29
+
30
+ def matches?(value)
31
+ if polymorphic?
32
+ class_list.any? { |klass| value.class <= klass }
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def class_names
39
+ options.fetch(:class, name)
40
+ end
41
+
42
+ def polymorphic?
43
+ class_names.is_a? Array
44
+ end
45
+ end
46
+
47
+ ActiveInteraction::ObjectFilter.prepend(ActiveInteraction::Extras::FilterExtensions::ObjectClasses)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Implementation inspired by
4
+ # https://github.com/AaronLasseigne/active_interaction/blob/c9d5608c3b8aab23d463f99c832b2ac5139911de/lib/active_interaction/filters/abstract_date_time_filter.rb#L42
5
+ module ActiveInteraction::Extras::FilterExtensions::TimezoneSupport
6
+ def convert_string(value)
7
+ if time_with_zone?
8
+ Time.zone.parse(value) ||
9
+ raise(ArgumentError, "no time information in #{value.inspect}")
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+
16
+ ActiveInteraction::TimeFilter.include(ActiveInteraction::Extras::FilterExtensions::TimezoneSupport)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveInteraction::Extras::Filters::AnythingFilter < ActiveInteraction::Filter
4
+ register :anything
5
+
6
+ def matches?(_object)
7
+ true
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveInteraction::Extras::Filters::UUIDFilter < ActiveInteraction::StringFilter
4
+ register :uuid
5
+
6
+ REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.freeze
7
+
8
+ def matches?(value)
9
+ super && REGEX.match?(value)
10
+ end
11
+
12
+ def convert(value)
13
+ super&.presence
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveInteraction::Extras::Jobs::Core
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def define_job_class(klass)
6
+ unless const_defined?(:Job, false)
7
+ const_set(:Job, Class.new(klass))
8
+ end
9
+ end
10
+
11
+ def job(&block)
12
+ job_class.class_exec(&block)
13
+ end
14
+
15
+ def job_class
16
+ const_get(:Job, false)
17
+ end
18
+
19
+ def inherited(subclass)
20
+ super
21
+ subclass.define_job_class(job_class)
22
+ end
23
+
24
+ def delay(options = {})
25
+ configured_job_class.new(job_class, options)
26
+ end
27
+
28
+ def configured_job_class
29
+ raise NotImplementedError
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ en:
2
+ active_interaction:
3
+ types:
4
+ uuid: UUID
5
+ anything: anything
@@ -67,6 +67,8 @@ module ActiveInteraction::Extras::Rspec
67
67
  expect(klass).to_not receive(:delay).with(*with)
68
68
  end
69
69
 
70
+ alias expect_not_to_run_delayed expect_to_not_run_delayed
71
+
70
72
  # see expect_to_run
71
73
  #
72
74
  # additional params
@@ -0,0 +1,71 @@
1
+ require 'active_job'
2
+
3
+ module ActiveInteraction::Extras::Sidekiq
4
+ extend ActiveSupport::Concern
5
+
6
+ include ActiveInteraction::Extras::Jobs::Core
7
+
8
+ class_methods do
9
+ def configured_job_class
10
+ ConfiguredJob
11
+ end
12
+ end
13
+
14
+ module Perform
15
+ extend ActiveSupport::Concern
16
+
17
+ class_methods do
18
+ def deserialize_active_job_args(serialized_job)
19
+ ActiveJob::Arguments.deserialize(serialized_job['args']).first&.with_indifferent_access || {}
20
+ end
21
+
22
+ def perform_later(*args)
23
+ ConfiguredJob.new(self).perform_later(*args)
24
+ end
25
+ end
26
+
27
+ def perform(*args)
28
+ # support for sidekiq encrypted params
29
+ if args.length > 1 && args[0].nil?
30
+ args.shift
31
+ end
32
+
33
+ args = ActiveJob::Arguments.deserialize(args)
34
+ if self.class.respond_to?(:module_parent)
35
+ self.class.module_parent.run!(*args)
36
+ else
37
+ self.class.parent.run!(*args)
38
+ end
39
+ end
40
+
41
+ def deserialize_active_job_args(job_arguments)
42
+ ActiveJob::Arguments.deserialize(job_arguments).first&.with_indifferent_access || {}
43
+ end
44
+ end
45
+
46
+ class ConfiguredJob < ::ActiveJob::ConfiguredJob
47
+ def perform_now(*args)
48
+ @job_class.run!(*args)
49
+ end
50
+
51
+ def perform_later(*args)
52
+ args = ActiveJob::Arguments.serialize(args)
53
+ scope = @job_class.set(@options.except(:wait, :wait_until))
54
+
55
+ if @job_class.sidekiq_options['encrypt']
56
+ args.prepend(nil)
57
+ end
58
+
59
+ if @options[:wait]
60
+ scope.perform_in @options[:wait], *args
61
+ elsif @options[:wait_until]
62
+ scope.perform_at @options[:wait_until], *args
63
+ else
64
+ scope.perform_async *args
65
+ end
66
+ end
67
+
68
+ alias_method :run!, :perform_later
69
+ alias_method :run, :perform_later
70
+ end
71
+ end
@@ -1,32 +1,26 @@
1
+ # Add transaction wrapper
2
+ # run_in_transaction!
3
+ # skip_run_in_transaction!
1
4
  module ActiveInteraction::Extras::Transaction
2
5
  extend ActiveSupport::Concern
3
6
 
4
- def run_in_transaction!
5
- result_or_errors = nil
6
- ActiveRecord::Base.transaction do
7
- result_or_errors = yield
8
-
9
- # check by class because
10
- # errors added by compose method are merged after execute,
11
- # so we need to check return type ourselves
12
- #
13
- # see ActiveInteraction::Runnable#run
14
- if result_or_errors.is_a?(ActiveInteraction::Errors) && result_or_errors.any?
15
- raise ActiveRecord::Rollback
7
+ included do
8
+ class_attribute :run_in_transaction_options
9
+ set_callback :execute, :around, ->(_interaction, block) {
10
+ ActiveRecord::Base.transaction(run_in_transaction_options) do
11
+ block.call
16
12
  end
17
-
18
- raise ActiveRecord::Rollback if errors.any?
19
- end
20
- result_or_errors
13
+ }, if: :run_in_transaction_options
21
14
  end
22
15
 
23
16
  class_methods do
24
- def run_in_transaction!
25
- set_callback :execute, :around, :run_in_transaction!, prepend: true
17
+ # https://pragtob.wordpress.com/2017/12/12/surprises-with-nested-transactions-rollbacks-and-activerecord/
18
+ def run_in_transaction!(requires_new: true)
19
+ self.run_in_transaction_options = {requires_new: requires_new}
26
20
  end
27
21
 
28
22
  def skip_run_in_transaction!
29
- skip_callback :execute, :around, :run_in_transaction!
23
+ self.run_in_transaction_options = nil
30
24
  end
31
25
  end
32
26
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveInteraction
2
2
  module Extras
3
- VERSION = "0.1.0"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_interaction-extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Katunin
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-25 00:00:00.000000000 Z
11
+ date: 2021-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_interaction
@@ -16,56 +16,56 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.0.0
19
+ version: '4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.0.0
26
+ version: '4'
27
27
  - !ruby/object:Gem::Dependency
28
- name: active_interaction-active_job
28
+ name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '6.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '6.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.16'
47
+ version: '2.2'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.16'
54
+ version: '2.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '10.0'
61
+ version: 12.3.3
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '10.0'
68
+ version: 12.3.3
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.7'
83
- - !ruby/object:Gem::Dependency
84
- name: rails
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '4.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '4.0'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: pry
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,15 +94,17 @@ dependencies:
108
94
  - - ">="
109
95
  - !ruby/object:Gem::Version
110
96
  version: '0'
111
- description: Extension for active_interaction gem
97
+ description: Extensions for active_interaction gem
112
98
  email:
113
99
  - antulik@gmail.com
114
100
  executables: []
115
101
  extensions: []
116
102
  extra_rdoc_files: []
117
103
  files:
104
+ - ".github/workflows/ci.yml"
118
105
  - ".gitignore"
119
106
  - ".rspec"
107
+ - CHANGELOG.md
120
108
  - CODE_OF_CONDUCT.md
121
109
  - Gemfile
122
110
  - LICENSE.txt
@@ -128,18 +116,29 @@ files:
128
116
  - lib/active_interaction/extras.rb
129
117
  - lib/active_interaction/extras/active_job.rb
130
118
  - lib/active_interaction/extras/all.rb
119
+ - lib/active_interaction/extras/filter_alias.rb
120
+ - lib/active_interaction/extras/filter_extensions.rb
121
+ - lib/active_interaction/extras/filter_extensions/hash_auto_strip.rb
122
+ - lib/active_interaction/extras/filter_extensions/object_classes.rb
123
+ - lib/active_interaction/extras/filter_extensions/timezone_support.rb
124
+ - lib/active_interaction/extras/filters/anything_filter.rb
125
+ - lib/active_interaction/extras/filters/uuid_filter.rb
131
126
  - lib/active_interaction/extras/halt.rb
127
+ - lib/active_interaction/extras/jobs/core.rb
128
+ - lib/active_interaction/extras/locale/en.yml
132
129
  - lib/active_interaction/extras/model_fields.rb
133
130
  - lib/active_interaction/extras/rspec.rb
134
131
  - lib/active_interaction/extras/run_callback.rb
132
+ - lib/active_interaction/extras/sidekiq.rb
135
133
  - lib/active_interaction/extras/strong_params.rb
136
134
  - lib/active_interaction/extras/transaction.rb
137
135
  - lib/active_interaction/extras/version.rb
138
- homepage: https://github.com/antulik/xxxx
136
+ homepage: https://github.com/antulik/active_interaction-extras
139
137
  licenses:
140
138
  - MIT
141
- metadata: {}
142
- post_install_message:
139
+ metadata:
140
+ changelog_uri: https://github.com/antulik/active_interaction-extras/blob/master/CHANGELOG.md
141
+ post_install_message:
143
142
  rdoc_options: []
144
143
  require_paths:
145
144
  - lib
@@ -154,9 +153,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
153
  - !ruby/object:Gem::Version
155
154
  version: '0'
156
155
  requirements: []
157
- rubyforge_project:
158
- rubygems_version: 2.6.8
159
- signing_key:
156
+ rubygems_version: 3.1.4
157
+ signing_key:
160
158
  specification_version: 4
161
- summary: Extension for active_interaction gem
159
+ summary: Extensions for active_interaction gem
162
160
  test_files: []