active_interaction 1.1.7 → 1.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +3 -2
- data/lib/active_interaction.rb +7 -1
- data/lib/active_interaction/base.rb +61 -13
- data/lib/active_interaction/concerns/runnable.rb +2 -18
- data/lib/active_interaction/concerns/transactable.rb +72 -0
- data/lib/active_interaction/errors.rb +7 -0
- data/lib/active_interaction/filter.rb +23 -2
- data/lib/active_interaction/filter_column.rb +59 -0
- data/lib/active_interaction/filters/abstract_date_time_filter.rb +14 -0
- data/lib/active_interaction/filters/abstract_filter.rb +4 -7
- data/lib/active_interaction/filters/abstract_numeric_filter.rb +4 -0
- data/lib/active_interaction/filters/boolean_filter.rb +4 -0
- data/lib/active_interaction/filters/date_time_filter.rb +3 -0
- data/lib/active_interaction/filters/decimal_filter.rb +54 -0
- data/lib/active_interaction/filters/file_filter.rb +4 -0
- data/lib/active_interaction/filters/time_filter.rb +4 -0
- data/lib/active_interaction/grouped_input.rb +24 -0
- data/lib/active_interaction/locale/en.yml +1 -0
- data/lib/active_interaction/modules/input_processor.rb +40 -0
- data/lib/active_interaction/version.rb +1 -1
- data/spec/active_interaction/base_spec.rb +90 -29
- data/spec/active_interaction/concerns/runnable_spec.rb +0 -26
- data/spec/active_interaction/concerns/transactable_spec.rb +114 -0
- data/spec/active_interaction/filter_column_spec.rb +96 -0
- data/spec/active_interaction/filter_spec.rb +15 -11
- data/spec/active_interaction/filters/array_filter_spec.rb +13 -5
- data/spec/active_interaction/filters/boolean_filter_spec.rb +6 -0
- data/spec/active_interaction/filters/date_filter_spec.rb +76 -5
- data/spec/active_interaction/filters/date_time_filter_spec.rb +87 -5
- data/spec/active_interaction/filters/decimal_filter_spec.rb +70 -0
- data/spec/active_interaction/filters/file_filter_spec.rb +11 -3
- data/spec/active_interaction/filters/float_filter_spec.rb +12 -4
- data/spec/active_interaction/filters/hash_filter_spec.rb +16 -8
- data/spec/active_interaction/filters/integer_filter_spec.rb +12 -4
- data/spec/active_interaction/filters/model_filter_spec.rb +12 -5
- data/spec/active_interaction/filters/string_filter_spec.rb +11 -3
- data/spec/active_interaction/filters/symbol_filter_spec.rb +10 -2
- data/spec/active_interaction/filters/time_filter_spec.rb +87 -5
- data/spec/active_interaction/grouped_input_spec.rb +19 -0
- data/spec/active_interaction/modules/input_processor_spec.rb +75 -0
- data/spec/support/filters.rb +6 -0
- metadata +16 -1
@@ -0,0 +1,70 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe ActiveInteraction::DecimalFilter, :filter do
|
6
|
+
include_context 'filters'
|
7
|
+
it_behaves_like 'a filter'
|
8
|
+
|
9
|
+
shared_context 'with digits' do
|
10
|
+
let(:digits) { 4 }
|
11
|
+
|
12
|
+
before do
|
13
|
+
options.merge!(digits: digits)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#cast' do
|
18
|
+
let(:result) { filter.cast(value) }
|
19
|
+
|
20
|
+
context 'with a Float' do
|
21
|
+
let(:value) { rand }
|
22
|
+
|
23
|
+
it 'returns the BigDecimal' do
|
24
|
+
expect(result).to eql BigDecimal.new(value, 0)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with :digits option' do
|
28
|
+
include_context 'with digits'
|
29
|
+
|
30
|
+
let(:value) { 1.23456789 }
|
31
|
+
|
32
|
+
it 'returns BigDecimal with given digits' do
|
33
|
+
expect(result).to eql BigDecimal.new('1.235')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with a Numeric' do
|
39
|
+
let(:value) { rand(1 << 16) }
|
40
|
+
|
41
|
+
it 'returns a BigDecimal' do
|
42
|
+
expect(result).to eql BigDecimal.new(value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with a String' do
|
47
|
+
let(:value) { rand.to_s }
|
48
|
+
|
49
|
+
it 'returns a BigDecimal' do
|
50
|
+
expect(result).to eql BigDecimal.new(value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with an invalid String' do
|
55
|
+
let(:value) { 'invalid' }
|
56
|
+
|
57
|
+
it 'raises an error' do
|
58
|
+
expect do
|
59
|
+
result
|
60
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#database_column_type' do
|
66
|
+
it 'returns :decimal' do
|
67
|
+
expect(filter.database_column_type).to eql :decimal
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -7,11 +7,13 @@ describe ActiveInteraction::FileFilter, :filter do
|
|
7
7
|
it_behaves_like 'a filter'
|
8
8
|
|
9
9
|
describe '#cast' do
|
10
|
+
let(:result) { filter.cast(value) }
|
11
|
+
|
10
12
|
context 'with a File' do
|
11
13
|
let(:value) { File.new(__FILE__) }
|
12
14
|
|
13
15
|
it 'returns the File' do
|
14
|
-
expect(
|
16
|
+
expect(result).to eql value
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -19,7 +21,7 @@ describe ActiveInteraction::FileFilter, :filter do
|
|
19
21
|
let(:value) { Tempfile.new(SecureRandom.hex) }
|
20
22
|
|
21
23
|
it 'returns the Tempfile' do
|
22
|
-
expect(
|
24
|
+
expect(result).to eq value
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,8 +29,14 @@ describe ActiveInteraction::FileFilter, :filter do
|
|
27
29
|
let(:value) { double(tempfile: Tempfile.new(SecureRandom.hex)) }
|
28
30
|
|
29
31
|
it 'returns the Tempfile' do
|
30
|
-
expect(
|
32
|
+
expect(result).to eq value.tempfile
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
36
|
+
|
37
|
+
describe '#database_column_type' do
|
38
|
+
it 'returns :file' do
|
39
|
+
expect(filter.database_column_type).to eql :file
|
40
|
+
end
|
41
|
+
end
|
34
42
|
end
|
@@ -7,11 +7,13 @@ describe ActiveInteraction::FloatFilter, :filter do
|
|
7
7
|
it_behaves_like 'a filter'
|
8
8
|
|
9
9
|
describe '#cast' do
|
10
|
+
let(:result) { filter.cast(value) }
|
11
|
+
|
10
12
|
context 'with a Float' do
|
11
13
|
let(:value) { rand }
|
12
14
|
|
13
15
|
it 'returns the Float' do
|
14
|
-
expect(
|
16
|
+
expect(result).to eql value
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -19,7 +21,7 @@ describe ActiveInteraction::FloatFilter, :filter do
|
|
19
21
|
let(:value) { rand(1 << 16) }
|
20
22
|
|
21
23
|
it 'returns a Float' do
|
22
|
-
expect(
|
24
|
+
expect(result).to eql value.to_f
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,7 +29,7 @@ describe ActiveInteraction::FloatFilter, :filter do
|
|
27
29
|
let(:value) { rand.to_s }
|
28
30
|
|
29
31
|
it 'returns a Float' do
|
30
|
-
expect(
|
32
|
+
expect(result).to eql Float(value)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -36,9 +38,15 @@ describe ActiveInteraction::FloatFilter, :filter do
|
|
36
38
|
|
37
39
|
it 'raises an error' do
|
38
40
|
expect do
|
39
|
-
|
41
|
+
result
|
40
42
|
end.to raise_error ActiveInteraction::InvalidValueError
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
46
|
+
|
47
|
+
describe '#database_column_type' do
|
48
|
+
it 'returns :float' do
|
49
|
+
expect(filter.database_column_type).to eql :float
|
50
|
+
end
|
51
|
+
end
|
44
52
|
end
|
@@ -15,11 +15,13 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
describe '#cast' do
|
18
|
+
let(:result) { filter.cast(value) }
|
19
|
+
|
18
20
|
context 'with a Hash' do
|
19
21
|
let(:value) { {} }
|
20
22
|
|
21
23
|
it 'returns the Hash' do
|
22
|
-
expect(
|
24
|
+
expect(result).to eql value
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,7 +29,7 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
27
29
|
let(:value) { { a: {} } }
|
28
30
|
|
29
31
|
it 'returns an empty Hash' do
|
30
|
-
expect(
|
32
|
+
expect(result).to eql({})
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -38,7 +40,7 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
38
40
|
let(:value) { { a: {} } }
|
39
41
|
|
40
42
|
it 'returns the Hash' do
|
41
|
-
expect(
|
43
|
+
expect(result).to eql value
|
42
44
|
end
|
43
45
|
|
44
46
|
context 'with String keys' do
|
@@ -47,7 +49,7 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
47
49
|
end
|
48
50
|
|
49
51
|
it 'does not raise an error' do
|
50
|
-
expect {
|
52
|
+
expect { result }.to_not raise_error
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
@@ -59,13 +61,13 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
59
61
|
|
60
62
|
it 'raises an error' do
|
61
63
|
expect do
|
62
|
-
|
64
|
+
result
|
63
65
|
end.to raise_error ActiveInteraction::InvalidNestedValueError
|
64
66
|
end
|
65
67
|
|
66
68
|
it 'populates the error' do
|
67
69
|
begin
|
68
|
-
|
70
|
+
result
|
69
71
|
rescue ActiveInteraction::InvalidNestedValueError => e
|
70
72
|
expect(e.filter_name).to eql k
|
71
73
|
expect(e.input_value).to eql v
|
@@ -82,11 +84,11 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
82
84
|
end
|
83
85
|
|
84
86
|
it 'symbolizes String keys' do
|
85
|
-
expect(
|
87
|
+
expect(result).to have_key :a
|
86
88
|
end
|
87
89
|
|
88
90
|
it 'leaves other keys alone' do
|
89
|
-
expect(
|
91
|
+
expect(result).to have_key 1
|
90
92
|
end
|
91
93
|
end
|
92
94
|
end
|
@@ -114,4 +116,10 @@ describe ActiveInteraction::HashFilter, :filter do
|
|
114
116
|
end
|
115
117
|
end
|
116
118
|
end
|
119
|
+
|
120
|
+
describe '#database_column_type' do
|
121
|
+
it 'returns :string' do
|
122
|
+
expect(filter.database_column_type).to eql :string
|
123
|
+
end
|
124
|
+
end
|
117
125
|
end
|
@@ -7,11 +7,13 @@ describe ActiveInteraction::IntegerFilter, :filter do
|
|
7
7
|
it_behaves_like 'a filter'
|
8
8
|
|
9
9
|
describe '#cast' do
|
10
|
+
let(:result) { filter.cast(value) }
|
11
|
+
|
10
12
|
context 'with an Integer' do
|
11
13
|
let(:value) { rand(1 << 16) }
|
12
14
|
|
13
15
|
it 'returns the Integer' do
|
14
|
-
expect(
|
16
|
+
expect(result).to eql value
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -19,7 +21,7 @@ describe ActiveInteraction::IntegerFilter, :filter do
|
|
19
21
|
let(:value) { rand(1 << 16) + rand }
|
20
22
|
|
21
23
|
it 'returns an Integer' do
|
22
|
-
expect(
|
24
|
+
expect(result).to eql value.to_i
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,7 +29,7 @@ describe ActiveInteraction::IntegerFilter, :filter do
|
|
27
29
|
let(:value) { rand(1 << 16).to_s }
|
28
30
|
|
29
31
|
it 'returns an Integer' do
|
30
|
-
expect(
|
32
|
+
expect(result).to eql Integer(value)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -36,9 +38,15 @@ describe ActiveInteraction::IntegerFilter, :filter do
|
|
36
38
|
|
37
39
|
it 'raises an error' do
|
38
40
|
expect do
|
39
|
-
|
41
|
+
result
|
40
42
|
end.to raise_error ActiveInteraction::InvalidValueError
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
46
|
+
|
47
|
+
describe '#database_column_type' do
|
48
|
+
it 'returns :integer' do
|
49
|
+
expect(filter.database_column_type).to eql :integer
|
50
|
+
end
|
51
|
+
end
|
44
52
|
end
|
@@ -14,14 +14,15 @@ describe ActiveInteraction::ModelFilter, :filter do
|
|
14
14
|
|
15
15
|
describe '#cast' do
|
16
16
|
let(:value) { Model.new }
|
17
|
+
let(:result) { filter.cast(value) }
|
17
18
|
|
18
19
|
context 'with class as a Class' do
|
19
20
|
it 'returns the instance' do
|
20
|
-
expect(
|
21
|
+
expect(result).to eql value
|
21
22
|
end
|
22
23
|
|
23
24
|
it 'handles reconstantizing' do
|
24
|
-
expect(
|
25
|
+
expect(result).to eql value
|
25
26
|
|
26
27
|
Object.send(:remove_const, :Model)
|
27
28
|
class Model; end
|
@@ -69,7 +70,7 @@ describe ActiveInteraction::ModelFilter, :filter do
|
|
69
70
|
end
|
70
71
|
|
71
72
|
it 'returns the instance' do
|
72
|
-
expect(
|
73
|
+
expect(result).to eql value
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
@@ -79,7 +80,7 @@ describe ActiveInteraction::ModelFilter, :filter do
|
|
79
80
|
end
|
80
81
|
|
81
82
|
it 'returns the instance' do
|
82
|
-
expect(
|
83
|
+
expect(result).to eql value
|
83
84
|
end
|
84
85
|
end
|
85
86
|
|
@@ -90,9 +91,15 @@ describe ActiveInteraction::ModelFilter, :filter do
|
|
90
91
|
|
91
92
|
it 'raises an error' do
|
92
93
|
expect do
|
93
|
-
|
94
|
+
result
|
94
95
|
end.to raise_error ActiveInteraction::InvalidClassError
|
95
96
|
end
|
96
97
|
end
|
97
98
|
end
|
99
|
+
|
100
|
+
describe '#database_column_type' do
|
101
|
+
it 'returns :string' do
|
102
|
+
expect(filter.database_column_type).to eql :string
|
103
|
+
end
|
104
|
+
end
|
98
105
|
end
|
@@ -13,11 +13,13 @@ describe ActiveInteraction::StringFilter, :filter do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
describe '#cast' do
|
16
|
+
let(:result) { filter.cast(value) }
|
17
|
+
|
16
18
|
context 'with a String' do
|
17
19
|
let(:value) { SecureRandom.hex }
|
18
20
|
|
19
21
|
it 'returns the String' do
|
20
|
-
expect(
|
22
|
+
expect(result).to eql value
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
@@ -25,16 +27,22 @@ describe ActiveInteraction::StringFilter, :filter do
|
|
25
27
|
let(:value) { " #{SecureRandom.hex} " }
|
26
28
|
|
27
29
|
it 'returns the stripped string' do
|
28
|
-
expect(
|
30
|
+
expect(result).to eql value.strip
|
29
31
|
end
|
30
32
|
|
31
33
|
context 'without strip' do
|
32
34
|
include_context 'without strip'
|
33
35
|
|
34
36
|
it 'returns the String' do
|
35
|
-
expect(
|
37
|
+
expect(result).to eql value
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
39
41
|
end
|
42
|
+
|
43
|
+
describe '#database_column_type' do
|
44
|
+
it 'returns :string' do
|
45
|
+
expect(filter.database_column_type).to eql :string
|
46
|
+
end
|
47
|
+
end
|
40
48
|
end
|
@@ -7,11 +7,13 @@ describe ActiveInteraction::SymbolFilter, :filter do
|
|
7
7
|
it_behaves_like 'a filter'
|
8
8
|
|
9
9
|
describe '#cast' do
|
10
|
+
let(:result) { filter.cast(value) }
|
11
|
+
|
10
12
|
context 'with a Symbol' do
|
11
13
|
let(:value) { SecureRandom.hex.to_sym }
|
12
14
|
|
13
15
|
it 'returns the Symbol' do
|
14
|
-
expect(
|
16
|
+
expect(result).to eql value
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -19,8 +21,14 @@ describe ActiveInteraction::SymbolFilter, :filter do
|
|
19
21
|
let(:value) { SecureRandom.hex }
|
20
22
|
|
21
23
|
it 'returns a Symbol' do
|
22
|
-
expect(
|
24
|
+
expect(result).to eql value.to_sym
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
28
|
+
|
29
|
+
describe '#database_column_type' do
|
30
|
+
it 'returns :string' do
|
31
|
+
expect(filter.database_column_type).to eql :string
|
32
|
+
end
|
33
|
+
end
|
26
34
|
end
|
@@ -15,11 +15,13 @@ describe ActiveInteraction::TimeFilter, :filter do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
describe '#cast' do
|
18
|
+
let(:result) { filter.cast(value) }
|
19
|
+
|
18
20
|
context 'with a Time' do
|
19
21
|
let(:value) { Time.new }
|
20
22
|
|
21
23
|
it 'returns the Time' do
|
22
|
-
expect(
|
24
|
+
expect(result).to eql value
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
@@ -27,7 +29,7 @@ describe ActiveInteraction::TimeFilter, :filter do
|
|
27
29
|
let(:value) { '2011-12-13 14:15:16 +1718' }
|
28
30
|
|
29
31
|
it 'returns a Time' do
|
30
|
-
expect(
|
32
|
+
expect(result).to eql Time.parse(value)
|
31
33
|
end
|
32
34
|
|
33
35
|
context 'with format' do
|
@@ -36,7 +38,7 @@ describe ActiveInteraction::TimeFilter, :filter do
|
|
36
38
|
let(:value) { '13/12/2011 14:15:16 +1718' }
|
37
39
|
|
38
40
|
it 'returns a Time' do
|
39
|
-
expect(
|
41
|
+
expect(result).to eql Time.strptime(value, format)
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
@@ -46,7 +48,7 @@ describe ActiveInteraction::TimeFilter, :filter do
|
|
46
48
|
|
47
49
|
it 'raises an error' do
|
48
50
|
expect do
|
49
|
-
|
51
|
+
result
|
50
52
|
end.to raise_error ActiveInteraction::InvalidValueError
|
51
53
|
end
|
52
54
|
|
@@ -55,10 +57,90 @@ describe ActiveInteraction::TimeFilter, :filter do
|
|
55
57
|
|
56
58
|
it do
|
57
59
|
expect do
|
58
|
-
|
60
|
+
result
|
61
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'with a GroupedInput' do
|
67
|
+
let(:year) { 2012 }
|
68
|
+
let(:month) { 1 }
|
69
|
+
let(:day) { 2 }
|
70
|
+
let(:hour) { 3 }
|
71
|
+
let(:min) { 4 }
|
72
|
+
let(:sec) { 5 }
|
73
|
+
let(:value) do
|
74
|
+
ActiveInteraction::GroupedInput.new(
|
75
|
+
'1' => year.to_s,
|
76
|
+
'2' => month.to_s,
|
77
|
+
'3' => day.to_s,
|
78
|
+
'4' => hour.to_s,
|
79
|
+
'5' => min.to_s,
|
80
|
+
'6' => sec.to_s
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns a Time' do
|
85
|
+
expect(
|
86
|
+
result
|
87
|
+
).to eql Time.new(year, month, day, hour, min, sec)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with an invalid GroupedInput' do
|
92
|
+
context 'empty' do
|
93
|
+
let(:value) { ActiveInteraction::GroupedInput.new }
|
94
|
+
|
95
|
+
it 'raises an error' do
|
96
|
+
expect do
|
97
|
+
result
|
98
|
+
end.to raise_error ActiveInteraction::InvalidValueError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'partial inputs' do
|
103
|
+
let(:value) do
|
104
|
+
ActiveInteraction::GroupedInput.new(
|
105
|
+
'2' => '1'
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'raises an error' do
|
110
|
+
expect do
|
111
|
+
result
|
59
112
|
end.to raise_error ActiveInteraction::InvalidValueError
|
60
113
|
end
|
61
114
|
end
|
62
115
|
end
|
63
116
|
end
|
117
|
+
|
118
|
+
describe '#database_column_type' do
|
119
|
+
it 'returns :datetime' do
|
120
|
+
expect(filter.database_column_type).to eql :datetime
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#default' do
|
125
|
+
context 'with a GroupedInput' do
|
126
|
+
before do
|
127
|
+
options.merge!(
|
128
|
+
default: ActiveInteraction::GroupedInput.new(
|
129
|
+
'1' => '2012',
|
130
|
+
'2' => '1',
|
131
|
+
'3' => '2',
|
132
|
+
'4' => '3',
|
133
|
+
'5' => '4',
|
134
|
+
'6' => '5'
|
135
|
+
)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'raises an error' do
|
140
|
+
expect do
|
141
|
+
filter.default
|
142
|
+
end.to raise_error ActiveInteraction::InvalidDefaultError
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
64
146
|
end
|