cascade-rb 0.1.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 +7 -0
- data/.gitignore +7 -0
- data/.hound.yml +2 -0
- data/.ruby-style.yml +1063 -0
- data/Gemfile +4 -0
- data/README.md +25 -0
- data/Rakefile +9 -0
- data/cascade.gemspec +33 -0
- data/lib/cascade.rb +11 -0
- data/lib/cascade/cascade_csv.rb +13 -0
- data/lib/cascade/columns_matching.rb +41 -0
- data/lib/cascade/complex_fields.rb +3 -0
- data/lib/cascade/complex_fields/boolean.rb +11 -0
- data/lib/cascade/complex_fields/country_iso.rb +12 -0
- data/lib/cascade/complex_fields/currency.rb +23 -0
- data/lib/cascade/concerns/statistics_collectible.rb +17 -0
- data/lib/cascade/data_parser.rb +28 -0
- data/lib/cascade/error_handler.rb +23 -0
- data/lib/cascade/exceptions.rb +2 -0
- data/lib/cascade/exceptions/unknown_presenter_type.rb +3 -0
- data/lib/cascade/exceptions/wrong_mapping_format.rb +3 -0
- data/lib/cascade/helpers/configuration.rb +32 -0
- data/lib/cascade/helpers/hash.rb +5 -0
- data/lib/cascade/row_processor.rb +51 -0
- data/lib/cascade/statistics.rb +58 -0
- data/lib/cascade/statistics_stores.rb +3 -0
- data/lib/cascade/statistics_stores/abstract_store.rb +21 -0
- data/lib/cascade/statistics_stores/array_store.rb +15 -0
- data/lib/cascade/statistics_stores/counter_store.rb +16 -0
- data/lib/cascade/version.rb +4 -0
- data/spec/lib/cascade_csv_spec.rb +21 -0
- data/spec/lib/columns_matching_spec.rb +52 -0
- data/spec/lib/complex_fields/boolean_spec.rb +22 -0
- data/spec/lib/complex_fields/country_iso_spec.rb +24 -0
- data/spec/lib/complex_fields/currency_spec.rb +26 -0
- data/spec/lib/concerns/statistics_collectible_spec.rb +15 -0
- data/spec/lib/data_parser_spec.rb +37 -0
- data/spec/lib/error_handler_spec.rb +32 -0
- data/spec/lib/exceptions/unknown_presenter_type_spec.rb +9 -0
- data/spec/lib/exceptions/wrong_mapping_format_spec.rb +9 -0
- data/spec/lib/helpers/configuration_spec.rb +36 -0
- data/spec/lib/helpers/hash.rb +14 -0
- data/spec/lib/row_processor_spec.rb +71 -0
- data/spec/lib/statistics_spec.rb +39 -0
- data/spec/lib/statistics_stores/abstract_store_spec.rb +22 -0
- data/spec/lib/statistics_stores/array_store_spec.rb +18 -0
- data/spec/lib/statistics_stores/counter_store_spec.rb +19 -0
- data/spec/spec_helper.rb +16 -0
- metadata +264 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cascade
|
2
|
+
module StatisticsStores
|
3
|
+
class AbstractStore
|
4
|
+
def initialize(default_value = nil)
|
5
|
+
@store = default_value || initialize_value
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :store
|
9
|
+
|
10
|
+
def update(*)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def initialize_value(*)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/cascade_csv"
|
3
|
+
|
4
|
+
describe Cascade::CascadeCsv do
|
5
|
+
def described_class
|
6
|
+
Cascade::CascadeCsv
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:filename) { "filename.csv" }
|
10
|
+
|
11
|
+
it "pass default col_sep and quote_char if in not setted" do
|
12
|
+
mock(CSV).open(filename, col_sep: "\t", quote_char: "\0")
|
13
|
+
described_class.open(filename)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "pass specified col_sep and quote_char" do
|
17
|
+
mock(CSV).open(filename, col_sep: "custom_sep", quote_char: "custom_quote")
|
18
|
+
described_class.open(filename, col_sep: "custom_sep",
|
19
|
+
quote_char: "custom_quote")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/columns_matching"
|
3
|
+
|
4
|
+
describe Cascade::ColumnsMatching do
|
5
|
+
def described_class
|
6
|
+
Cascade::ColumnsMatching
|
7
|
+
end
|
8
|
+
subject { described_class.new }
|
9
|
+
|
10
|
+
context "on settings file parsing" do
|
11
|
+
subject { described_class.new(filepath: "config/columns_match.yml") }
|
12
|
+
|
13
|
+
it "raise error if columns matching file doesnt contain mapping key" do
|
14
|
+
mock(YAML).load_file("config/columns_match.yml") { {} }
|
15
|
+
assert_raises(Cascade::WrongMappingFormat) { subject }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raise error if columns matching file doesnt contain any info" do
|
19
|
+
mock(YAML).load_file("config/columns_match.yml") { nil }
|
20
|
+
assert_raises(Cascade::WrongMappingFormat) { subject }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "after file parsed" do
|
25
|
+
before do
|
26
|
+
stub(YAML).load_file do
|
27
|
+
{
|
28
|
+
"mapping" => {
|
29
|
+
"name" => "string",
|
30
|
+
"class" => "string"
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "#supported_keys" do
|
37
|
+
it { subject.must_respond_to(:supported_keys) }
|
38
|
+
it "return array" do
|
39
|
+
subject.supported_keys.must_be_kind_of Array
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#column_type" do
|
44
|
+
it "return curresponding value for passed column value" do
|
45
|
+
assert_equal :string,
|
46
|
+
described_class.new(content: { name: "string" }).column_type(:name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it { delegate_method(:index).to(:supported_keys) }
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/complex_fields/boolean"
|
3
|
+
|
4
|
+
describe Cascade::ComplexFields::Boolean do
|
5
|
+
def described_class
|
6
|
+
Cascade::ComplexFields::Boolean
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:subject) { described_class.new }
|
10
|
+
|
11
|
+
it "return true value for values that seems like true" do
|
12
|
+
["True", "true", "x", "+", true].each do |value|
|
13
|
+
assert subject.call(value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "return false value for values that cant be true" do
|
18
|
+
["false", false, nil, "", "some value"].each do |value|
|
19
|
+
refute subject.call(value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/complex_fields/country_iso"
|
3
|
+
|
4
|
+
describe Cascade::ComplexFields::CountryIso do
|
5
|
+
def described_class
|
6
|
+
Cascade::ComplexFields::CountryIso
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:subject) { described_class.new }
|
10
|
+
|
11
|
+
it "translate country name to alpha-2 code" do
|
12
|
+
assert_equal subject.call("France"), "FR"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raise error if there is no such country" do
|
16
|
+
assert_raises IsoCountryCodes::UnknownCodeError do
|
17
|
+
subject.call("some_blank_value")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "return nil if nil value passed" do
|
22
|
+
assert_nil subject.call(nil)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/complex_fields/currency"
|
3
|
+
|
4
|
+
describe Cascade::ComplexFields::Currency do
|
5
|
+
def described_class
|
6
|
+
Cascade::ComplexFields::Currency
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { described_class.new }
|
10
|
+
|
11
|
+
describe "parse" do
|
12
|
+
it "return nil if input string isn't number" do
|
13
|
+
assert_nil subject.call("0o")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "prepare string to use it as bignum" do
|
17
|
+
assert_kind_of BigDecimal, subject.call("1 123 123 45")
|
18
|
+
assert_kind_of BigDecimal, subject.call("1 123, 123 45")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "return zero if field is empty" do
|
22
|
+
assert_nil subject.call("")
|
23
|
+
assert_nil subject.call(nil)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/concerns/statistics_collectible"
|
3
|
+
|
4
|
+
describe Cascade::StatisticsCollectible do
|
5
|
+
class ExtendableClass; include Cascade::StatisticsCollectible; end
|
6
|
+
subject { ExtendableClass.new }
|
7
|
+
|
8
|
+
it "defines helper for access to statics class" do
|
9
|
+
assert_instance_of Cascade::Statistics, subject.statistics
|
10
|
+
end
|
11
|
+
|
12
|
+
it "delegates register_action method to statistics" do
|
13
|
+
assert_respond_to subject, :register_action
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/data_parser"
|
3
|
+
|
4
|
+
describe Cascade::DataParser do
|
5
|
+
def described_class
|
6
|
+
Cascade::DataParser
|
7
|
+
end
|
8
|
+
|
9
|
+
class FakeDataProvider
|
10
|
+
def open(_)
|
11
|
+
[
|
12
|
+
["Sally Whittaker", "2018", "McCarren House", "312",
|
13
|
+
"3.75", "France", "+", "123.123"],
|
14
|
+
["Jeff Smith", "2018", "Prescott House", "17-D", "3.20",
|
15
|
+
"Austria", "45.12"]
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:filename) { "spec/examples/data_test.txt" }
|
21
|
+
let(:data_provider) { FakeDataProvider.new }
|
22
|
+
|
23
|
+
before do
|
24
|
+
@processor_calls_count = 0
|
25
|
+
@data_saves_count = 0
|
26
|
+
row_processor = ->(_row) { @processor_calls_count += 1 }
|
27
|
+
data_saver = ->(*) { @data_saves_count += 1 }
|
28
|
+
@parser = described_class.new(filename, row_processor: row_processor,
|
29
|
+
data_saver: data_saver, data_provider: data_provider)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "calls row processor for each file line" do
|
33
|
+
@parser.call
|
34
|
+
assert_equal @processor_calls_count, 2
|
35
|
+
assert_equal @data_saves_count, 2
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/error_handler"
|
3
|
+
|
4
|
+
describe Cascade::ErrorHandler do
|
5
|
+
def described_class
|
6
|
+
Cascade::ErrorHandler
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:error_store) do
|
10
|
+
->(row, reason) do
|
11
|
+
@errors ||= []
|
12
|
+
@errors << [row, reason]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
let(:row) { Struct.new(:fields) }
|
16
|
+
|
17
|
+
subject { described_class.new(error_store: error_store) }
|
18
|
+
|
19
|
+
Cascade::ErrorHandler::HANDLING_EXCEPTIONS.each do |exception|
|
20
|
+
it "catch #{exception} and send info to error store" do
|
21
|
+
subject.with_errors_handling(row) { raise exception }
|
22
|
+
assert_includes @errors, [row, exception.to_s]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "DEFAULT_ERROR_STORE" do
|
27
|
+
it "create new array and push row with reason" do
|
28
|
+
result = Cascade::ErrorHandler::DEFAULT_ERROR_STORE.call(:row, :reason)
|
29
|
+
assert_includes result, %i(row reason)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/helpers/configuration"
|
3
|
+
|
4
|
+
describe Configuration do
|
5
|
+
class ExtendableClass
|
6
|
+
extend Configuration
|
7
|
+
|
8
|
+
define_setting :simple_setting
|
9
|
+
define_setting :setting_with_default, 42
|
10
|
+
end
|
11
|
+
|
12
|
+
it "defines class variables with curresponding default values" do
|
13
|
+
assert_equal ExtendableClass.class_variables,
|
14
|
+
[:@@simple_setting, :@@setting_with_default]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "defines reader methods for extended class" do
|
18
|
+
assert_respond_to ExtendableClass, :simple_setting
|
19
|
+
assert_respond_to ExtendableClass, :setting_with_default
|
20
|
+
end
|
21
|
+
|
22
|
+
it "defines write accessor for extended class" do
|
23
|
+
assert_respond_to ExtendableClass, :simple_setting=
|
24
|
+
assert_respond_to ExtendableClass, :setting_with_default=
|
25
|
+
end
|
26
|
+
|
27
|
+
it "allows to set class variables in configuration block" do
|
28
|
+
ExtendableClass.configuration do |config|
|
29
|
+
config.simple_setting = :value
|
30
|
+
config.setting_with_default = 29
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_equal ExtendableClass.setting_with_default, 29
|
34
|
+
assert_equal ExtendableClass.simple_setting, :value
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/helpers/hash"
|
3
|
+
|
4
|
+
describe Hash do
|
5
|
+
describe "#reverse_merge" do
|
6
|
+
let(:another_hash) { { a: 1, b: 2 } }
|
7
|
+
let(:hash) { { a: 4, c: 3 } }
|
8
|
+
|
9
|
+
it "merge self into anothe hash" do
|
10
|
+
assert_equal hash.reverse_merge(another_hash),
|
11
|
+
a: 4, b: 2, c: 3
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cascade/row_processor"
|
3
|
+
|
4
|
+
describe Cascade::RowProcessor do
|
5
|
+
def described_class
|
6
|
+
Cascade::RowProcessor
|
7
|
+
end
|
8
|
+
|
9
|
+
it "allows to set settings" do
|
10
|
+
assert_respond_to described_class, :use_default_presenter=
|
11
|
+
assert_respond_to described_class, :deafult_presenter=
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when parsing row" do
|
15
|
+
let(:row) { [:a_value, :b_value, :c_value] }
|
16
|
+
let(:keys) { [:a, :b, :c] }
|
17
|
+
let(:columns_matching) { OpenStruct.new(supported_keys: keys) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
keys.each_with_index do |key, index|
|
21
|
+
stub(columns_matching).index(key) { index }
|
22
|
+
stub(columns_matching).column_type(key) { :string }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "collect row values with corresponding keys" do
|
27
|
+
processed_row = described_class.new(columns_matching:
|
28
|
+
columns_matching).call(row)
|
29
|
+
assert_equal(processed_row, a: :a_value, b: :b_value, c: :c_value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when presenting row values" do
|
34
|
+
let(:row) { ["raw_value"] }
|
35
|
+
let(:columns_matching) { OpenStruct.new(supported_keys: [:a]) }
|
36
|
+
let(:value_presenter) { ->(_raw_value) { "parsed_value" } }
|
37
|
+
|
38
|
+
before do
|
39
|
+
stub(columns_matching).index(:a) { 0 }
|
40
|
+
stub(columns_matching).column_type(:a) { :presenter }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when curresponding presenter passed" do
|
44
|
+
it "process field with specified presenter" do
|
45
|
+
parsed_value = described_class.new(columns_matching: columns_matching,
|
46
|
+
presenter: value_presenter).call(row)[:a]
|
47
|
+
assert_equal "parsed_value", parsed_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when curresponding presenter not passed" do
|
52
|
+
it "process field with default presenter if use_default_presenter true" do
|
53
|
+
described_class.stub(:use_default_presenter, true) do
|
54
|
+
described_class.stub(:deafult_presenter, -> { value_presenter }) do
|
55
|
+
parsed_value = described_class.new(columns_matching:
|
56
|
+
columns_matching).call(row)[:a]
|
57
|
+
assert_equal "parsed_value", parsed_value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "process field and rise error if use_default_presenter false" do
|
63
|
+
described_class.stub(:use_default_presenter, false) do
|
64
|
+
assert_raises(Cascade::UnknownPresenterType) do
|
65
|
+
described_class.new(columns_matching: columns_matching).call(row)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|