active_interaction 3.8.2 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
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)