active_interaction 3.8.3 → 4.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -0
  3. data/README.md +93 -107
  4. data/lib/active_interaction.rb +1 -7
  5. data/lib/active_interaction/base.rb +23 -26
  6. data/lib/active_interaction/concerns/active_modelable.rb +1 -3
  7. data/lib/active_interaction/concerns/active_recordable.rb +1 -6
  8. data/lib/active_interaction/concerns/hashable.rb +0 -1
  9. data/lib/active_interaction/concerns/missable.rb +0 -1
  10. data/lib/active_interaction/concerns/runnable.rb +6 -12
  11. data/lib/active_interaction/errors.rb +3 -6
  12. data/lib/active_interaction/filter.rb +51 -37
  13. data/lib/active_interaction/filter_column.rb +0 -3
  14. data/lib/active_interaction/filters/abstract_date_time_filter.rb +34 -36
  15. data/lib/active_interaction/filters/abstract_numeric_filter.rb +27 -17
  16. data/lib/active_interaction/filters/array_filter.rb +57 -36
  17. data/lib/active_interaction/filters/boolean_filter.rb +26 -12
  18. data/lib/active_interaction/filters/date_filter.rb +1 -2
  19. data/lib/active_interaction/filters/date_time_filter.rb +1 -2
  20. data/lib/active_interaction/filters/decimal_filter.rb +10 -28
  21. data/lib/active_interaction/filters/file_filter.rb +6 -5
  22. data/lib/active_interaction/filters/float_filter.rb +1 -2
  23. data/lib/active_interaction/filters/hash_filter.rb +37 -27
  24. data/lib/active_interaction/filters/integer_filter.rb +7 -8
  25. data/lib/active_interaction/filters/interface_filter.rb +48 -14
  26. data/lib/active_interaction/filters/object_filter.rb +23 -50
  27. data/lib/active_interaction/filters/record_filter.rb +10 -35
  28. data/lib/active_interaction/filters/string_filter.rb +21 -12
  29. data/lib/active_interaction/filters/symbol_filter.rb +13 -7
  30. data/lib/active_interaction/filters/time_filter.rb +19 -22
  31. data/lib/active_interaction/grouped_input.rb +0 -3
  32. data/lib/active_interaction/inputs.rb +89 -0
  33. data/lib/active_interaction/modules/input_processor.rb +1 -4
  34. data/lib/active_interaction/modules/validation.rb +9 -12
  35. data/lib/active_interaction/version.rb +1 -3
  36. data/spec/active_interaction/base_spec.rb +13 -41
  37. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -2
  38. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -2
  39. data/spec/active_interaction/concerns/hashable_spec.rb +1 -3
  40. data/spec/active_interaction/concerns/missable_spec.rb +0 -2
  41. data/spec/active_interaction/concerns/runnable_spec.rb +9 -13
  42. data/spec/active_interaction/errors_spec.rb +4 -25
  43. data/spec/active_interaction/filter_column_spec.rb +0 -2
  44. data/spec/active_interaction/filter_spec.rb +0 -2
  45. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +1 -3
  46. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -3
  47. data/spec/active_interaction/filters/array_filter_spec.rb +41 -15
  48. data/spec/active_interaction/filters/boolean_filter_spec.rb +58 -4
  49. data/spec/active_interaction/filters/date_filter_spec.rb +43 -3
  50. data/spec/active_interaction/filters/date_time_filter_spec.rb +43 -3
  51. data/spec/active_interaction/filters/decimal_filter_spec.rb +57 -3
  52. data/spec/active_interaction/filters/file_filter_spec.rb +1 -3
  53. data/spec/active_interaction/filters/float_filter_spec.rb +60 -4
  54. data/spec/active_interaction/filters/hash_filter_spec.rb +19 -9
  55. data/spec/active_interaction/filters/integer_filter_spec.rb +49 -7
  56. data/spec/active_interaction/filters/interface_filter_spec.rb +397 -24
  57. data/spec/active_interaction/filters/object_filter_spec.rb +23 -59
  58. data/spec/active_interaction/filters/record_filter_spec.rb +23 -49
  59. data/spec/active_interaction/filters/string_filter_spec.rb +15 -3
  60. data/spec/active_interaction/filters/symbol_filter_spec.rb +15 -3
  61. data/spec/active_interaction/filters/time_filter_spec.rb +65 -3
  62. data/spec/active_interaction/grouped_input_spec.rb +0 -2
  63. data/spec/active_interaction/i18n_spec.rb +3 -7
  64. data/spec/active_interaction/{modules/input_processor_spec.rb → inputs_spec.rb} +3 -5
  65. data/spec/active_interaction/integration/array_interaction_spec.rb +18 -22
  66. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -2
  67. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -2
  68. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -2
  69. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -2
  70. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -2
  71. data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -2
  72. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -2
  73. data/spec/active_interaction/integration/interface_interaction_spec.rb +1 -3
  74. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -2
  75. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -2
  76. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -2
  77. data/spec/active_interaction/integration/time_interaction_spec.rb +14 -18
  78. data/spec/active_interaction/modules/validation_spec.rb +1 -3
  79. data/spec/spec_helper.rb +2 -6
  80. data/spec/support/concerns.rb +0 -2
  81. data/spec/support/filters.rb +13 -9
  82. data/spec/support/interactions.rb +22 -14
  83. metadata +77 -52
  84. data/lib/active_interaction/backports.rb +0 -59
  85. data/lib/active_interaction/filters/abstract_filter.rb +0 -19
  86. data/spec/active_interaction/filters/abstract_filter_spec.rb +0 -8
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'active_model'
@@ -7,8 +6,6 @@ require 'active_model'
7
6
  #
