active_interaction-extras 1.0.4 → 2.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +4 -3
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +24 -0
  5. data/README.md +325 -0
  6. data/active_interaction-extras.gemspec +8 -5
  7. data/lib/active_interaction/extras/active_job.rb +36 -5
  8. data/lib/active_interaction/extras/add_to_batch.rb +13 -0
  9. data/lib/active_interaction/extras/after_initialize.rb +20 -0
  10. data/lib/active_interaction/extras/all.rb +16 -1
  11. data/lib/active_interaction/extras/all_runner.rb +91 -0
  12. data/lib/active_interaction/extras/append_inputs.rb +60 -0
  13. data/lib/active_interaction/extras/current.rb +23 -0
  14. data/lib/active_interaction/extras/file_blobs.rb +80 -0
  15. data/lib/active_interaction/extras/filter_alias.rb +28 -2
  16. data/lib/active_interaction/extras/filter_extensions/object_classes.rb +2 -0
  17. data/lib/active_interaction/extras/filter_extensions.rb +8 -0
  18. data/lib/active_interaction/extras/filters/anything_filter.rb +9 -2
  19. data/lib/active_interaction/extras/filters/ar_relation_filter.rb +22 -0
  20. data/lib/active_interaction/extras/filters/uuid_filter.rb +7 -2
  21. data/lib/active_interaction/extras/form_for.rb +33 -0
  22. data/lib/active_interaction/extras/good_job.rb +45 -0
  23. data/lib/active_interaction/extras/include_errors.rb +10 -0
  24. data/lib/active_interaction/extras/initialize_with.rb +16 -0
  25. data/lib/active_interaction/extras/instance_runnable.rb +35 -0
  26. data/lib/active_interaction/extras/model_fields.rb +75 -37
  27. data/lib/active_interaction/extras/model_names.rb +20 -0
  28. data/lib/active_interaction/extras/named_callbacks.rb +54 -0
  29. data/lib/active_interaction/extras/nested_attributes.rb +126 -0
  30. data/lib/active_interaction/extras/strong_params.rb +50 -10
  31. data/lib/active_interaction/extras/transaction.rb +27 -5
  32. data/lib/active_interaction/extras/version.rb +1 -1
  33. data/lib/active_interaction/extras.rb +10 -23
  34. metadata +49 -17
@@ -0,0 +1,126 @@
1
+ # inspired by store_model/nested_attributes.rb
2
+ module ActiveInteraction::Extras::NestedAttributes
3
+ extend ActiveSupport::Concern
4
+
5
+ module InputsExtension
6
+ extend ActiveSupport::Concern
7
+
8
+ def normalize(inputs)
9
+ if @base.class.respond_to? :nested_attribute_options
10
+ @base.class.nested_attribute_options.each do |attribute, options|
11
+ alias_name = "#{attribute}_attributes"
12
+ next if !inputs.key?(alias_name) && !inputs.key?(alias_name.to_sym)
13
+
14
+ value = inputs[alias_name] || inputs[alias_name.to_sym]
15
+ value = @base.class.process_nested_collection(value, options)
16
+
17
+ inputs[attribute.to_s] = value
18
+ end
19
+ end
20
+
21
+ super
22
+ end
23
+ end
24
+
25
+ included do
26
+ ActiveInteraction::Inputs.prepend InputsExtension
27
+
28
+ class_attribute :nested_attribute_options, default: {}
29
+ end
30
+
31
+ class_methods do
32
+ def accepts_nested_attributes_for(*attributes)
33
+ options = attributes.extract_options!
34
+ options.reverse_merge!(allow_destroy: false, update_only: false)
35
+
36
+ if options.key?(:permit)
37
+ if options[:permit].is_a?(Array)
38
+ options[:permit] |= [:id, :_destroy]
39
+ end
40
+ else
41
+ raise ArgumentError, "missing :permit option"
42
+ end
43
+
44
+ attributes.each do |attribute|
45
+ nested_attribute_options[attribute] = options
46
+
47
+ case filters[attribute]
48
+ when ActiveInteraction::ArrayFilter
49
+ define_association_setter_for_many attribute, options
50
+ else
51
+ define_association_setter_for_single(attribute, options)
52
+ define_method "#{attribute}_attributes=" do |*args, **kwargs|
53
+ send("#{attribute}=", *args, **kwargs)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def define_association_setter_for_many(association, options)
60
+ define_method "#{association}_attributes=" do |attributes|
61
+ attributes = self.class.process_nested_collection(attributes, options)
62
+ send("#{association}=", attributes)
63
+ end
64
+ end
65
+
66
+ def define_association_setter_for_single(association, options)
67
+ return unless options&.dig(:allow_destroy)
68
+
69
+ define_method "#{association}=" do |attributes|
70
+ if ActiveRecord::Type::Boolean.new.cast(attributes.stringify_keys.dig("_destroy"))
71
+ super(nil)
72
+ else
73
+ super(attributes)
74
+ end
75
+ end
76
+ end
77
+
78
+ def process_nested_collection(attributes, options = nil)
79
+ attributes = attributes.values if attributes.is_a?(Hash)
80
+ if options&.dig(:allow_destroy)
81
+ attributes.reject! do |attribute|
82
+ ActiveRecord::Type::Boolean.new.cast(attribute.stringify_keys.dig("_destroy"))
83
+ end
84
+ end
85
+
86
+ attributes.reject! { |attribute| call_reject_if(attribute, options[:reject_if]) } if options&.dig(:reject_if)
87
+
88
+ attributes
89
+ end
90
+
91
+ def call_reject_if(attributes, callback)
92
+ callback = ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC if callback == :all_blank
93
+
94
+ case callback
95
+ when Symbol
96
+ method(callback).arity.zero? ? send(callback) : send(callback, attributes)
97
+ when Proc
98
+ callback.call(attributes)
99
+ end
100
+ end
101
+ end
102
+
103
+ def assign_collection_association(model, association_name, attributes_collection, destroy_missing: false, **options)
104
+
105
+ if attributes_collection.is_a?(ActiveRecord::Associations::CollectionProxy)
106
+ return attributes_collection
107
+ end
108
+
109
+ model.instance_exec do
110
+ begin
111
+ old_options = nested_attributes_options[association_name]
112
+ nested_attributes_options[association_name] = options
113
+
114
+ list = assign_nested_attributes_for_collection_association(association_name, attributes_collection)
115
+
116
+ if destroy_missing
117
+ association(association_name).scope.excluding(list).each(&:destroy!)
118
+ end
119
+
120
+ list
121
+ ensure
122
+ nested_attributes_options[association_name] = old_options
123
+ end
124
+ end
125
+ end
126
+ end
@@ -5,6 +5,8 @@ module ActiveInteraction::Extras::StrongParams
5
5
  # TODO: whitelist :params and :form_params, so they could not be used as filters
