active_interaction 3.8.2 → 4.0.3

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 +192 -0
  3. data/README.md +93 -107
  4. data/lib/active_interaction.rb +1 -7
  5. data/lib/active_interaction/base.rb +44 -67
  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 +7 -14
  11. data/lib/active_interaction/errors.rb +4 -19
  12. data/lib/active_interaction/filter.rb +66 -37
  13. data/lib/active_interaction/filter_column.rb +0 -3
  14. data/lib/active_interaction/filters/abstract_date_time_filter.rb +38 -36
  15. data/lib/active_interaction/filters/abstract_numeric_filter.rb +27 -17
  16. data/lib/active_interaction/filters/array_filter.rb +59 -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 +24 -19
  31. data/lib/active_interaction/grouped_input.rb +0 -3
  32. data/lib/active_interaction/inputs.rb +120 -0
  33. data/lib/active_interaction/modules/validation.rb +9 -12
  34. data/lib/active_interaction/version.rb +1 -3
  35. data/spec/active_interaction/base_spec.rb +38 -99
  36. data/spec/active_interaction/concerns/active_modelable_spec.rb +0 -2
  37. data/spec/active_interaction/concerns/active_recordable_spec.rb +0 -2
  38. data/spec/active_interaction/concerns/hashable_spec.rb +0 -2
  39. data/spec/active_interaction/concerns/missable_spec.rb +0 -2
  40. data/spec/active_interaction/concerns/runnable_spec.rb +26 -12
  41. data/spec/active_interaction/errors_spec.rb +4 -25
  42. data/spec/active_interaction/filter_column_spec.rb +0 -2
  43. data/spec/active_interaction/filter_spec.rb +0 -2
  44. data/spec/active_interaction/filters/abstract_date_time_filter_spec.rb +1 -3
  45. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -3
  46. data/spec/active_interaction/filters/array_filter_spec.rb +51 -14
  47. data/spec/active_interaction/filters/boolean_filter_spec.rb +58 -4
  48. data/spec/active_interaction/filters/date_filter_spec.rb +43 -3
  49. data/spec/active_interaction/filters/date_time_filter_spec.rb +43 -3
  50. data/spec/active_interaction/filters/decimal_filter_spec.rb +57 -3
  51. data/spec/active_interaction/filters/file_filter_spec.rb +1 -3
  52. data/spec/active_interaction/filters/float_filter_spec.rb +60 -4
  53. data/spec/active_interaction/filters/hash_filter_spec.rb +19 -9
  54. data/spec/active_interaction/filters/integer_filter_spec.rb +49 -7
  55. data/spec/active_interaction/filters/interface_filter_spec.rb +397 -24
  56. data/spec/active_interaction/filters/object_filter_spec.rb +23 -59
  57. data/spec/active_interaction/filters/record_filter_spec.rb +23 -49
  58. data/spec/active_interaction/filters/string_filter_spec.rb +15 -3
  59. data/spec/active_interaction/filters/symbol_filter_spec.rb +15 -3
  60. data/spec/active_interaction/filters/time_filter_spec.rb +65 -3
  61. data/spec/active_interaction/grouped_input_spec.rb +0 -2
  62. data/spec/active_interaction/i18n_spec.rb +3 -7
  63. data/spec/active_interaction/{modules/input_processor_spec.rb → inputs_spec.rb} +35 -5
  64. data/spec/active_interaction/integration/array_interaction_spec.rb +18 -22
  65. data/spec/active_interaction/integration/boolean_interaction_spec.rb +0 -2
  66. data/spec/active_interaction/integration/date_interaction_spec.rb +0 -2
  67. data/spec/active_interaction/integration/date_time_interaction_spec.rb +0 -2
  68. data/spec/active_interaction/integration/file_interaction_spec.rb +0 -2
  69. data/spec/active_interaction/integration/float_interaction_spec.rb +0 -2
  70. data/spec/active_interaction/integration/hash_interaction_spec.rb +0 -2
  71. data/spec/active_interaction/integration/integer_interaction_spec.rb +0 -2
  72. data/spec/active_interaction/integration/interface_interaction_spec.rb +1 -3
  73. data/spec/active_interaction/integration/object_interaction_spec.rb +0 -2
  74. data/spec/active_interaction/integration/string_interaction_spec.rb +0 -2
  75. data/spec/active_interaction/integration/symbol_interaction_spec.rb +0 -2
  76. data/spec/active_interaction/integration/time_interaction_spec.rb +14 -18
  77. data/spec/active_interaction/modules/validation_spec.rb +1 -3
  78. data/spec/spec_helper.rb +2 -6
  79. data/spec/support/concerns.rb +0 -2
  80. data/spec/support/filters.rb +13 -9
  81. data/spec/support/interactions.rb +22 -14
  82. metadata +81 -57
  83. data/lib/active_interaction/backports.rb +0 -59
  84. data/lib/active_interaction/filters/abstract_filter.rb +0 -19
  85. data/lib/active_interaction/modules/input_processor.rb +0 -52
  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,8 +1,5 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'active_support/core_ext/hash/indifferent_access'
