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.
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +202 -0
- data/lib/active_interaction.rb +23 -0
- data/lib/active_interaction/base.rb +162 -0
- data/lib/active_interaction/errors.rb +6 -0
- data/lib/active_interaction/filter.rb +41 -0
- data/lib/active_interaction/filter_method.rb +17 -0
- data/lib/active_interaction/filter_methods.rb +26 -0
- data/lib/active_interaction/filters/array_filter.rb +56 -0
- data/lib/active_interaction/filters/boolean_filter.rb +30 -0
- data/lib/active_interaction/filters/date_filter.rb +31 -0
- data/lib/active_interaction/filters/date_time_filter.rb +31 -0
- data/lib/active_interaction/filters/file_filter.rb +38 -0
- data/lib/active_interaction/filters/float_filter.rb +32 -0
- data/lib/active_interaction/filters/hash_filter.rb +47 -0
- data/lib/active_interaction/filters/integer_filter.rb +31 -0
- data/lib/active_interaction/filters/model_filter.rb +40 -0
- data/lib/active_interaction/filters/string_filter.rb +25 -0
- data/lib/active_interaction/filters/time_filter.rb +44 -0
- data/lib/active_interaction/overload_hash.rb +11 -0
- data/lib/active_interaction/version.rb +3 -0
- data/spec/active_interaction/base_spec.rb +175 -0
- data/spec/active_interaction/filter_method_spec.rb +48 -0
- data/spec/active_interaction/filter_methods_spec.rb +30 -0
- data/spec/active_interaction/filter_spec.rb +29 -0
- data/spec/active_interaction/filters/array_filter_spec.rb +54 -0
- data/spec/active_interaction/filters/boolean_filter_spec.rb +40 -0
- data/spec/active_interaction/filters/date_filter_spec.rb +32 -0
- data/spec/active_interaction/filters/date_time_filter_spec.rb +32 -0
- data/spec/active_interaction/filters/file_filter_spec.rb +32 -0
- data/spec/active_interaction/filters/float_filter_spec.rb +40 -0
- data/spec/active_interaction/filters/hash_filter_spec.rb +57 -0
- data/spec/active_interaction/filters/integer_filter_spec.rb +32 -0
- data/spec/active_interaction/filters/model_filter_spec.rb +40 -0
- data/spec/active_interaction/filters/string_filter_spec.rb +16 -0
- data/spec/active_interaction/filters/time_filter_spec.rb +66 -0
- data/spec/active_interaction/integration/array_interaction_spec.rb +69 -0
- data/spec/active_interaction/integration/boolean_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/date_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/date_time_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/file_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/float_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/hash_interaction_spec.rb +69 -0
- data/spec/active_interaction/integration/integer_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/model_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/string_interaction_spec.rb +5 -0
- data/spec/active_interaction/integration/time_interaction_spec.rb +5 -0
- data/spec/active_interaction/overload_hash_spec.rb +41 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/filters.rb +37 -0
- data/spec/support/interactions.rb +79 -0
- 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
|