active_interaction 0.5.0 → 0.6.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -3
  3. data/README.md +8 -6
  4. data/lib/active_interaction.rb +5 -3
  5. data/lib/active_interaction/active_model.rb +29 -0
  6. data/lib/active_interaction/base.rb +82 -116
  7. data/lib/active_interaction/errors.rb +79 -5
  8. data/lib/active_interaction/filter.rb +195 -21
  9. data/lib/active_interaction/filters.rb +26 -0
  10. data/lib/active_interaction/filters/array_filter.rb +22 -25
  11. data/lib/active_interaction/filters/boolean_filter.rb +12 -12
  12. data/lib/active_interaction/filters/date_filter.rb +32 -5
  13. data/lib/active_interaction/filters/date_time_filter.rb +34 -7
  14. data/lib/active_interaction/filters/file_filter.rb +12 -9
  15. data/lib/active_interaction/filters/float_filter.rb +13 -11
  16. data/lib/active_interaction/filters/hash_filter.rb +36 -17
  17. data/lib/active_interaction/filters/integer_filter.rb +13 -11
  18. data/lib/active_interaction/filters/model_filter.rb +15 -15
  19. data/lib/active_interaction/filters/string_filter.rb +19 -8
  20. data/lib/active_interaction/filters/symbol_filter.rb +29 -0
  21. data/lib/active_interaction/filters/time_filter.rb +38 -16
  22. data/lib/active_interaction/method_missing.rb +18 -0
  23. data/lib/active_interaction/overload_hash.rb +1 -0
  24. data/lib/active_interaction/validation.rb +19 -0
  25. data/lib/active_interaction/version.rb +1 -1
  26. data/spec/active_interaction/active_model_spec.rb +33 -0
  27. data/spec/active_interaction/base_spec.rb +54 -48
  28. data/spec/active_interaction/errors_spec.rb +99 -0
  29. data/spec/active_interaction/filter_spec.rb +12 -20
  30. data/spec/active_interaction/filters/array_filter_spec.rb +50 -28
  31. data/spec/active_interaction/filters/boolean_filter_spec.rb +15 -15
  32. data/spec/active_interaction/filters/date_filter_spec.rb +30 -18
  33. data/spec/active_interaction/filters/date_time_filter_spec.rb +31 -19
  34. data/spec/active_interaction/filters/file_filter_spec.rb +7 -7
  35. data/spec/active_interaction/filters/float_filter_spec.rb +13 -11
  36. data/spec/active_interaction/filters/hash_filter_spec.rb +38 -29
  37. data/spec/active_interaction/filters/integer_filter_spec.rb +18 -8
  38. data/spec/active_interaction/filters/model_filter_spec.rb +24 -20
  39. data/spec/active_interaction/filters/string_filter_spec.rb +14 -8
  40. data/spec/active_interaction/filters/symbol_filter_spec.rb +24 -0
  41. data/spec/active_interaction/filters/time_filter_spec.rb +33 -69
  42. data/spec/active_interaction/filters_spec.rb +21 -0
  43. data/spec/active_interaction/i18n_spec.rb +0 -15
  44. data/spec/active_interaction/integration/array_interaction_spec.rb +2 -22
  45. data/spec/active_interaction/integration/hash_interaction_spec.rb +5 -25
  46. data/spec/active_interaction/integration/symbol_interaction_spec.rb +5 -0
  47. data/spec/active_interaction/method_missing_spec.rb +69 -0
  48. data/spec/active_interaction/validation_spec.rb +55 -0
  49. data/spec/spec_helper.rb +6 -0
  50. data/spec/support/filters.rb +168 -14
  51. data/spec/support/interactions.rb +11 -13
  52. metadata +31 -13
  53. data/lib/active_interaction/filter_method.rb +0 -13
  54. data/lib/active_interaction/filter_methods.rb +0 -26
  55. data/lib/active_interaction/filters/abstract_date_time_filter.rb +0 -25
  56. data/spec/active_interaction/filter_method_spec.rb +0 -43
  57. data/spec/active_interaction/filter_methods_spec.rb +0 -30
@@ -5,8 +5,8 @@ module ActiveInteraction
5
5
  # unless the format option is given, in which case they will be processed
