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,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::FileFilter do
4
+ include_context 'filters'
5
+ it_behaves_like 'a filter'
6
+
7
+ describe '.prepare(key, value, options = {}, &block)' do
8
+ context 'with a File' do
9
+ let(:value) { File.open(__FILE__) }
10
+
11
+ it 'returns the File' do
12
+ expect(result).to equal value
13
+ end
14
+ end
15
+
16
+ context 'with a Tempfile' do
17
+ let(:value) { Tempfile.new('temp') }
18
+
19
+ it 'returns the Tempfile' do
20
+ expect(result).to equal value
21
+ end
22
+ end
23
+
24
+ context 'with a object that responds to `tempfile`' do
25
+ let(:value) { double(tempfile: Tempfile.new('temp')) }
26
+
27
+ it 'returns the Tempfile' do
28
+ expect(result).to equal value.tempfile
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::FloatFilter do
4
+ include_context 'filters'
5
+ it_behaves_like 'a filter'
6
+
7
+ describe '.prepare(key, value, options = {}, &block)' do
8
+ context 'with a Float' do
9
+ let(:value) { rand }
10
+
11
+ it 'returns the Float' do
12
+ expect(result).to eql value
13
+ end
14
+ end
15
+
16
+ context 'with an Integer' do
17
+ let(:value) { rand(1 << 16) }
18
+
19
+ it 'converts the Integer' do
20
+ expect(result).to eql Float(value)
21
+ end
22
+ end
23
+
24
+ context 'with a valid String' do
25
+ let(:value) { rand.to_s }
26
+
27
+ it 'converts the String' do
28
+ expect(result).to eql Float(value)
29
+ end
30
+ end
31
+
32
+ context 'with an invalid String' do
33
+ let(:value) { 'not a valid Float' }
34
+
35
+ it 'raises an error' do
36
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::HashFilter do
4
+ include_context 'filters'
5
+ it_behaves_like 'a filter'
6
+
7
+ describe '.prepare(key, value, options = {}, &block)' do
8
+ context 'with a Hash' do
9
+ let(:value) { {} }
10
+
11
+ it 'returns the Hash' do
12
+ expect(result).to eql value
13
+ end
14
+ end
15
+
16
+ context 'with block as a block' do
17
+ let(:block) { Proc.new { hash :a } }
18
+
19
+ context 'with a Hash containing a Hash' do
20
+ let(:value) { { a: {} } }
21
+
22
+ it 'returns the Hash' do
23
+ expect(result).to eql value
24
+ end
25
+ end
26
+
27
+ context 'with a Hash containing anything else' do
28
+ let(:value) { { a: Object.new } }
29
+
30
+ it 'raises an error' do
31
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'with block as a block with multiple filters' do
37
+ let(:block) { Proc.new { hash :a; hash :b } }
38
+
39
+ context 'with a Hash containing Hashes' do
40
+ let(:value) { { a: {}, b: {} } }
41
+
42
+ it 'returns the Hash' do
43
+ expect(result).to eql value
44
+ end
45
+ end
46
+ end
47
+
48
+ context 'with block as a nested block' do
49
+ let(:block) { Proc.new { hash :a do; hash :b end } }
50
+ let(:value) { { a: { b: {} } } }
51
+
52
+ it 'returns the Hash' do
53
+ expect(result).to eql value
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::IntegerFilter do
4
+ include_context 'filters'
5
+ it_behaves_like 'a filter'
6
+
7
+ describe '.prepare(key, value, options = {}, &block)' do
8
+ context 'with an Integer' do
9
+ let(:value) { rand(1 << 16) }
10
+
11
+ it 'returns the Integer' do
12
+ expect(result).to eql value
13
+ end
14
+ end
15
+
16
+ context 'with a valid String' do
17
+ let(:value) { rand(1 << 16).to_s }
18
+
19
+ it 'converts the String' do
20
+ expect(result).to eql Integer(value)
21
+ end
22
+ end
23
+
24
+ context 'with an invalid String' do
25
+ let(:value) { 'not a valid Integer' }
26
+
27
+ it 'raises an error' do
28
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ TestModel = Class.new
4
+
5
+ describe ActiveInteraction::ModelFilter do
6
+ include_context 'filters'
7
+ it_behaves_like 'a filter'
8
+
9
+ before { options.merge!(class: TestModel) }
10
+
11
+ describe '.prepare(key, value, options = {}, &block)' do
12
+ shared_examples 'typechecking' do
13
+ context 'with the right class' do
14
+ let(:value) { TestModel.new }
15
+
16
+ it 'returns the instance' do
17
+ expect(result).to eql value
18
+ end
19
+ end
20
+ end
21
+
22
+ context 'with options[:class] as a Class' do
23
+ include_examples 'typechecking'
24
+ end
25
+
26
+ context 'with options[:class] as a valid String' do
27
+ include_examples 'typechecking'
28
+
29
+ before { options.merge!(class: options[:class].to_s) }
30
+ end
31
+
32
+ context 'with options[:class] as an invalid String' do
33
+ before { options.merge!(class: 'not a valid Class') }
34
+
35
+ it 'raises an error' do
36
+ expect { result }.to raise_error NameError
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::StringFilter do
4
+ include_context 'filters'
5
+ it_behaves_like 'a filter'
6
+
7
+ describe '.prepare(key, value, options = {}, &block)' do
8
+ context 'with a String' do
9
+ let(:value) { SecureRandom.hex }
10
+
11
+ it 'returns the String' do
12
+ expect(result).to eql value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveInteraction::TimeFilter do
4
+ include_context 'filters'
5
+ it_behaves_like 'a filter'
6
+
7
+ describe '.prepare(key, value, options = {}, &block)' do
8
+ context 'with a Time' do
9
+ let(:value) { Time.now }
10
+
11
+ it 'returns the Time' do
12
+ expect(result).to eql value
13
+ end
14
+ end
15
+
16
+ shared_examples 'conversion' do
17
+ context 'with a float' do
18
+ let(:value) { rand }
19
+
20
+ it 'converts the Float' do
21
+ expect(result).to eql Time.at(value)
22
+ end
23
+ end
24
+
25
+ context 'with an Integer' do
26
+ let(:value) { rand(1 << 16) }
27
+
28
+ it 'converts the Integer' do
29
+ expect(result).to eql Time.at(value)
30
+ end
31
+ end
32
+
33
+ context 'with a valid String' do
34
+ let(:value) { '2001-01-01T01:01:01+01:01' }
35
+
36
+ it 'parses the String' do
37
+ expect(result).to eql Time.parse(value)
38
+ end
39
+ end
40
+
41
+ context 'with an invalid String' do
42
+ let(:value) { 'not a valid Time' }
43
+
44
+ it 'raises an error' do
45
+ expect { result }.to raise_error ActiveInteraction::InvalidValue
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'without Time.zone' do
51
+ include_examples 'conversion'
52
+ end
53
+
54
+ context 'with Time.zone' do
55
+ include_examples 'conversion'
56
+
57
+ before do
58
+ allow(Time).to receive(:zone).and_return(Time)
59
+ end
60
+
61
+ after do
62
+ expect(Time).to have_received(:zone).with(no_args)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ class ArrayInteraction < ActiveInteraction::Base
4
+ array :a do
5
+ array
6
+ end
7
+ array :b, default: [[]] do
8
+ array
9
+ end
10
+
11
+ def execute
12
+ { a: a, b: b }
13
+ end
14
+ end
15
+
16
+ describe ArrayInteraction do
17
+ include_context 'interactions'
18
+ it_behaves_like 'an interaction', :array, -> { [] }
19
+
20
+ context 'with options[:a]' do
21
+ let(:a) { [[]] }
22
+
23
+ before { options.merge!(a: a) }
24
+
25
+ it 'returns the correct value for :a' do
26
+ expect(result[:a]).to eq a
27
+ end
28
+
29
+ it 'returns the correct value for :b' do
30
+ expect(result[:b]).to eq [[]]
31
+ end
32
+ end
33
+
34
+ context 'with an invalid default' do
35
+ it 'raises an error' do
36
+ expect {
37
+ Class.new(ActiveInteraction::Base) do
38
+ array :a, default: Object.new
39
+ end
40
+ }.to raise_error ActiveInteraction::InvalidDefaultValue
41
+ end
42
+ end
43
+
44
+ context 'with an invalid nested default' do
45
+ it 'raises an error' do
46
+ expect {
47
+ Class.new(ActiveInteraction::Base) do
48
+ array :a, default: [Object.new] do
49
+ array
50
+ end
51
+ end
52
+ }.to raise_error ActiveInteraction::InvalidDefaultValue
53
+ end
54
+ end
55
+
56
+ context 'with an invalidly nested default' do
57
+ it 'raises an error' do
58
+ expect {
59
+ klass = Class.new(ActiveInteraction::Base) do
60
+ array :a do
61
+ array default: []
62
+ end
63
+ end
64
+ # TODO: We should fail when defining the class, not when trying to run it.
65
+ klass.run(a: [])
66
+ }.to raise_error ArgumentError
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'BooleanInteraction' do
4
+ it_behaves_like 'an interaction', :boolean, -> { [false, true].sample }
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'DateInteraction' do
4
+ it_behaves_like 'an interaction', :date, -> { Date.today }
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'DateTimeInteraction' do
4
+ it_behaves_like 'an interaction', :date_time, -> { DateTime.now }
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'FileInteraction' do
4
+ it_behaves_like 'an interaction', :file, -> { File.open(__FILE__) }
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'FloatInteraction' do
4
+ it_behaves_like 'an interaction', :float, -> { rand }
5
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ class HashInteraction < ActiveInteraction::Base
4
+ hash :a do
5
+ hash :x
6
+ end
7
+ hash :b, default: { x: {} } do
8
+ hash :x
9
+ end
10
+
11
+ def execute
12
+ { a: a, b: b }
13
+ end
14
+ end
15
+
16
+ describe HashInteraction do
17
+ include_context 'interactions'
18
+ it_behaves_like 'an interaction', :hash, -> { {} }
19
+
20
+ context 'with options[:a]' do
21
+ let(:a) { { 'x' => {} } }
22
+
23
+ before { options.merge!(a: a) }
24
+
25
+ it 'returns the correct value for :a' do
26
+ expect(result[:a]).to eq a
27
+ end
28
+
29
+ it 'returns the correct value for :b' do
30
+ expect(result[:b]).to eq(x: {})
31
+ end
32
+ end
33
+
34
+ context 'with an invalid default' do
35
+ it 'raises an error' do
36
+ expect {
37
+ Class.new(ActiveInteraction::Base) do
38
+ hash :a, default: Object.new
39
+ end
40
+ }.to raise_error ActiveInteraction::InvalidDefaultValue
41
+ end
42
+ end
43
+
44
+ context 'with an invalid nested default' do
45
+ it 'raises an error' do
46
+ expect {
47
+ Class.new(ActiveInteraction::Base) do
48
+ hash :a, default: { x: Object.new } do
49
+ hash :x
50
+ end
51
+ end
52
+ }.to raise_error ActiveInteraction::InvalidDefaultValue
53
+ end
54
+ end
55
+
56
+ context 'with an invalidly nested default' do
57
+ it 'raises an error' do
58
+ expect {
59
+ klass = Class.new(ActiveInteraction::Base) do
60
+ hash :a do
61
+ hash :x, default: {}
62
+ end
63
+ end
64
+ # TODO: We should fail when defining the class, not when trying to run it.
65
+ klass.run(a: {})
66
+ }.to raise_error ArgumentError
67
+ end
68
+ end
69
+ end