6
6
  return super if self.class.filters.key?(:params) || self.class.filters.key?(:form_params)
7
7
 
8
+ return super if %i[fetch key? merge].any? { |m| !inputs.respond_to?(m) }
9
+
8
10
  if inputs.key?(:params) && inputs.key?(:form_params)
9
11
  raise ArgumentError, 'Both options :params and :form_params are given. ' \
10
12
  'One or none are accepted.'
@@ -27,18 +29,56 @@ module ActiveInteraction::Extras::StrongParams
27
29
 
28
30
  class_methods do
29
31
  def permitted_params
30
- filters.map do |filter_name, filter|
31
- next unless filter.options[:permit]
32
-
33
- case filter
34
- when ActiveInteraction::ArrayFilter
35
- { filter_name => [] }
36
- when ActiveInteraction::HashFilter
37
- { filter_name => {} }
32
+ permissions = filters.map do |filter_name, filter|
33
+ [
34
+ permission_for_filter(filter, filter_name),
35
+ # (permission_for_filter(filter, filter.options[:as]) if filter.options[:as])
36
+ ]
37
+ end.flatten(1).compact
38
+
39
+ if respond_to?(:nested_attribute_options)
40
+ nested_attribute_options.each do |attribute, opts|
41
+ permitted_list = opts.fetch(:permit)
42
+ permissions << {"#{attribute}_attributes": permitted_list}
43
+ end
44
+ end
45
+
46
+ permissions
47
+ end
48
+
49
+ def permission_for_filter(filter, name = filter.name)
50
+ permit = filter.options[:permit]
51
+ return unless permit
52
+
53
+ case filter
54
+ when ActiveInteraction::ArrayFilter
55
+ value =
56
+ if permit == true
57
+ nested_type = filter.filters.values.first
58
+ case nested_type
59
+ when ActiveInteraction::HashFilter, ActiveInteraction::ObjectFilter
60
+ {}
61
+ else
62
+ []
63
+ end
64
+ else
65
+ permit
66
+ end
67
+
68
+ { name => value }
69
+ when ActiveInteraction::HashFilter, ActiveInteraction::ObjectFilter
70
+ if permit == true
71
+ { name => {} }
38
72
  else
39
- filter_name
73
+ { name => permit }
40
74
  end
41
- end.compact
75
+ else
76
+ if permit == true
77
+ name
78
+ else
79
+ { name => permit }
80
+ end
81
+ end
42
82
  end
43
83
  end
44
84
  end
@@ -6,22 +6,44 @@ module ActiveInteraction::Extras::Transaction
6
6
 
7
7
  included do
8
8
  class_attribute :run_in_transaction_options
