active_interaction 0.10.2 → 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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +32 -35
  4. data/lib/active_interaction.rb +14 -6
  5. data/lib/active_interaction/base.rb +155 -135
  6. data/lib/active_interaction/concerns/active_modelable.rb +46 -0
  7. data/lib/active_interaction/{modules/overload_hash.rb → concerns/hashable.rb} +5 -1
  8. data/lib/active_interaction/concerns/missable.rb +47 -0
  9. data/lib/active_interaction/concerns/runnable.rb +156 -0
  10. data/lib/active_interaction/errors.rb +50 -14
  11. data/lib/active_interaction/filter.rb +33 -45
  12. data/lib/active_interaction/filters/abstract_date_time_filter.rb +24 -15
  13. data/lib/active_interaction/filters/abstract_filter.rb +18 -0
  14. data/lib/active_interaction/filters/abstract_numeric_filter.rb +13 -8
  15. data/lib/active_interaction/filters/array_filter.rb +42 -35
  16. data/lib/active_interaction/filters/boolean_filter.rb +8 -11
  17. data/lib/active_interaction/filters/date_filter.rb +11 -15
  18. data/lib/active_interaction/filters/date_time_filter.rb +11 -15
  19. data/lib/active_interaction/filters/file_filter.rb +11 -11
  20. data/lib/active_interaction/filters/float_filter.rb +7 -15
  21. data/lib/active_interaction/filters/hash_filter.rb +18 -24
  22. data/lib/active_interaction/filters/integer_filter.rb +7 -14
  23. data/lib/active_interaction/filters/model_filter.rb +13 -14
  24. data/lib/active_interaction/filters/string_filter.rb +11 -14
  25. data/lib/active_interaction/filters/symbol_filter.rb +6 -9
  26. data/lib/active_interaction/filters/time_filter.rb +13 -16
  27. data/lib/active_interaction/modules/validation.rb +9 -4
  28. data/lib/active_interaction/version.rb +5 -2
  29. data/spec/active_interaction/base_spec.rb +109 -4
  30. data/spec/active_interaction/{modules/active_model_spec.rb → concerns/active_modelable_spec.rb} +15 -3
  31. data/spec/active_interaction/{modules/overload_hash_spec.rb → concerns/hashable_spec.rb} +2 -3
  32. data/spec/active_interaction/{modules/method_missing_spec.rb → concerns/missable_spec.rb} +38 -3
  33. data/spec/active_interaction/concerns/runnable_spec.rb +192 -0
  34. data/spec/active_interaction/filters/abstract_filter_spec.rb +8 -0
  35. data/spec/active_interaction/filters/abstract_numeric_filter_spec.rb +1 -1
  36. data/spec/active_interaction/modules/validation_spec.rb +2 -2
  37. data/spec/support/concerns.rb +15 -0
  38. data/spec/support/filters.rb +5 -5
  39. data/spec/support/interactions.rb +6 -5
  40. metadata +47 -73
  41. data/lib/active_interaction/filters.rb +0 -28
  42. data/lib/active_interaction/modules/active_model.rb +0 -32
  43. data/lib/active_interaction/modules/core.rb +0 -70
  44. data/lib/active_interaction/modules/method_missing.rb +0 -20
  45. data/spec/active_interaction/filters_spec.rb +0 -23
  46. data/spec/active_interaction/modules/core_spec.rb +0 -114
@@ -1,22 +1,22 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module ActiveInteraction
4
+ # @abstract
5
+ #
6
+ # Common logic for filters that handle `Date`, `DateTime`, and `Time`
7
+ # objects.
8
+ #
4
9
  # @private
5
- class AbstractDateTimeFilter < Filter
10
+ class AbstractDateTimeFilter < AbstractFilter
11
+ alias_method :_cast, :cast
12
+ private :_cast
13
+
6
14
  def cast(value)
7
15
  case value
8
16
  when *klasses
9
17
  value
10
18
  when String
11
- begin
12
- if has_format?
13
- klass.strptime(value, format)
14
- else
15
- klass.parse(value)
16
- end
17
- rescue ArgumentError
18
- super
19
- end
19
+ convert(value)
20
20
  else
