active_interaction 0.1.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 (53) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +202 -0
  4. data/lib/active_interaction.rb +23 -0
  5. data/lib/active_interaction/base.rb +162 -0
  6. data/lib/active_interaction/errors.rb +6 -0
  7. data/lib/active_interaction/filter.rb +41 -0
  8. data/lib/active_interaction/filter_method.rb +17 -0
  9. data/lib/active_interaction/filter_methods.rb +26 -0
  10. data/lib/active_interaction/filters/array_filter.rb +56 -0
  11. data/lib/active_interaction/filters/boolean_filter.rb +30 -0
  12. data/lib/active_interaction/filters/date_filter.rb +31 -0
  13. data/lib/active_interaction/filters/date_time_filter.rb +31 -0
  14. data/lib/active_interaction/filters/file_filter.rb +38 -0
  15. data/lib/active_interaction/filters/float_filter.rb +32 -0
  16. data/lib/active_interaction/filters/hash_filter.rb +47 -0
  17. data/lib/active_interaction/filters/integer_filter.rb +31 -0
  18. data/lib/active_interaction/filters/model_filter.rb +40 -0
  19. data/lib/active_interaction/filters/string_filter.rb +25 -0
  20. data/lib/active_interaction/filters/time_filter.rb +44 -0
  21. data/lib/active_interaction/overload_hash.rb +11 -0
  22. data/lib/active_interaction/version.rb +3 -0
  23. data/spec/active_interaction/base_spec.rb +175 -0
  24. data/spec/active_interaction/filter_method_spec.rb +48 -0
  25. data/spec/active_interaction/filter_methods_spec.rb +30 -0
  26. data/spec/active_interaction/filter_spec.rb +29 -0
  27. data/spec/active_interaction/filters/array_filter_spec.rb +54 -0
  28. data/spec/active_interaction/filters/boolean_filter_spec.rb +40 -0
  29. data/spec/active_interaction/filters/date_filter_spec.rb +32 -0
  30. data/spec/active_interaction/filters/date_time_filter_spec.rb +32 -0
  31. data/spec/active_interaction/filters/file_filter_spec.rb +32 -0
  32. data/spec/active_interaction/filters/float_filter_spec.rb +40 -0
  33. data/spec/active_interaction/filters/hash_filter_spec.rb +57 -0
  34. data/spec/active_interaction/filters/integer_filter_spec.rb +32 -0
  35. data/spec/active_interaction/filters/model_filter_spec.rb +40 -0
  36. data/spec/active_interaction/filters/string_filter_spec.rb +16 -0
  37. data/spec/active_interaction/filters/time_filter_spec.rb +66 -0
  38. data/spec/active_interaction/integration/array_interaction_spec.rb +69 -0
  39. data/spec/active_interaction/integration/boolean_interaction_spec.rb +5 -0
  40. data/spec/active_interaction/integration/date_interaction_spec.rb +5 -0
  41. data/spec/active_interaction/integration/date_time_interaction_spec.rb +5 -0
  42. data/spec/active_interaction/integration/file_interaction_spec.rb +5 -0
  43. data/spec/active_interaction/integration/float_interaction_spec.rb +5 -0
  44. data/spec/active_interaction/integration/hash_interaction_spec.rb +69 -0
  45. data/spec/active_interaction/integration/integer_interaction_spec.rb +5 -0
  46. data/spec/active_interaction/integration/model_interaction_spec.rb +5 -0
  47. data/spec/active_interaction/integration/string_interaction_spec.rb +5 -0
  48. data/spec/active_interaction/integration/time_interaction_spec.rb +5 -0
  49. data/spec/active_interaction/overload_hash_spec.rb +41 -0
  50. data/spec/spec_helper.rb +6 -0
  51. data/spec/support/filters.rb +37 -0
  52. data/spec/support/interactions.rb +79 -0
  53. metadata +278 -0
