active_interaction 0.1.1

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