6
6
  # with `strptime`.
7
7
  #
8
- # @macro attribute_method_params
9
- # @option options [String] :format Parse strings using this format string.
8
+ # @macro filter_method_params
9
+ # @option options [String] :format parse strings using this format string
10
10
  #
11
11
  # @example
12
12
  # date :birthday
@@ -14,13 +14,40 @@ module ActiveInteraction
14
14
  # @example
15
15
  # date :birthday, format: '%Y-%m-%d'
16
16
  #
17
+ # @since 0.1.0
18
+ #
17
19
  # @method self.date(*attributes, options = {})
18
20
  end
19
21
 
20
22
  # @private
21
- class DateFilter < AbstractDateTimeFilter
22
- def self.prepare(key, value, options = {}, &block)
23
- super(key, value, options.merge(class: Date), &block)
23
+ class DateFilter < Filter
24
+ def cast(value)
25
+ case value
26
+ when Date
27
+ value
28
+ when String
29
+ begin
30
+ if has_format?
31
+ Date.strptime(value, format)
32
+ else
33
+ Date.parse(value)
34
+ end
35
+ rescue ArgumentError
36
+ super
37
+ end
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def has_format?
46
+ options.has_key?(:format)
47
+ end
48
+
49
+ def format
50
+ options.fetch(:format)
24
51
  end
25
52
  end
26
53
  end
@@ -5,22 +5,49 @@ module ActiveInteraction
5
5
  # unless the format option is given, in which case they will be processed
6
6
  # with `strptime`.
7
7
  #
8
- # @macro attribute_method_params
9
- # @option options [String] :format Parse strings using this format string.
8
+ # @macro filter_method_params
9
+ # @option options [String] :format parse strings using this format string
10
10
  #
11
11
  # @example
12
- # date_time :start_date
12
+ # date :start_date
13
13
  #
14
14
  # @example
15
- # date_time :start_date, format: '%Y-%m-%dT%H:%M:%S'
15
+ # date :start_date, format: '%Y-%m-%dT%H:%M:%S%:z'
16
+ #
17
+ # @since 0.1.0
16
18
  #
17
19
  # @method self.date_time(*attributes, options = {})
18
20
  end
19
21
 
20
22
  # @private
21
- class DateTimeFilter < AbstractDateTimeFilter
22
- def self.prepare(key, value, options = {}, &block)
23
- super(key, value, options.merge(class: DateTime), &block)
23
+ class DateTimeFilter < Filter
24
+ def cast(value)
25
+ case value
26
+ when DateTime
27
+ value
28
+ when String
29
+ begin
30
+ if has_format?
31
+ DateTime.strptime(value, format)
32
+ else
33
+ DateTime.parse(value)
34
+ end
35
+ rescue ArgumentError
36
+ super
37
+ end
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def has_format?
46
+ options.has_key?(:format)
47
+ end
48
+
49
+ def format
50
+ options.fetch(:format)
24
51
  end
25
52
  end
26
53
  end
@@ -1,38 +1,41 @@
1
1
  module ActiveInteraction
2
2
  class Base
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
- # the attributes are Files or TempFiles. It will also extract a file from
4
+ # the attributes are Files or Tempfiles. It will also extract a file from
5
5
  # any object with a `tempfile` method. This is useful when passing in
6
6
  # Rails params that include a file upload.
7
7
  #
8
- # @macro attribute_method_params
8
+ # @macro filter_method_params
9
9
  #
10
10
  # @example
11
11
  # file :image
12
12
  #
13
+ # @since 0.1.0
14
+ #
13
15
  # @method self.file(*attributes, options = {})
14
16
  end
15
17
 
16
18
  # @private
17
19
  class FileFilter < Filter
18
- def self.prepare(key, value, options = {}, &block)
20
+ def cast(value)
19
21
  value = extract_file(value)
20
22
 
21
23
  case value
22
- when File, Tempfile
23
- value
24
- else
25
- super
24
+ when File, Tempfile
25
+ value
26
+ else
27
+ super
26
28
  end
27
29
  end
28
30
 
29
- def self.extract_file(value)
31
+ private
32
+
33
+ def extract_file(value)
30
34
  if value.respond_to?(:tempfile)