5
-
6
3
  module ActiveInteraction
7
4
  # @abstract Subclass and override {#execute} to implement a custom
8
5
  # ActiveInteraction::Base class.
@@ -28,7 +25,7 @@ module ActiveInteraction
28
25
  # else
29
26
  # outcome.errors
30
27
  # end
31
- class Base # rubocop:disable Metrics/ClassLength
28
+ class Base
32
29
  include ActiveModelable
33
30
  include ActiveRecordable
34
31
  include Runnable
@@ -46,7 +43,7 @@ module ActiveInteraction
46
43
  #
47
44
  # Runs validations and if there are no errors it will call {#execute}.
48
45
  #
49
- # @param (see ActiveInteraction::Base#initialize)
46
+ # @param (see ActiveInteraction::Inputs.process)
50
47
  #
51
48
  # @return [Base]
52
49
 
@@ -74,9 +71,7 @@ module ActiveInteraction
74
71
  # @return [String, nil] The description.
75
72
  def desc(desc = nil)
76
73
  if desc.nil?
77
- unless instance_variable_defined?(:@_interaction_desc)
78
- @_interaction_desc = nil
79
- end
74
+ @_interaction_desc = nil unless instance_variable_defined?(:@_interaction_desc)
80
75
  else
81
76
  @_interaction_desc = desc
82
77
  end
@@ -88,17 +83,21 @@ module ActiveInteraction
88
83
  #
89
84
  # @return [Hash{Symbol => Filter}]
90
85
  def filters
86
+ # rubocop:disable Naming/MemoizedInstanceVariableName
91
87
  @_interaction_filters ||= {}
88
+ # rubocop:enable Naming/MemoizedInstanceVariableName
92
89
  end
93
90
 
94
91
  # @private
95
- def method_missing(*args, &block) # rubocop:disable Style/MethodMissing
92
+ # rubocop:disable Style/MissingRespondToMissing
93
+ def method_missing(*args, &block)
96
94
  super do |klass, names, options|
97
95
  raise InvalidFilterError, 'missing attribute name' if names.empty?
98
96
 
99
97
  names.each { |name| add_filter(klass, name, options, &block) }
100
98
  end
101
99
  end
100
+ # rubocop:enable Style/MissingRespondToMissing
102
101
 
103
102
  private
104
103
 
@@ -106,9 +105,7 @@ module ActiveInteraction
106
105
  # @param name [Symbol]
107
106
  # @param options [Hash]
108
107
  def add_filter(klass, name, options, &block)
109
- if InputProcessor.reserved?(name)
110
- raise InvalidFilterError, %("#{name}" is a reserved name)
111
- end
108
+ raise InvalidFilterError, %("#{name}" is a reserved name) if Inputs.reserved?(name)
112
109
 
113
110
  initialize_filter(klass.new(name, options, &block))
114
111
  end
@@ -133,7 +130,7 @@ module ActiveInteraction
133
130
  other_filters.select! { |k, _| [*only].include?(k) } if only
134
131
  other_filters.reject! { |k, _| [*except].include?(k) } if except
135
132
 
136
- other_filters.values.each { |filter| initialize_filter(filter) }
133
+ other_filters.each_value { |filter| initialize_filter(filter) }
137
134
  end
138
135
 
139
136
  # @param klass [Class]
@@ -146,13 +143,10 @@ module ActiveInteraction
146
143
  # @param filter [Filter]
147
144
  def initialize_filter(filter)
148
145
  attribute = filter.name
149
- if filters.key?(attribute)
150
- warn "WARNING: Redefining #{name}##{attribute} filter"
151
- end
146
+ warn "WARNING: Redefining #{name}##{attribute} filter" if filters.key?(attribute)
152
147
  filters[attribute] = filter
153
148
 
154
149
  attr_accessor attribute
155
- define_method("#{attribute}?") { !public_send(attribute).nil? }
156
150
 
157
151
  eagerly_evaluate_default(filter)
158
152
  end
@@ -164,12 +158,11 @@ module ActiveInteraction
164
158
  end
165
159
  end
166
160
 
167
- # @param inputs [Hash{Symbol => Object}] Attribute values to set.
168
- #
169
161
  # @private
170
162
  def initialize(inputs = {})
171
- inputs = normalize_inputs!(inputs)
172
- process_inputs(inputs.symbolize_keys)
163
+ @_interaction_raw_inputs = inputs
164
+
165
+ populate_filters_and_inputs(Inputs.process(inputs))
173
166
  end