21
21
  super
22
22
  end
@@ -24,18 +24,27 @@ module ActiveInteraction
24
24
 
25
25
  private
26
26
 
27
+ def convert(value)
28
+ if format?
29
+ klass.strptime(value, format)
30
+ else
31
+ klass.parse(value)
32
+ end
33
+ rescue ArgumentError
34
+ _cast(value)
35
+ end
36
+
37
+ # @return [String]
27
38
  def format
28
39
  options.fetch(:format)
29
40
  end
30
41
 
31
- def has_format?
42
+ # @return [Boolean]
43
+ def format?
32
44
  options.key?(:format)
33
45
  end
34
46
 
35
- def klass
36
- self.class.slug.to_s.camelize.constantize
37
- end
38
-
47
+ # @return [Array<Class>]
39
48
  def klasses
40
49
  [klass]
41
50
  end
@@ -0,0 +1,18 @@
1
+ # coding: utf-8
2
+
3
+ module ActiveInteraction
4
+ # @abstract
5
+ #
6
+ # Common logic for filters that can guess the class they operator on based on
7
+ # their name.
8
+ #
9
+ # @private
10
+ class AbstractFilter < Filter
11
+ private
12
+
13
+ # @return [Class]
14
+ def klass
15
+ self.class.slug.to_s.camelize.constantize
16
+ end
17
+ end
18
+ end
@@ -1,18 +1,21 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module ActiveInteraction
4
+ # @abstract
5
+ #
6
+ # Common logic for filters that handle numeric objects.
7
+ #
4
8
  # @private
5
- class AbstractNumericFilter < Filter
9
+ class AbstractNumericFilter < AbstractFilter
10
+ alias_method :_cast, :cast
11
+ private :_cast
12
+
6
13
  def cast(value)
7
14
  case value
8
15
  when klass
9
16
  value
10
17
  when Numeric, String
11
- begin
12
- send(klass.name, value)
13
- rescue ArgumentError
14
- super
15
- end
18
+ convert(value)
16
19
  else
17
20
  super
18
21
  end
@@ -20,8 +23,10 @@ module ActiveInteraction
20
23
 
21
24
  private
22
25
 
23
- def klass
24
- fail NotImplementedError
26
+ def convert(value)
27
+ Kernel.public_send(klass.name, value)
28
+ rescue ArgumentError
29
+ _cast(value)
25
30
  end
26
31
  end
27
32
  end
@@ -2,61 +2,68 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are Arrays.
5
+ # @!method self.array(*attributes, options = {}, &block)
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are Arrays.
7
8
  #
8
- # @macro filter_method_params
9
- # @param block [Proc] filter method to apply to each element
9
+ # @!macro filter_method_params
10
+ # @param block [Proc] filter method to apply to each element
10
11
  #
11
- # @example
12
- # array :ids
13
- #
14
- # @example An Array of Integers
15
- # array :ids do
16
- # integer
17
- # end
18
- #
19
- # @example An Array of Integers where some or all are nil
20
- # array :ids do
21
- # integer default: nil
22
- # end
23
- #
24
- # @since 0.1.0
25
- #
26
- # @method self.array(*attributes, options = {}, &block)
12
+ # @example
13
+ # array :ids
14
+ # @example
15
+ # array :ids do
16
+ # integer
17
+ # end
18
+ # @example
19
+ # array :ids do
20
+ # integer default: nil
21
+ # end
27
22
  end
28
23
 
29
24
  # @private
30
25
  class ArrayFilter < Filter
31
- include MethodMissing
26
+ include Missable
32
27
 
33
28
  def cast(value)
34
29
  case value
35
30
  when Array
36
- return value if filters.none?
31
+ return value if filters.empty?
37
32
 
38
- filter = filters.first
33
+ filter = filters.values.first
39
34
  value.map { |e| filter.clean(e) }
40
35
  else
41
36
  super
42
37
  end
43
38
  end
44
39
 
45
- def method_missing(*_, &block)
40
+ def method_missing(*, &block)
46
41
  super do |klass, names, options|
47
42
  filter = klass.new(name, options, &block)