9
- set_callback :execute, :around, ->(_interaction, block) {
9
+
10
+ around_execute if: :run_in_transaction_options do |_interaction, block|
10
11
  ActiveRecord::Base.transaction(**run_in_transaction_options) do
11
12
  block.call
12
13
  raise ActiveRecord::Rollback if _interaction.errors.any?
13
14
  end
14
- }, if: :run_in_transaction_options
15
+ end
15
16
  end
16
17
 
17
18
  class_methods do
18
19
  # https://pragtob.wordpress.com/2017/12/12/surprises-with-nested-transactions-rollbacks-and-activerecord/
19
- def run_in_transaction!(requires_new: true)
20
- self.run_in_transaction_options = {requires_new: requires_new}
20
+ def run_in_transaction!(enabled = true, requires_new: true)
21
+ value =
22
+ if enabled
23
+ { requires_new: requires_new }
24
+ else
25
+ false
26
+ end
27
+
28
+ self.run_in_transaction_options = value
21
29
  end
22
30
 
23
31
  def skip_run_in_transaction!
24
- self.run_in_transaction_options = nil
32
+ self.run_in_transaction_options = false
33
+ end
34
+
35
+ def force_explicit_transactions!
36
+ before_filter do
37
+ if run_in_transaction_options.nil?
38
+ raise "Missing transaction definition. Add `run_in_transaction!`."
39
+ end
40
+ end
41
+ end
42
+
43
+ def list_missing_transaction_definition
44
+ Zeitwerk::Loader.eager_load_all
45
+ descendants
46
+ .select { |klass| klass.run_in_transaction_options.nil? }
25
47
  end
26
48
  end
27
49
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveInteraction
2
2
  module Extras
3
- VERSION = "1.0.4"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
@@ -1,38 +1,25 @@
1
1
  require "active_interaction/extras/version"
2
2
 
3
3
  require 'active_support'
4
+ require 'active_support/core_ext/class/attribute'
4
5
  require 'active_interaction'
5
6
 
6
- module ActiveInteraction
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
7
+ require 'zeitwerk'
17
8
 
18
- autoload(:All, "active_interaction/extras/all")
19
-
20
- autoload(:FilterAlias, "active_interaction/extras/filter_alias")
21
- autoload(:Halt, "active_interaction/extras/halt")
22
- autoload(:ModelFields, "active_interaction/extras/model_fields")
23
- autoload(:RunCallback, "active_interaction/extras/run_callback")
24
- autoload(:StrongParams, "active_interaction/extras/strong_params")
25
- autoload(:Transaction, "active_interaction/extras/transaction")
9
+ loader = Zeitwerk::Loader.for_gem_extension(ActiveInteraction)
10
+ loader.inflector.inflect(
11
+ 'uuid_filter' => 'UUIDFilter',
12
+ )
13
+ loader.setup
26
14
 
27
- autoload(:TimezoneSupport, "active_interaction/extras/timezone_support")
28
- autoload(:Rspec, "active_interaction/extras/rspec")
15
+ module ActiveInteraction
16
+ module Extras
29
17
 
30
- autoload(:ActiveJob, "active_interaction/extras/active_job")
31
- autoload(:Sidekiq, "active_interaction/extras/sidekiq")
32
18
  end
33
19
  end
34
20
 
35
21
  require 'active_interaction/extras/filters/anything_filter'
22
+ require 'active_interaction/extras/filters/ar_relation_filter'
36
23
  require 'active_interaction/extras/filters/uuid_filter'
37
24
 