174
167
 
175
168
  # @!method compose(other, inputs = {})
@@ -195,9 +188,7 @@ module ActiveInteraction
195
188
  #
196
189
  # @return [Hash{Symbol => Object}] All inputs passed to {.run} or {.run!}.
197
190
  def inputs
198
- self.class.filters.keys.each_with_object({}) do |name, h|
199
- h[name] = public_send(name)
200
- end
191
+ @_interaction_inputs
201
192
  end
202
193
 
203
194
  # Returns `true` if the given key was in the hash passed to {.run}.
@@ -241,24 +232,30 @@ module ActiveInteraction
241
232
  # rubocop:disable all
242
233
  def given?(input, *rest)
243
234
  filter_level = self.class
244
- input_level = @_interaction_inputs
235
+ input_level = @_interaction_raw_inputs
245
236
 
246
237
  [input, *rest].each do |key_or_index|
247
238
  if key_or_index.is_a?(Symbol) || key_or_index.is_a?(String)
248
- key_or_index = key_or_index.to_sym
249
- filter_level = filter_level.filters[key_or_index]
239
+ key = key_or_index.to_sym
240
+ key_to_s = key_or_index.to_s
241
+ filter_level = filter_level.filters[key]
250
242
 
251
243
  break false if filter_level.nil? || input_level.nil?
252
- break false unless input_level.key?(key_or_index) || input_level.key?(key_or_index.to_s)
244
+ if filter_level.accepts_grouped_inputs?
245
+ break false unless input_level.key?(key) || input_level.key?(key_to_s) || Inputs.keys_for_group?(input_level.keys, key)
246
+ else
247
+ break false unless input_level.key?(key) || input_level.key?(key_to_s)
248
+ end
253
249
 
254
- input_level = input_level[key_or_index] || input_level[key_or_index.to_s]
250
+ input_level = input_level[key] || input_level[key_to_s]
255
251
  else
252
+ index = key_or_index
256
253
  filter_level = filter_level.filters.first.last
257
254
 
258
255
  break false if filter_level.nil? || input_level.nil?
259
- break false unless key_or_index.between?(-input_level.size, input_level.size - 1)
256
+ break false unless index.between?(-input_level.size, input_level.size - 1)
260
257
 
261
- input_level = input_level[key_or_index]
258
+ input_level = input_level[index]
262
259
  end
263
260
  end && true
264
261
  end
@@ -274,50 +271,30 @@ module ActiveInteraction
274
271
 
275
272
  private
276
273
 
277
- # We want to allow both `Hash` objects and `ActionController::Parameters`
278
- # objects. In Rails < 5, parameters are a subclass of hash and calling
279
- # `#symbolize_keys` returns the entire hash, not just permitted values. In
280
- # Rails >= 5, parameters are not a subclass of hash but calling
281
- # `#to_unsafe_h` returns the entire hash.
282
- def normalize_inputs!(inputs)
283
- return inputs if inputs.is_a?(Hash)
284
-
285
- parameters = 'ActionController::Parameters'
286
- klass = parameters.safe_constantize
287
- return inputs.to_unsafe_h if klass && inputs.is_a?(klass)
288
-
289
- raise ArgumentError, "inputs must be a hash or #{parameters}"
290
- end
274
+ def populate_filters_and_inputs(inputs)
275
+ @_interaction_inputs = Inputs.new
291
276
 
292
- # @param inputs [Hash{Symbol => Object}]
293
- def process_inputs(inputs)
294
- @_interaction_inputs = inputs
277
+ self.class.filters.each do |name, filter|
278
+ value =
279
+ begin
280
+ filter.clean(inputs[name], self)
281
+ rescue InvalidValueError, MissingValueError, NoDefaultError
282
+ # #type_check will add errors if appropriate.
283
+ # We'll get the original value for the error.
284
+ inputs[name]
285
+ end
295
286
 
296
- inputs.each do |key, value|
297
- populate_reader(key, value) unless InputProcessor.reserved?(key)
287
+ @_interaction_inputs[name] = value
288
+ public_send("#{name}=", value)
298
289
  end
299
290
 
300
- populate_filters(InputProcessor.process(inputs))
301
- end
302
-
303
- def populate_reader(key, value)
304
- instance_variable_set("@#{key}", value) if respond_to?(key)
305
- end
306
-
307
- def populate_filters(inputs)
308
- 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
314
- end
291
+ @_interaction_inputs.freeze
315
292
  end
316
293
 
317
294
  def type_check
318
295
  run_callbacks(:type_check) do
319
- Validation.validate(self, self.class.filters, inputs).each do |error|
320
- errors.add(*error)
296
+ Validation.validate(self, self.class.filters, inputs).each do |attr, type, kwargs = {}|
297
+ errors.add(attr, type, **kwargs)
321
298
  end
