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,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