48
43
 
49
- if filters.any?
50
- fail InvalidFilterError, 'multiple filters in array block'
51
- end
52
- unless names.empty?
53
- fail InvalidFilterError, 'attribute names in array block'
54
- end
55
- if filter.has_default?
56
- fail InvalidDefaultError, 'default values in array block'
57
- end
58
-
59
- filters.add(filter)
44
+ validate(filter, names)
45
+
46
+ filters[name] = filter
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # @param filter [Filter]
53
+ # @param names [Array<Symbol>]
54
+ #
55
+ # @raise [InvalidFilterError]
56
+ def validate(filter, names)
57
+ unless filters.empty?
58
+ fail InvalidFilterError, 'multiple filters in array block'
59
+ end
60
+
61
+ unless names.empty?
62
+ fail InvalidFilterError, 'attribute names in array block'
63
+ end
64
+
65
+ if filter.default?
66
+ fail InvalidDefaultError, 'default values in array block'
60
67
  end
61
68
  end
62
69
  end
@@ -2,19 +2,16 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are Booleans. The strings `"1"` and `"true"`
7
- # (case-insensitive) are converted to `true` while the strings `"0"` and
8
- # `"false"` are converted to `false`.
5
+ # @!method self.boolean(*attributes, options = {})
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are Booleans. The strings `"1"` and `"true"`
8
+ # (case-insensitive) are converted to `true` while the strings `"0"`
9
+ # and `"false"` are converted to `false`.
9
10
  #
10
- # @macro filter_method_params
11
+ # @!macro filter_method_params
11
12
  #
12
- # @example
13
- # boolean :subscribed
14
- #
15
- # @since 0.1.0
16
- #
17
- # @method self.boolean(*attributes, options = {})
13
+ # @example
14
+ # boolean :subscribed
18
15
  end
19
16
 
20
17
  # @private
@@ -2,23 +2,19 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are Dates. String values are processed using `parse`
7
- # unless the format option is given, in which case they will be processed
8
- # with `strptime`.
5
+ # @!method self.date(*attributes, options = {})
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are Dates. String values are processed using `parse`
8
+ # unless the format option is given, in which case they will be
9
+ # processed with `strptime`.
9
10
  #
10
- # @macro filter_method_params
11
- # @option options [String] :format parse strings using this format string
11
+ # @!macro filter_method_params
12
+ # @option options [String] :format parse strings using this format string
12
13
  #
13
- # @example
14
- # date :birthday
15
- #
16
- # @example
17
- # date :birthday, format: '%Y-%m-%d'
18
- #
19
- # @since 0.1.0
20
- #
21
- # @method self.date(*attributes, options = {})
14
+ # @example
15
+ # date :birthday
16
+ # @example
17
+ # date :birthday, format: '%Y-%m-%d'
22
18
  end
23
19
 
24
20
  # @private
@@ -2,23 +2,19 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are DateTimes. String values are processed using `parse`
7
- # unless the format option is given, in which case they will be processed
8
- # with `strptime`.
5
+ # @!method self.date_time(*attributes, options = {})
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are DateTimes. String values are processed using
8
+ # `parse` unless the format option is given, in which case they will be
9
+ # processed with `strptime`.
9
10
  #
10
- # @macro filter_method_params
11
- # @option options [String] :format parse strings using this format string
11
+ # @!macro filter_method_params
12
+ # @option options [String] :format parse strings using this format string
12
13
  #
13
- # @example
14
- # date_time :start_date
15
- #
16
- # @example
17
- # date_time :start_date, format: '%Y-%m-%dT%H:%M:%S%:z'
18
- #
19
- # @since 0.1.0
20
- #
21
- # @method self.date_time(*attributes, options = {})
14
+ # @example
15
+ # date_time :start_date
16
+ # @example
17
+ # date_time :start_date, format: '%Y-%m-%dT%H:%M:%SZ'
22
18
  end
23
19
 
24
20
  # @private