322
299
  end
323
300
  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
@@ -74,14 +71,11 @@ module ActiveInteraction
74
71
  def run
75
72
  return self.result = nil unless valid?
76
73
 
77
- run_callbacks(:execute) do
78
- self.result =
79
- begin
80
- execute
81
- rescue Interrupt => interrupt
82
- errors.backtrace = interrupt.errors.backtrace || interrupt.backtrace
83
- errors.merge!(interrupt.errors)
84
- end
74
+ self.result = run_callbacks(:execute) do
75
+ execute
76
+ rescue Interrupt => e
77
+ errors.backtrace = e.errors.backtrace || e.backtrace
78
+ errors.merge!(e.errors)
85
79
  end
86
80
  end
87
81
 
@@ -99,8 +93,7 @@ module ActiveInteraction
99
93
  raise e
100
94
  end
101
95
 
102
- #
103
- module ClassMethods
96
+ module ClassMethods # rubocop:disable Style/Documentation
104
97
  def new(*)
105
98
  super.tap do |instance|
106
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
  #
@@ -100,11 +98,7 @@ module ActiveInteraction
100
98
  #
101
99
  # @return [Errors]
102
100
  def merge!(other)
103
- if other.respond_to?(:details)
104
- merge_details!(other)
105
- else
106
- merge_messages!(other)
107
- end
101
+ merge_details!(other)
108
102
 
109
103
  self
110
104
  end
@@ -119,14 +113,6 @@ module ActiveInteraction
119
113
  detail[:error].is_a?(Symbol)
120
114
  end
121
115
 
122
- def merge_messages!(other)
123
- other.messages.each do |attribute, messages|
124
- messages.each do |message|
125
- merge_message!(attribute, message)
126
- end
127
- end
128
- end
129
-
130
116
  def merge_message!(attribute, message)
131
117
  unless attribute?(attribute)
132
118
  message = full_message(attribute, message)
@@ -151,9 +137,8 @@ module ActiveInteraction
151
137
  if attribute?(attribute) || attribute == :base
152
138
  options = detail.dup
153
139
  error = options.delete(:error)
154
- options[:message] = message
155
140
 
156
- add(attribute, error, options) unless added?(attribute, error, options)
141
+ add(attribute, error, **options.merge(message: message)) unless added?(attribute, error, **options)
157
142
  else
158
143
  merge_message!(attribute, message)
159
144
  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,74 @@ 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
 
181
+ # Tells whether or not the filter accepts a group of parameters to form a
182
+ # single input.
183
+ #
184
+ # @example
185
+ # ActiveInteraction::TimeFilter.new(Time.now).accepts_grouped_inputs?
186
+ # # => true
187
+ # @example
188
+ # ActiveInteraction::Filter.new(:example).accepts_grouped_inputs?
189
+ # # => false
190
+ #
191
+ # @return [Boolean]
192
+ def accepts_grouped_inputs?
193
+ false
194
+ end
195
+
203
196
  private
204
197
 
205
- # @param value [Object]
206
- # @return [String]
198
+ # rubocop:disable Metrics/MethodLength
199
+ def cast(value, context, convert: true, reconstantize: true)
200
+ if matches?(value)
201
+ adjust_output(value, context)
202
+ # we can't use `nil?` because BasicObject doesn't have it
203
+ elsif value == nil # rubocop:disable Style/NilComparison
204
+ raise MissingValueError, name unless default?
205
+
206
+ nil
207
+ elsif reconstantize
208
+ send(__method__, value, context,
209
+ convert: convert,
210
+ reconstantize: false
211
+ )
212
+ elsif convert
213
+ send(__method__, convert(value), context,
214
+ convert: false,
215
+ reconstantize: reconstantize
216
+ )
217
+ else
218
+ raise InvalidValueError, "#{name}: #{describe(value)}"
219
+ end
220
+ end
221
+ # rubocop:enable Metrics/MethodLength
222
+
223
+ def matches?(_value)
224
+ false
225
+ end
226
+
227
+ def adjust_output(value, _context)
228
+ value
229
+ end
230
+
231
+ def convert(value)
232
+ value
233
+ end
234
+
235
+ def klass
236
+ @klass ||= Object.const_get(self.class.slug.to_s.camelize, false)
237
+ end
238
+
207
239
  def describe(value)
208
240
  value.inspect
209
241
  rescue NoMethodError
210
242
  "(Object doesn't support #inspect)"
211
243
  end
212
244
 
213
- # @param context [Base, nil]
214
- #
215
- # @return [Object]
216
245
  def raw_default(context)
217
246
  value = options.fetch(:default)
218
247
  return value unless value.is_a?(Proc)