8
7
  # @author Aaron Lasseigne <aaron.lasseigne@gmail.com>
9
8
  # @author Taylor Fausak <taylor@fausak.me>
10
- #
11
- # @since 1.0.0
12
9
  module ActiveInteraction
13
10
  end
14
11
 
@@ -22,13 +19,12 @@ require 'active_interaction/concerns/missable'
22
19
  require 'active_interaction/concerns/runnable'
23
20
 
24
21
  require 'active_interaction/grouped_input'
22
+ require 'active_interaction/inputs'
25
23
 
26
- require 'active_interaction/modules/input_processor'
27
24
  require 'active_interaction/modules/validation'
28
25
 
29
26
  require 'active_interaction/filter_column'
30
27
  require 'active_interaction/filter'
31
- require 'active_interaction/filters/abstract_filter'
32
28
  require 'active_interaction/filters/interface_filter'
33
29
  require 'active_interaction/filters/abstract_date_time_filter'
34
30
  require 'active_interaction/filters/abstract_numeric_filter'
@@ -49,8 +45,6 @@ require 'active_interaction/filters/time_filter'
49
45
 
50
46
  require 'active_interaction/base'
51
47
 
52
- require 'active_interaction/backports'
53
-
54
48
  I18n.load_path.unshift(
55
49
  *Dir.glob(
56
50
  File.expand_path(
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'active_support/core_ext/hash/indifferent_access'
@@ -28,7 +27,7 @@ module ActiveInteraction
28
27
  # else
29
28
  # outcome.errors
30
29
  # end
31
- class Base # rubocop:disable Metrics/ClassLength
30
+ class Base
32
31
  include ActiveModelable
33
32
  include ActiveRecordable
34
33
  include Runnable
@@ -74,9 +73,7 @@ module ActiveInteraction
74
73
  # @return [String, nil] The description.
75
74
  def desc(desc = nil)
76
75
  if desc.nil?
77
- unless instance_variable_defined?(:@_interaction_desc)
78
- @_interaction_desc = nil
79
- end
76
+ @_interaction_desc = nil unless instance_variable_defined?(:@_interaction_desc)
80
77
  else
81
78
  @_interaction_desc = desc
82
79
  end
@@ -88,17 +85,21 @@ module ActiveInteraction
88
85
  #
89
86
  # @return [Hash{Symbol => Filter}]
90
87
  def filters
88
+ # rubocop:disable Naming/MemoizedInstanceVariableName
91
89
  @_interaction_filters ||= {}
90
+ # rubocop:enable Naming/MemoizedInstanceVariableName
92
91
  end
93
92
 
94
93
  # @private
95
- def method_missing(*args, &block) # rubocop:disable Style/MethodMissing
94
+ # rubocop:disable Style/MissingRespondToMissing
95
+ def method_missing(*args, &block)
96
96
  super do |klass, names, options|
97
97
  raise InvalidFilterError, 'missing attribute name' if names.empty?
98
98
 
99
99
  names.each { |name| add_filter(klass, name, options, &block) }
100
100
  end
101
101
  end
102
+ # rubocop:enable Style/MissingRespondToMissing
102
103
 
103
104
  private
104
105
 
@@ -106,9 +107,7 @@ module ActiveInteraction
106
107
  # @param name [Symbol]
107
108
  # @param options [Hash]
108
109
  def add_filter(klass, name, options, &block)
109
- if InputProcessor.reserved?(name)
110
- raise InvalidFilterError, %("#{name}" is a reserved name)
111
- end
110
+ raise InvalidFilterError, %("#{name}" is a reserved name) if ActiveInteraction::Inputs.reserved?(name)
112
111
 
113
112
  initialize_filter(klass.new(name, options, &block))
114
113
  end
@@ -133,7 +132,7 @@ module ActiveInteraction
133
132
  other_filters.select! { |k, _| [*only].include?(k) } if only
134
133
  other_filters.reject! { |k, _| [*except].include?(k) } if except
135
134
 
136
- other_filters.values.each { |filter| initialize_filter(filter) }
135
+ other_filters.each_value { |filter| initialize_filter(filter) }
137
136
  end
138
137
 
139
138
  # @param klass [Class]
@@ -146,13 +145,10 @@ module ActiveInteraction
146
145
  # @param filter [Filter]
147
146
  def initialize_filter(filter)
148
147
  attribute = filter.name
149
- if filters.key?(attribute)
150
- warn "WARNING: Redefining #{name}##{attribute} filter"
151
- end
148
+ warn "WARNING: Redefining #{name}##{attribute} filter" if filters.key?(attribute)
152
149
  filters[attribute] = filter
153
150
 
154
151
  attr_accessor attribute
155
- define_method("#{attribute}?") { !public_send(attribute).nil? }
156
152
 
157
153
  eagerly_evaluate_default(filter)
158
154
  end
@@ -195,9 +191,10 @@ module ActiveInteraction
195
191
  #
196
192
  # @return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.
197
193
  def inputs
198
- self.class.filters.keys.each_with_object({}) do |name, h|
199
- h[name] = public_send(name)
200
- end
194
+ @inputs ||= self.class.filters
195
+ .each_key.with_object(ActiveInteraction::Inputs.new) do |name, h|
196
+ h[name] = public_send(name)
197
+ end.freeze
201
198
  end
202
199
 
203
200
  # Returns `true` if the given key was in the hash passed to {.run}.
@@ -294,10 +291,12 @@ module ActiveInteraction
294
291
  @_interaction_inputs = inputs
295
292
 
296
293
  inputs.each do |key, value|
297
- populate_reader(key, value) unless InputProcessor.reserved?(key)
294
+ next if ActiveInteraction::Inputs.reserved?(key)
295
+
296
+ populate_reader(key, value)
298
297
  end
299
298
 
300
- populate_filters(InputProcessor.process(inputs))
299
+ populate_filters(ActiveInteraction::Inputs.process(inputs))
301
300
  end
302
301
 
303
302
  def populate_reader(key, value)
@@ -306,18 +305,16 @@ module ActiveInteraction
306
305
 
307
306
  def populate_filters(inputs)
308
307
  self.class.filters.each do |name, filter|
309
- begin
310
- public_send("#{name}=", filter.clean(inputs[name], self))
311
- rescue InvalidValueError, MissingValueError, NoDefaultError
312
- nil # #type_check will add errors if appropriate.
313
- end
308
+ public_send("#{name}=", filter.clean(inputs[name], self))
309
+ rescue InvalidValueError, MissingValueError, NoDefaultError
310
+ nil # #type_check will add errors if appropriate.
314
311
  end
315
312
  end
316
313
 
317
314
  def type_check
318
315
  run_callbacks(:type_check) do
319
- Validation.validate(self, self.class.filters, inputs).each do |error|
320
- errors.add(*error)
316
+ Validation.validate(self, self.class.filters, inputs).each do |attr, type, kwargs = {}|
317
+ errors.add(attr, type, **kwargs)
321
318
  end
322
319
  end
323
320
  end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module ActiveInteraction
@@ -34,8 +33,7 @@ module ActiveInteraction
34
33
  false
35
34
  end
36
35
 
37
- #
38
- module ClassMethods
36
+ module ClassMethods # rubocop:disable Style/Documentation
39
37
  # @return [Symbol]
40
38
  #
41
39
  # @see ActiveModel::Translation#i18n_scope
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module ActiveInteraction
@@ -24,8 +23,6 @@ module ActiveInteraction
24
23
  # # => nil
25
24
  #
26
25
  # @return [FilterColumn, nil]
27
- #
28
- # @since 1.2.0
29
26
  def column_for_attribute(name)
30
27
  filter = self.class.filters[name]
31
28
  FilterColumn.intern(filter.database_column_type) if filter
@@ -49,9 +46,7 @@ module ActiveInteraction
49
46
  # # => false
50
47
  #
51
48
  # @return [Boolean]
52
- #
53
- # @since 1.5.0
54
- def has_attribute?(name) # rubocop:disable Style/PredicateName
49
+ def has_attribute?(name) # rubocop:disable Naming/PredicateName
55
50
  self.class.filters.key?(name.to_sym)
56
51
  end
57
52
  end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module ActiveInteraction
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module ActiveInteraction
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module ActiveInteraction
@@ -46,9 +45,7 @@ module ActiveInteraction
46
45
 
47
46
  # @return [Boolean]
48
47
  def valid?(*)
49
- if instance_variable_defined?(:@_interaction_valid)
50
- return @_interaction_valid
51
- end
48
+ return @_interaction_valid if instance_variable_defined?(:@_interaction_valid)
52
49
 
53
50
  super
54
51
  end
@@ -75,12 +72,10 @@ module ActiveInteraction
75
72
  return self.result = nil unless valid?
76
73
 
77
74
  self.result = run_callbacks(:execute) do
78
- begin
79
- execute
80
- rescue Interrupt => interrupt
81
- errors.backtrace = interrupt.errors.backtrace || interrupt.backtrace
82
- errors.merge!(interrupt.errors)
83
- end
75
+ execute
76
+ rescue Interrupt => e
77
+ errors.backtrace = e.errors.backtrace || e.backtrace
78
+ errors.merge!(e.errors)
84
79
  end
85
80
  end
86
81
 
@@ -98,8 +93,7 @@ module ActiveInteraction
98
93
  raise e
99
94
  end
100
95
 
101
- #
102
- module ClassMethods
96
+ module ClassMethods # rubocop:disable Style/Documentation
103
97
  def new(*)
104
98
  super.tap do |instance|
105
99
  {
@@ -1,17 +1,15 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- #
5
3
  module ActiveInteraction
6
4
  # Top-level error class. All other errors subclass this.
7
5
  #
8
6
  # @return [Class]
9
7
  Error = Class.new(StandardError)
10
8
 
11
- # Raised if a class name is invalid.
9
+ # Raised if a constant name is invalid.
12
10
  #
13
11
  # @return [Class]
14
- InvalidClassError = Class.new(Error)
12
+ InvalidNameError = Class.new(Error)
15
13
 
16
14
  # Raised if a converter is invalid.
17
15
  #
@@ -151,9 +149,8 @@ module ActiveInteraction
151
149
  if attribute?(attribute) || attribute == :base
152
150
  options = detail.dup
153
151
  error = options.delete(:error)
154
- options[:message] = message
155
152
 
156
- add(attribute, error, options) unless added?(attribute, error, options)
153
+ add(attribute, error, **options.merge(message: message)) unless added?(attribute, error, **options)
157
154
  else
158
155
  merge_message!(attribute, message)
159
156
  end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'active_support/inflector'
@@ -78,16 +77,16 @@ module ActiveInteraction
78
77
  # to the default value.
79
78
  #
80
79
  # @example
81
- # ActiveInteraction::Filter.new(:example).clean(nil)
80
+ # ActiveInteraction::Filter.new(:example).clean(nil, nil)
82
81
  # # => ActiveInteraction::MissingValueError: example
83
82
  # @example
84
- # ActiveInteraction::Filter.new(:example).clean(0)
83
+ # ActiveInteraction::Filter.new(:example).clean(0, nil)
85
84
  # # => ActiveInteraction::InvalidValueError: example: 0
86
85
  # @example
87
- # ActiveInteraction::Filter.new(:example, default: nil).clean(nil)
86
+ # ActiveInteraction::Filter.new(:example, default: nil).clean(nil, nil)
88
87
  # # => nil
89
88
  # @example
90
- # ActiveInteraction::Filter.new(:example, default: 0).clean(nil)
89
+ # ActiveInteraction::Filter.new(:example, default: 0).clean(nil, nil)
91
90
  # # => ActiveInteraction::InvalidDefaultError: example: 0
92
91
  #
93
92
  # @param value [Object]
@@ -95,7 +94,9 @@ module ActiveInteraction
95
94
  #
96
95
  # @return [Object]
97
96
  #
98
- # @raise (see #cast)
97
+ # @raise [MissingValueError] If the value is missing and there is no
98
+ # default.
99
+ # @raise [InvalidValueError] If the value is invalid.
99
100
  # @raise (see #default)
100
101
  def clean(value, context)
101
102
  value = cast(value, context)
@@ -120,7 +121,7 @@ module ActiveInteraction
120
121
  #
121
122
  # @param context [Base, nil]
122
123
  #
123
- # @return (see #raw_default)
124
+ # @return [Object]
124
125
  #
125
126
  # @raise [NoDefaultError] If the default is missing.
126
127
  # @raise [InvalidDefaultError] If the default is invalid.
@@ -131,8 +132,8 @@ module ActiveInteraction
131
132
  raise InvalidValueError if value.is_a?(GroupedInput)
132
133
 
133
134
  cast(value, context)
134
- rescue InvalidNestedValueError => error
135
- raise InvalidDefaultError, "#{name}: #{value.inspect} (#{error})"
135
+ rescue InvalidNestedValueError => e
136
+ raise InvalidDefaultError, "#{name}: #{value.inspect} (#{e})"
136
137
  rescue InvalidValueError, MissingValueError
137
138
  raise InvalidDefaultError, "#{name}: #{value.inspect}"
138
139
  end
@@ -162,27 +163,6 @@ module ActiveInteraction
162
163
  options.key?(:default)
163
164
  end
164
165
 
165
- # @param value [Object]
166
- # @param _interaction [Base, nil]
167
- #
168
- # @return [Object]
169
- #
170
- # @raise [MissingValueError] If the value is missing and there is no
171
- # default.
172
- # @raise [InvalidValueError] If the value is invalid.
173
- #
174
- # @private
175
- def cast(value, _interaction)
176
- case value
177
- when NilClass
178
- raise MissingValueError, name unless default?
179
-
180
- nil
181
- else
182
- raise InvalidValueError, "#{name}: #{describe(value)}"
183
- end
184
- end
185
-
186
166
  # Gets the type of database column that would represent the filter data.
187
167
  #
188
168
  # @example
@@ -194,25 +174,59 @@ module ActiveInteraction
194
174
  #
195
175
  # @return [Symbol] A database column type. If no sensible mapping exists,
196
176
  # returns `:string`.
197
- #
198
- # @since 1.2.0
199
177
  def database_column_type
200
178
  :string
201
179
  end
202
180
 
203
181
  private
204
182
 
205
- # @param value [Object]
206
- # @return [String]
183
+ # rubocop:disable Metrics/MethodLength
184
+ def cast(value, context, convert: true, reconstantize: true)
185
+ if matches?(value)
186
+ adjust_output(value, context)
187
+ # we can't use `nil?` because BasicObject doesn't have it
188
+ elsif value == nil # rubocop:disable Style/NilComparison
189
+ raise MissingValueError, name unless default?
190
+
191
+ nil
192
+ elsif reconstantize
193
+ send(__method__, value, context,
194
+ convert: convert,
195
+ reconstantize: false
196
+ )
197
+ elsif convert
198
+ send(__method__, convert(value), context,
199
+ convert: false,
200
+ reconstantize: reconstantize
201
+ )
202
+ else
203
+ raise InvalidValueError, "#{name}: #{describe(value)}"
204
+ end
205
+ end
206
+ # rubocop:enable Metrics/MethodLength
207
+
208
+ def matches?(_value)
209
+ false
210
+ end
211
+
212
+ def adjust_output(value, _context)
213
+ value
214
+ end
215
+
216
+ def convert(value)
217
+ value
218
+ end
219
+
220
+ def klass
221
+ @klass ||= Object.const_get(self.class.slug.to_s.camelize, false)
222
+ end
223
+
207
224
  def describe(value)
208
225
  value.inspect
209
226
  rescue NoMethodError
210
227
  "(Object doesn't support #inspect)"
211
228
  end
212
229
 
213
- # @param context [Base, nil]
214
- #
215
- # @return [Object]
216
230
  def raw_default(context)
217
231
  value = options.fetch(:default)
218
232
  return value unless value.is_a?(Proc)
@@ -1,10 +1,7 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module ActiveInteraction
5
4
  # A minimal implementation of an `ActiveRecord::ConnectionAdapters::Column`.
6
- #
7
- # @since 1.2.0
8
5
  class FilterColumn
9
6
  # @return [nil]
10
7
  attr_reader :limit