@@ -0,0 +1,17 @@
1
+ module ActiveInteraction
2
+ # @private
3
+ class FilterMethod
4
+ attr_reader :method_name, :attribute, :options, :block
5
+
6
+ def initialize(method_name, *args, &block)
7
+ @method_name, @block = method_name, block
8
+
9
+ @attribute = args.shift if args.first.is_a?(Symbol)
10
+ @options = (args.first || {}).dup
11
+
12
+ if @options.include?(:default)
13
+ raise ArgumentError, ':default is not supported inside filter blocks'
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,26 @@
1
+ module ActiveInteraction
2
+ # @private
3
+ class FilterMethods
4
+ include Enumerable
5
+ include OverloadHash
6
+
7
+ def self.evaluate(&block)
8
+ me = new
9
+ me.instance_eval(&block)
10
+ me
11
+ end
12
+
13
+ def initialize
14
+ @filter_methods = []
15
+ end
16
+
17
+ def each(&block)
18
+ @filter_methods.each(&block)
19
+ end
20
+
21
+ def method_missing(filter_type, *args, &block)
22
+ @filter_methods.push(FilterMethod.new(filter_type, *args, &block))
23
+ end
24
+ private :method_missing
25
+ end
26
+ end
@@ -0,0 +1,56 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Arrays.
5
+ #
6
+ # @macro attribute_method_params
7
+ # @param block [Proc] A filter method to apply to each element.
8
+ #
9
+ # @example
10
+ # array :ids
11
+ #
12
+ # @example An Array of Integers
13
+ # array :ids do
14
+ # integer
15
+ # end
16
+ #
17
+ # @example An Array of Integers where some or all are nil
18
+ # array :ids do
19
+ # integer allow_nil: true
20
+ # end
21
+ #
22
+ # @method self.array(*attributes, options = {}, &block)
23
+ end
24
+
25
+ # @private
26
+ class ArrayFilter < Filter
27
+ def self.prepare(key, value, options = {}, &block)
28
+ case value
29
+ when Array
30
+ convert_values(value, &block)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
36
+ def self.convert_values(values, &block)
37
+ return values.dup unless block_given?
38
+
39
+ filter_method = get_filter_method(FilterMethods.evaluate(&block))
40
+
41
+ values.map do |value|
42
+ Filter.factory(filter_method.method_name).prepare(filter_method.attribute, value, filter_method.options, &filter_method.block)
43
+ end
44
+ end
45
+ private_class_method :convert_values
46
+
47
+ def self.get_filter_method(filter_methods)
48
+ if filter_methods.count > 1
49
+ raise ArgumentError, 'Array filter blocks can only contain one filter.'
50
+ else
51
+ filter_method = filter_methods.first
52
+ end
53
+ end
54
+ private_class_method :get_filter_method
55
+ end
56
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Arrays. The String `"1"` is converted to `true` and
5
+ # `"0"` is converted to `false`.
6
+ #
7
+ # @macro attribute_method_params
8
+ #
9
+ # @example
10
+ # boolean :subscribed
11
+ #
12
+ # @method self.boolean(*attributes, options = {})
13
+ end
14
+
15
+ # @private
16
+ class BooleanFilter < Filter
17
+ def self.prepare(key, value, options = {}, &block)
18
+ case value
19
+ when TrueClass, FalseClass
20
+ value
21
+ when '0'
22
+ false
23
+ when '1'
24
+ true
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Dates. String values are processed using `parse`.
5
+ #
6
+ # @macro attribute_method_params
7
+ #
8
+ # @example
9
+ # date :birthday
10
+ #
11
+ # @method self.date(*attributes, options = {})
12
+ end
13
+
14
+ # @private
15
+ class DateFilter < Filter
16
+ def self.prepare(key, value, options = {}, &block)
17
+ case value
18
+ when Date
19
+ value
20
+ when String
21
+ begin
22
+ Date.parse(value)
23
+ rescue ArgumentError
24
+ bad_value
25
+ end
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are DateTimes. String values are processed using `parse`.
5
+ #
6
+ # @macro attribute_method_params
7
+ #
8
+ # @example
9
+ # date_time :start_date
10
+ #
11
+ # @method self.date_time(*attributes, options = {})
12
+ end
13
+
14
+ # @private
15
+ class DateTimeFilter < Filter
16
+ def self.prepare(key, value, options = {}, &block)
17
+ case value
18
+ when DateTime
19
+ value
20
+ when String
21
+ begin
22
+ DateTime.parse(value)
23
+ rescue ArgumentError
24
+ bad_value
25
+ end
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveInteraction
2
+ class Base
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
5
+ # any object with a `tempfile` method. This is useful when passing in Rails
6
+ # params that include a file upload.
7
+ #
8
+ # @macro attribute_method_params
9
+ #
10
+ # @example
11
+ # file :image
12
+ #
13
+ # @method self.file(*attributes, options = {})
14
+ end
15
+
16
+ # @private
17
+ class FileFilter < Filter
18
+ def self.prepare(key, value, options = {}, &block)
19
+ value = extract_file(value)
20
+
21
+ case value
22
+ when File, Tempfile
23
+ value
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def self.extract_file(value)
30
+ if value.respond_to?(:tempfile)
31
+ value = value.tempfile
32
+ else
33
+ value
34
+ end
35
+ end
36
+ private_class_method :extract_file
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Floats. Integer and String values are converted into
5
+ # Floats.
6
+ #
7
+ # @macro attribute_method_params
8
+ #
9
+ # @example
10
+ # float :amount
11
+ #
12
+ # @method self.float(*attributes, options = {})
13
+ end
14
+
15
+ # @private
16
+ class FloatFilter < Filter
17
+ def self.prepare(key, value, options = {}, &block)
18
+ case value
19
+ when Float
20
+ value
21
+ when Integer, String
22
+ begin
23
+ Float(value)
24
+ rescue ArgumentError
25
+ bad_value
26
+ end
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Hashes.
5
+ #
6
+ # @macro attribute_method_params
7
+ # @param block [Proc] Filter methods to apply for select keys.
8
+ #
9
+ # @example
10
+ # hash :order
11
+ #
12
+ # @example A Hash where certain keys also have their values ensured.
13
+ # hash :order do
14
+ # model :account
15
+ # model :item
16
+ # integer :quantity
17
+ # boolean :delivered
18
+ # end
19
+ #
20
+ # @method self.hash(*attributes, options = {}, &block)
21
+ end
22
+
23
+ # @private
24
+ class HashFilter < Filter
25
+ def self.prepare(key, value, options = {}, &block)
26
+ case value
27
+ when Hash
28
+ convert_values(value.dup, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def self.convert_values(hash, &block)
35
+ return hash unless block_given?
36
+
37
+ FilterMethods.evaluate(&block).each do |filter_method|
38
+ key = filter_method.attribute
39
+
40
+ hash[key] = Filter.factory(filter_method.method_name).prepare(key, hash[key], filter_method.options, &filter_method.block)
41
+ end
42
+
43
+ hash
44
+ end
45
+ private_class_method :convert_values
46
+ end
47
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Integers. String values are converted into Integers.
5
+ #
6
+ # @macro attribute_method_params
7
+ #
8
+ # @example
9
+ # integer :quantity
10
+ #
11
+ # @method self.integer(*attributes, options = {})
12
+ end
13
+
14
+ # @private
15
+ class IntegerFilter < Filter
16
+ def self.prepare(key, value, options = {}, &block)
17
+ case value
18
+ when Integer
19
+ value
20
+ when String
21
+ begin
22
+ Integer(value)
23
+ rescue ArgumentError
24
+ bad_value
25
+ end
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are the correct class.
5
+ #
6
+ # @macro attribute_method_params
7
+ # @option options [Class, String, Symbol] :class (use the attribute name) Class name used to ensure the value.
8
+ #
9
+ # @example Ensures that the class is `Account`
10
+ # model :account
11
+ #
12
+ # @example Ensures that the class is `User`
13
+ # model :account, class: User
14
+ #
15
+ # @method self.model(*attributes, options = {})
16
+ end
17
+
18
+ # @private
19
+ class ModelFilter < Filter
20
+ def self.prepare(key, value, options = {}, &block)
21
+ key_class = constantize(options.fetch(:class, key))
22
+
23
+ case value
24
+ when key_class
25
+ value
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def self.constantize(constant_name)
32
+ if constant_name.is_a?(Symbol) || constant_name.is_a?(String)
33
+ constant_name.to_s.classify.constantize
34
+ else
35
+ constant_name
36
+ end
37
+ end
38
+ private_class_method :constantize
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Strings.
5
+ #
6
+ # @macro attribute_method_params
7
+ #
8
+ # @example
9
+ # string :first_name
10
+ #
11
+ # @method self.string(*attributes, options = {})
12
+ end
13
+
14
+ # @private
15
+ class StringFilter < Filter
16
+ def self.prepare(key, value, options = {}, &block)
17
+ case value
18
+ when String
19
+ value
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveInteraction
2
+ class Base
3
+ # Creates accessors for the attributes and ensures that values passed to
4
+ # the attributes are Times. Numeric values are processed using `at`.
5
+ # Strings are processed using `parse`. If `Time.zone` is available it will
6
+ # be used so that the values are time zone aware.
7
+ #
8
+ # @macro attribute_method_params
9
+ #
10
+ # @example
11
+ # time :start_date
12
+ #
13
+ # @method self.time(*attributes, options = {})
14
+ end
15
+
16
+ # @private
17
+ class TimeFilter < Filter
18
+ def self.prepare(key, value, options = {}, &block)
19
+ case value
20
+ when Time
21
+ value
22
+ when String
23
+ begin
24
+ time.parse(value)
25
+ rescue ArgumentError
26
+ bad_value
27
+ end
28
+ when Numeric
29
+ time.at(value)
30
+ else
31
+ super
32
+ end
33
+ end
34
+
35
+ def self.time
36
+ if Time.respond_to?(:zone)
37
+ Time.zone
38
+ else
39
+ Time
40
+ end
41
+ end
42
+ private_class_method :time
43
+ end
44
+ end