active_interaction 0.10.2 → 1.0.0

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