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