@@ -2,19 +2,16 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are Files or Tempfiles. It will also extract a file from
7
- # any object with a `tempfile` method. This is useful when passing in
8
- # Rails params that include a file upload.
5
+ # @!method self.file(*attributes, options = {})
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are Files or Tempfiles. It will also extract a file
8
+ # from any object with a `tempfile` method. This is useful when passing
9
+ # in Rails params that include a file upload.
9
10
  #
10
- # @macro filter_method_params
11
+ # @!macro filter_method_params
11
12
  #
12
- # @example
13
- # file :image
14
- #
15
- # @since 0.1.0
16
- #
17
- # @method self.file(*attributes, options = {})
13
+ # @example
14
+ # file :image
18
15
  end
19
16
 
20
17
  # @private
@@ -32,6 +29,9 @@ module ActiveInteraction
32
29
 
33
30
  private
34
31
 
32
+ # @param value [File, #tempfile]
33
+ #
34
+ # @return [File]
35
35
  def extract_file(value)
36
36
  if value.respond_to?(:tempfile)
37
37
  value.tempfile
@@ -2,26 +2,18 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are Floats. Integer and String values are converted into
7
- # Floats.
5
+ # @!method self.float(*attributes, options = {})
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are Floats. Integer and String values are converted
8
+ # into Floats.
8
9
  #
9
- # @macro filter_method_params
10
+ # @!macro filter_method_params
10
11
  #
11
- # @example
12
- # float :amount
13
- #
14
- # @since 0.1.0
15
- #
16
- # @method self.float(*attributes, options = {})
12
+ # @example
13
+ # float :amount
17
14
  end
18
15
 
19
16
  # @private
20
17
  class FloatFilter < AbstractNumericFilter
21
- private
22
-
23
- def klass
24
- Float
25
- end
26
18
  end
27
19
  end
@@ -2,40 +2,33 @@
2
2
 
3
3
  module ActiveInteraction
4
4
  class Base
5
- # Creates accessors for the attributes and ensures that values passed to
6
- # the attributes are Hashes.
5
+ # @!method self.hash(*attributes, options = {}, &block)
6
+ # Creates accessors for the attributes and ensures that values passed to
7
+ # the attributes are Hashes.
7
8
  #
8
- # @macro filter_method_params
9
- # @param block [Proc] filter methods to apply for select keys
10
- # @option options [Boolean] :strip (true) strip unknown keys
9
+ # @!macro filter_method_params
10
+ # @param block [Proc] filter methods to apply for select keys
11
+ # @option options [Boolean] :strip (true) strip unknown keys
11
12
  #
12
- # @example
13
- # hash :order
14
- #
15
- # @example A Hash where certain keys also have their values ensured.
16
- # hash :order do
17
- # model :account
18
- # model :item
19
- # integer :quantity
20
- # boolean :delivered
21
- # end
22
- #
23
- # @since 0.1.0
24
- #
25
- # @method self.hash(*attributes, options = {}, &block)
13
+ # @example
14
+ # hash :order
15
+ # @example
16
+ # hash :order do
17
+ # model :item
18
+ # integer :quantity, default: 1
19
+ # end
26
20
  end
27
21
 
28
22
  # @private
29
23
  class HashFilter < Filter
30
- include MethodMissing
24
+ include Missable
31
25
 
32
26
  def cast(value)
33
27
  case value
34
28
  when Hash
35
29
  value = value.symbolize_keys
36
- filters.each_with_object(strip? ? {} : value) do |filter, h|
37
- k = filter.name
38
- h[k] = filter.clean(value[k])
30
+ filters.each_with_object(strip? ? {} : value) do |(name, filter), h|
31
+ h[name] = filter.clean(value[name])
39
32
  end
40
33
  else
41
34
  super
@@ -55,13 +48,14 @@ module ActiveInteraction
55
48
  fail InvalidFilterError, 'missing attribute name' if names.empty?
56
49
 
57
50
  names.each do |name|
58
- filters.add(klass.new(name, options, &block))
51
+ filters[name] = klass.new(name, options, &block)
59
52
  end
60
53
  end
61
54
  end
62
55
 
63
56
  private
64
57
 
58
+ # @return [Boolean]
65
59
  def strip?
66
60
  options.fetch(:strip, true)
67
61
  end