31
35
  value.tempfile
32
36
  else
33
37
  value
34
38
  end
35
39
  end
36
- private_class_method :extract_file
37
40
  end
38
41
  end
@@ -4,28 +4,30 @@ module ActiveInteraction
4
4
  # the attributes are Floats. Integer and String values are converted into
5
5
  # Floats.
6
6
  #
7
- # @macro attribute_method_params
7
+ # @macro filter_method_params
8
8
  #
9
9
  # @example
10
10
  # float :amount
11
11
  #
12
+ # @since 0.1.0
13
+ #
12
14
  # @method self.float(*attributes, options = {})
13
15
  end
14
16
 
15
17
  # @private
16
18
  class FloatFilter < Filter
17
- def self.prepare(key, value, options = {}, &block)
19
+ def cast(value)
18
20
  case value
19
- when Float
20
- value
21
- when Integer, String
22
- begin
23
- Float(value)
24
- rescue ArgumentError
25
- super
26
- end
27
- else
21
+ when Numeric
22
+ value.to_f
23
+ when String
24
+ begin
25
+ Float(value)
26
+ rescue ArgumentError
28
27
  super
28
+ end
29
+ else
30
+ super
29
31
  end
30
32
  end
31
33
  end
@@ -3,8 +3,9 @@ module ActiveInteraction
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
4
  # the attributes are Hashes.
5
5
  #
6
- # @macro attribute_method_params
7
- # @param block [Proc] Filter methods to apply for select keys.
6
+ # @macro filter_method_params
7
+ # @param block [Proc] filter methods to apply for select keys
8
+ # @option options [Boolean] :strip (true) strip unknown keys
8
9
  #
9
10
  # @example
10
11
  # hash :order
@@ -17,33 +18,51 @@ module ActiveInteraction
17
18
  # boolean :delivered
18
19
  # end
19
20
  #
21
+ # @since 0.1.0
22
+ #
20
23
  # @method self.hash(*attributes, options = {}, &block)
21
24
  end
22
25
 
23
26
  # @private
24
27
  class HashFilter < Filter
25
- def self.prepare(key, value, options = {}, &block)
28
+ include MethodMissing
29
+
30
+ def cast(value)
26
31
  case value
27
- when Hash
28
- convert_values(value.merge(options[:default] || {}), &block)
29
- else
30
- super
32
+ when Hash
33
+ value = value.symbolize_keys
34
+ filters.reduce(strip? ? {} : value) do |h, f|
35
+ k = f.name
36
+ h[k] = f.clean(value[k])
37
+ h
38
+ end
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def default
45
+ if options[:default].is_a?(Hash) && !options[:default].empty?
46
+ raise InvalidDefaultError, "#{name}: #{options[:default].inspect}"
31
47
  end
48
+
49
+ super
32
50
  end
33
51
 
34
- def self.convert_values(hash, &block)
35
- return hash unless block_given?
52
+ def method_missing(*args, &block)
53
+ super do |klass, names, options|
54
+ raise InvalidFilterError, 'no name' if names.empty?
36
55
 
37
- FilterMethods.evaluate(&block).each do |method|
38
- key = method.attribute
39
- hash[key] = Filter.factory(method.method_name).
40
- prepare(key, hash[key], method.options, &method.block)
56
+ names.each do |name|
57
+ filters.add(klass.new(name, options, &block))
58
+ end
41
59
  end
60
+ end
61
+
62
+ private
42
63
 
43
- hash
44
- rescue InvalidValue, MissingValue
45
- raise InvalidNestedValue
64
+ def strip?
65
+ options.fetch(:strip, true)
46
66
  end
47
- private_class_method :convert_values
48
67
  end
49
68
  end
@@ -3,28 +3,30 @@ module ActiveInteraction
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
4
  # the attributes are Integers. String values are converted into Integers.
5
5
  #
6
- # @macro attribute_method_params
6
+ # @macro filter_method_params
7
7
  #
8
8
  # @example
9
9
  # integer :quantity
10
10
  #
11
+ # @since 0.1.0
12
+ #
11
13
  # @method self.integer(*attributes, options = {})