38
25
  I18n.load_path.unshift(
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_interaction-extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Katunin
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-06-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: active_interaction
@@ -16,54 +15,74 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 4.0.2
18
+ version: '5.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '6.0'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
26
  - - ">="
25
27
  - !ruby/object:Gem::Version
26
- version: 4.0.2
28
+ version: '5.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '6.0'
27
32
  - !ruby/object:Gem::Dependency
28
33
  name: activemodel
29
34
  requirement: !ruby/object:Gem::Requirement
30
35
  requirements:
31
36
  - - ">="
32
37
  - !ruby/object:Gem::Version
33
- version: '6.0'
38
+ version: '7.1'
34
39
  type: :runtime
35
40
  prerelease: false
36
41
  version_requirements: !ruby/object:Gem::Requirement
37
42
  requirements:
38
43
  - - ">="
39
44
  - !ruby/object:Gem::Version
40
- version: '6.0'
45
+ version: '7.1'
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: activesupport
43
48
  requirement: !ruby/object:Gem::Requirement
44
49
  requirements:
45
50
  - - ">="
46
51
  - !ruby/object:Gem::Version
47
- version: '6.0'
52
+ version: '7.1'
48
53
  type: :runtime
49
54
  prerelease: false
50
55
  version_requirements: !ruby/object:Gem::Requirement
51
56
  requirements:
52
57
  - - ">="
53
58
  - !ruby/object:Gem::Version
54
- version: '6.0'
59
+ version: '7.1'
60
+ - !ruby/object:Gem::Dependency
61
+ name: zeitwerk
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '2'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '2'
55
74
  - !ruby/object:Gem::Dependency
56
75
  name: bundler
57
76
  requirement: !ruby/object:Gem::Requirement
58
77
  requirements:
59
- - - "~>"
78
+ - - ">="
60
79
  - !ruby/object:Gem::Version
61
80
  version: '2.2'
62
81
  type: :development
63
82
  prerelease: false
64
83
  version_requirements: !ruby/object:Gem::Requirement
65
84
  requirements:
66
- - - "~>"
85
+ - - ">="
67
86
  - !ruby/object:Gem::Version
68
87
  version: '2.2'
69
88
  - !ruby/object:Gem::Dependency
@@ -114,14 +133,14 @@ dependencies:
114
133
  requirements:
115
134
  - - ">="
116
135
  - !ruby/object:Gem::Version
117
- version: '0'
136
+ version: '2'
118
137
  type: :development
119
138
  prerelease: false
120
139
  version_requirements: !ruby/object:Gem::Requirement
121
140
  requirements:
122
141
  - - ">="
123
142
  - !ruby/object:Gem::Version
124
- version: '0'
143
+ version: '2'
125
144
  - !ruby/object:Gem::Dependency
126
145
  name: activerecord
127
146
  requirement: !ruby/object:Gem::Requirement
@@ -185,18 +204,33 @@ files:
185
204
  - bin/setup
186
205
  - lib/active_interaction/extras.rb
187
206
  - lib/active_interaction/extras/active_job.rb
207
+ - lib/active_interaction/extras/add_to_batch.rb
208
+ - lib/active_interaction/extras/after_initialize.rb
188
209
  - lib/active_interaction/extras/all.rb
210
+ - lib/active_interaction/extras/all_runner.rb
211
+ - lib/active_interaction/extras/append_inputs.rb
212
+ - lib/active_interaction/extras/current.rb
213
+ - lib/active_interaction/extras/file_blobs.rb
189
214
  - lib/active_interaction/extras/filter_alias.rb
190
215
  - lib/active_interaction/extras/filter_extensions.rb
191
216
  - lib/active_interaction/extras/filter_extensions/hash_auto_strip.rb
192
217
  - lib/active_interaction/extras/filter_extensions/object_classes.rb
193
218
  - lib/active_interaction/extras/filter_extensions/timezone_support.rb
194
219
  - lib/active_interaction/extras/filters/anything_filter.rb
220
+ - lib/active_interaction/extras/filters/ar_relation_filter.rb
195
221
  - lib/active_interaction/extras/filters/uuid_filter.rb
222
+ - lib/active_interaction/extras/form_for.rb
223
+ - lib/active_interaction/extras/good_job.rb
196
224
  - lib/active_interaction/extras/halt.rb
225
+ - lib/active_interaction/extras/include_errors.rb
226
+ - lib/active_interaction/extras/initialize_with.rb
227
+ - lib/active_interaction/extras/instance_runnable.rb
197
228
  - lib/active_interaction/extras/jobs/core.rb
198
229
  - lib/active_interaction/extras/locale/en.yml
199
230
  - lib/active_interaction/extras/model_fields.rb
231
+ - lib/active_interaction/extras/model_names.rb
232
+ - lib/active_interaction/extras/named_callbacks.rb
233
+ - lib/active_interaction/extras/nested_attributes.rb
200
234
  - lib/active_interaction/extras/rspec.rb
201
235
  - lib/active_interaction/extras/run_callback.rb
202
236
  - lib/active_interaction/extras/sidekiq.rb
@@ -208,7 +242,6 @@ licenses:
208
242
  - MIT
209
243
  metadata:
210
244
  changelog_uri: https://github.com/antulik/active_interaction-extras/blob/master/CHANGELOG.md
211
- post_install_message:
212
245
  rdoc_options: []
213
246
  require_paths:
214
247
  - lib
@@ -216,15 +249,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
216
249
  requirements:
217
250
  - - ">="
218
251
  - !ruby/object:Gem::Version
219
- version: '0'
252
+ version: '3.0'
220
253
  required_rubygems_version: !ruby/object:Gem::Requirement
221
254
  requirements:
222
255
  - - ">="
223
256
  - !ruby/object:Gem::Version
224
257
  version: '0'
225
258
  requirements: []
226
- rubygems_version: 3.1.4
227
- signing_key:
259
+ rubygems_version: 4.0.14
228
260
  specification_version: 4
229
261
  summary: Extensions for active_interaction gem
230
262
  test_files: []