12
14
  end
13
15
 
14
16
  # @private
15
17
  class IntegerFilter < Filter
16
- def self.prepare(key, value, options = {}, &block)
18
+ def cast(value)
17
19
  case value
18
- when Integer
19
- value
20
- when String
21
- begin
22
- Integer(value)
23
- rescue ArgumentError
24
- super
25
- end
26
- else
20
+ when Numeric
21
+ value.to_i
22
+ when String
23
+ begin
24
+ Integer(value)
25
+ rescue ArgumentError
27
26
  super
27
+ end
28
+ else
29
+ super
28
30
  end
29
31
  end
30
32
  end
@@ -3,7 +3,7 @@ module ActiveInteraction
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
4
  # the attributes are the correct class.
5
5
  #
6
- # @macro attribute_method_params
6
+ # @macro filter_method_params
7
7
  # @option options [Class, String, Symbol] :class (use the attribute name)
8
8
  # Class name used to ensure the value.
9
9
  #
@@ -13,29 +13,29 @@ module ActiveInteraction
13
13
  # @example Ensures that the class is `User`
14
14
  # model :account, class: User
15
15
  #
16
+ # @since 0.1.0
17
+ #
16
18
  # @method self.model(*attributes, options = {})
17
19
  end
18
20
 
19
21
  # @private
20
22
  class ModelFilter < Filter
21
- def self.prepare(key, value, options = {}, &block)
22
- key_class = constantize(options.fetch(:class, key))
23
-
23
+ def cast(value)
24
24
  case value
25
- when key_class
26
- value
27
- else
28
- super
25
+ when klass
26
+ value
27
+ else
28
+ super
29
29
  end
30
30
  end
31
31
 
32
- def self.constantize(constant_name)
33
- if constant_name.is_a?(Symbol) || constant_name.is_a?(String)
34
- constant_name.to_s.classify.constantize
35
- else
36
- constant_name
37
- end
32
+ private
33
+
34
+ def klass
35
+ klass_name = options.fetch(:class, name).to_s.classify
36
+ klass_name.constantize
37
+ rescue NameError
38
+ raise InvalidClassError, klass_name.inspect
38
39
  end
39
- private_class_method :constantize
40
40
  end
41
41
  end
@@ -3,25 +3,36 @@ module ActiveInteraction
3
3
  # Creates accessors for the attributes and ensures that values passed to
4
4
  # the attributes are Strings.
5
5
  #
6
- # @macro attribute_method_params
7
- # @option options [Boolean] :strip (true) Strip leading and trailing
8
- # whitespace.
6
+ # @macro filter_method_params
7
+ # @option options [Boolean] :strip (true) strip leading and trailing
8
+ # whitespace
9
9
  #
10
10
  # @example
11
11
  # string :first_name
12
12
  #
13
+ # @example
14
+ # string :first_name, strip: false
15
+ #
16
+ # @since 0.1.0
17
+ #
13
18
  # @method self.string(*attributes, options = {})
14
19
  end
15
20
 
16
21
  # @private
17
22
  class StringFilter < Filter
18
- def self.prepare(key, value, options = {}, &block)
23
+ def cast(value)
19
24
  case value
20
- when String
21
- options.fetch(:strip, true) ? value.strip : value
22
- else
23
- super
25
+ when String
26
+ strip? ? value.strip : value
27
+ else
28
+ super
24
29
  end
25
30
  end
31
+
32
+ private
33
+
34
+ def strip?
35
+ options.fetch(:strip, true)
36
+ end
26
37
  end
27
38
  end
@@ -0,0 +1,29 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Symbols. Strings will be converted to Symbols.
5
+ #
6
+ # @macro filter_method_params
7
+ #
8
+ # @example
9
+ # symbol :condiment
10
+ #
11
+ # @since 0.6.0
12
+ #
13
+ # @method self.symbol(*attributes, options = {})
14
+ end
15
+
16
+ # @private
17
+ class SymbolFilter < Filter
18
+ def cast(value)
19
+ case value
20
+ when Symbol
21
+ value
22
+ when String
23
+ value.to_sym
24
+ else
25
+ super
26
+ end
27
+ end
28
+ end
29
+ end