csv_decision2 0.5.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +3 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +85 -0
- data/Dockerfile +6 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +356 -0
- data/benchmarks/rufus_decision.rb +158 -0
- data/csv_decision2.gemspec +38 -0
- data/doc/CSVDecision/CellValidationError.html +143 -0
- data/doc/CSVDecision/Columns/Default.html +589 -0
- data/doc/CSVDecision/Columns/Dictionary.html +801 -0
- data/doc/CSVDecision/Columns/Entry.html +508 -0
- data/doc/CSVDecision/Columns.html +1259 -0
- data/doc/CSVDecision/Constant.html +254 -0
- data/doc/CSVDecision/Data.html +479 -0
- data/doc/CSVDecision/Decide.html +302 -0
- data/doc/CSVDecision/Decision.html +1011 -0
- data/doc/CSVDecision/Defaults.html +291 -0
- data/doc/CSVDecision/Dictionary/Entry.html +1147 -0
- data/doc/CSVDecision/Dictionary.html +426 -0
- data/doc/CSVDecision/Error.html +139 -0
- data/doc/CSVDecision/FileError.html +143 -0
- data/doc/CSVDecision/Function.html +240 -0
- data/doc/CSVDecision/Guard.html +245 -0
- data/doc/CSVDecision/Header.html +647 -0
- data/doc/CSVDecision/Index.html +741 -0
- data/doc/CSVDecision/Input.html +404 -0
- data/doc/CSVDecision/Load.html +296 -0
- data/doc/CSVDecision/Matchers/Constant.html +484 -0
- data/doc/CSVDecision/Matchers/Function.html +511 -0
- data/doc/CSVDecision/Matchers/Guard.html +503 -0
- data/doc/CSVDecision/Matchers/Matcher.html +507 -0
- data/doc/CSVDecision/Matchers/Numeric.html +415 -0
- data/doc/CSVDecision/Matchers/Pattern.html +491 -0
- data/doc/CSVDecision/Matchers/Proc.html +704 -0
- data/doc/CSVDecision/Matchers/Range.html +379 -0
- data/doc/CSVDecision/Matchers/Symbol.html +426 -0
- data/doc/CSVDecision/Matchers.html +1567 -0
- data/doc/CSVDecision/Numeric.html +259 -0
- data/doc/CSVDecision/Options.html +443 -0
- data/doc/CSVDecision/Parse.html +282 -0
- data/doc/CSVDecision/Paths.html +742 -0
- data/doc/CSVDecision/Result.html +1200 -0
- data/doc/CSVDecision/Scan/InputHashes.html +369 -0
- data/doc/CSVDecision/Scan.html +313 -0
- data/doc/CSVDecision/ScanRow.html +866 -0
- data/doc/CSVDecision/Symbol.html +256 -0
- data/doc/CSVDecision/Table.html +1470 -0
- data/doc/CSVDecision/TableValidationError.html +143 -0
- data/doc/CSVDecision/Validate.html +422 -0
- data/doc/CSVDecision.html +621 -0
- data/doc/_index.html +471 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +499 -0
- data/doc/file.README.html +421 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +421 -0
- data/doc/js/app.js +248 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1163 -0
- data/doc/top-level-namespace.html +110 -0
- data/docker-compose.yml +13 -0
- data/lib/csv_decision/columns.rb +192 -0
- data/lib/csv_decision/data.rb +92 -0
- data/lib/csv_decision/decision.rb +196 -0
- data/lib/csv_decision/defaults.rb +47 -0
- data/lib/csv_decision/dictionary.rb +180 -0
- data/lib/csv_decision/header.rb +83 -0
- data/lib/csv_decision/index.rb +107 -0
- data/lib/csv_decision/input.rb +121 -0
- data/lib/csv_decision/load.rb +36 -0
- data/lib/csv_decision/matchers/constant.rb +74 -0
- data/lib/csv_decision/matchers/function.rb +56 -0
- data/lib/csv_decision/matchers/guard.rb +142 -0
- data/lib/csv_decision/matchers/numeric.rb +44 -0
- data/lib/csv_decision/matchers/pattern.rb +94 -0
- data/lib/csv_decision/matchers/range.rb +95 -0
- data/lib/csv_decision/matchers/symbol.rb +149 -0
- data/lib/csv_decision/matchers.rb +220 -0
- data/lib/csv_decision/options.rb +124 -0
- data/lib/csv_decision/parse.rb +165 -0
- data/lib/csv_decision/paths.rb +78 -0
- data/lib/csv_decision/result.rb +204 -0
- data/lib/csv_decision/scan.rb +117 -0
- data/lib/csv_decision/scan_row.rb +142 -0
- data/lib/csv_decision/table.rb +101 -0
- data/lib/csv_decision/validate.rb +85 -0
- data/lib/csv_decision.rb +45 -0
- data/spec/csv_decision/columns_spec.rb +251 -0
- data/spec/csv_decision/constant_spec.rb +36 -0
- data/spec/csv_decision/data_spec.rb +50 -0
- data/spec/csv_decision/decision_spec.rb +19 -0
- data/spec/csv_decision/examples_spec.rb +242 -0
- data/spec/csv_decision/index_spec.rb +58 -0
- data/spec/csv_decision/input_spec.rb +55 -0
- data/spec/csv_decision/load_spec.rb +28 -0
- data/spec/csv_decision/matchers/function_spec.rb +82 -0
- data/spec/csv_decision/matchers/guard_spec.rb +170 -0
- data/spec/csv_decision/matchers/numeric_spec.rb +47 -0
- data/spec/csv_decision/matchers/pattern_spec.rb +183 -0
- data/spec/csv_decision/matchers/range_spec.rb +70 -0
- data/spec/csv_decision/matchers/symbol_spec.rb +67 -0
- data/spec/csv_decision/options_spec.rb +94 -0
- data/spec/csv_decision/parse_spec.rb +44 -0
- data/spec/csv_decision/table_spec.rb +683 -0
- data/spec/csv_decision_spec.rb +7 -0
- data/spec/data/invalid/empty.csv +0 -0
- data/spec/data/invalid/invalid_header1.csv +4 -0
- data/spec/data/invalid/invalid_header2.csv +4 -0
- data/spec/data/invalid/invalid_header3.csv +4 -0
- data/spec/data/invalid/invalid_header4.csv +4 -0
- data/spec/data/valid/benchmark_regexp.csv +10 -0
- data/spec/data/valid/index_example.csv +13 -0
- data/spec/data/valid/multi_column_index.csv +10 -0
- data/spec/data/valid/multi_column_index2.csv +12 -0
- data/spec/data/valid/options_in_file1.csv +5 -0
- data/spec/data/valid/options_in_file2.csv +5 -0
- data/spec/data/valid/options_in_file3.csv +13 -0
- data/spec/data/valid/regular_expressions.csv +11 -0
- data/spec/data/valid/simple_constants.csv +5 -0
- data/spec/data/valid/simple_example.csv +10 -0
- data/spec/data/valid/valid.csv +4 -0
- data/spec/spec_helper.rb +106 -0
- metadata +352 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0a1c5b8a4584339463eb67ce93820087378a62c7edc0b63b61c199b008739604
|
4
|
+
data.tar.gz: e091e00ea144d06fefc692e23639e282f64150305ee5864354d53b91928d9f0d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 744dd6194daf1902d0eab2e302b82cab87c15c3fe9e0413b7f32b68138ccb3267f6601110fec732a8738596b403bb55a7b9b855e20adbaa0eb3f9dc982b53d5b
|
7
|
+
data.tar.gz: d977d550fab93f91ecae7b6ba668383c6bbc14538e7dd798d632cc2e986bc4a9eb89ff141db5d25d89efd0c63391e7858a4f761745a4294dfaa70eb131384ee4
|
data/.codeclimate.yml
ADDED
data/.coveralls.yml
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.3.0
|
3
|
+
|
4
|
+
Metrics/MethodLength:
|
5
|
+
CountComments: false
|
6
|
+
Max: 15
|
7
|
+
|
8
|
+
Metrics/LineLength:
|
9
|
+
Max: 100
|
10
|
+
|
11
|
+
Layout/TrailingWhitespace:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Layout/SpaceInsideArrayPercentLiteral:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Layout/AlignArray:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Layout/IdentArray:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Layout/ExtraSpacing:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/StringLiterals:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/WordArray:
|
30
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
## v0.5.1, 12 February 2018.
|
2
|
+
*Bug Fix*
|
3
|
+
- Fix bug when using a path to scan for a first match.
|
4
|
+
|
5
|
+
## v0.5.0, 11 February 2018.
|
6
|
+
*Additions*
|
7
|
+
- Add the ability to specify an input hash path using path: column types.
|
8
|
+
|
9
|
+
## v0.4.1, 10 February 2018.
|
10
|
+
*Additions*
|
11
|
+
- For consistency, input columns may now use the form `!nil?` to negate 0-arity Ruby methods.
|
12
|
+
(Typical SME users can be somewhat lax with syntax.)
|
13
|
+
|
14
|
+
## v0.4.0, 28 January 2018.
|
15
|
+
*Additions*
|
16
|
+
- Input columns may now use 0-arity Ruby methods to implement conditional logic.
|
17
|
+
For example `.present?` or `nil?`. Negation is also supported - e.g., `!.nil?`.
|
18
|
+
|
19
|
+
## v0.3.2, 28 January 2018.
|
20
|
+
*Changes*
|
21
|
+
- Refactor code and update documentation.
|
22
|
+
|
23
|
+
## v0.3.1, 21 January 2018.
|
24
|
+
*Changes*
|
25
|
+
- Optimize code.
|
26
|
+
|
27
|
+
## v0.3.0, 20 January 2018.
|
28
|
+
*Additions*
|
29
|
+
- Index one or more text-only input columns for faster lookup performance.
|
30
|
+
|
31
|
+
## v0.2.0, 13 January 2018.
|
32
|
+
*Additions*
|
33
|
+
- Set values in the input hash either as a default or unconditionally.
|
34
|
+
|
35
|
+
## v0.1.0, 5 January 2018.
|
36
|
+
*Additions*
|
37
|
+
- Implement more checks on output columns that are duplicated or
|
38
|
+
reference columns that appear after them.
|
39
|
+
|
40
|
+
## v0.0.9, 5 January 2018.
|
41
|
+
*Additions*
|
42
|
+
- Output column if: filter conditions.
|
43
|
+
|
44
|
+
## v0.0.8, 31 December 2017.
|
45
|
+
*Additions*
|
46
|
+
- Guard conditions can use `=~` and `!~` for regular expressions.
|
47
|
+
|
48
|
+
*Fixes*
|
49
|
+
- Bug with column symbol expression not recognising >= and <=.
|
50
|
+
|
51
|
+
## v0.0.7, 30 December 2017.
|
52
|
+
*Additions*
|
53
|
+
- Guard conditions using column symbols and expressions.
|
54
|
+
- Guard columns.
|
55
|
+
- Symbol functions (0-arity) in output columns.
|
56
|
+
- Update YARD documentation.
|
57
|
+
|
58
|
+
## v0.0.6, 26 December 2017.
|
59
|
+
*Additions*
|
60
|
+
- Update YARD documentation.
|
61
|
+
|
62
|
+
## v0.0.5, 26 December 2017.
|
63
|
+
*Additions*
|
64
|
+
- Update YARD documentation.
|
65
|
+
|
66
|
+
## v0.0.4, 26 December 2017.
|
67
|
+
*Additions*
|
68
|
+
- Adds symbol expressions for input columns.
|
69
|
+
- Adds non-string constants for output columns.
|
70
|
+
- Support Ruby 2.5.0
|
71
|
+
- Include YARD documentation.
|
72
|
+
|
73
|
+
*Changes*
|
74
|
+
- Move `benchmark.rb` to `benchmarks` folder and rename to `rufus_decision.rb`
|
75
|
+
|
76
|
+
## v0.0.3, 18 December 2017.
|
77
|
+
*Additions*
|
78
|
+
- Add non-string constants for input columns.
|
79
|
+
|
80
|
+
## v0.0.2, 18 December 2017.
|
81
|
+
*Additions*
|
82
|
+
- Adds accumulate option.
|
83
|
+
|
84
|
+
## v0.0.1, 18 December 2017.
|
85
|
+
- Initial release
|
data/Dockerfile
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Brett Vickers
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,356 @@
|
|
1
|
+
# CSV Decision
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/csv_decision)
|
4
|
+
[](https://travis-ci.org/bpvickers/csv_decision)
|
5
|
+
[](https://coveralls.io/github/bpvickers/csv_decision?branch=master)
|
6
|
+
[](https://codeclimate.com/github/bpvickers/csv_decision/maintainability)
|
7
|
+
[](#license)
|
8
|
+
|
9
|
+
### INFORMATION
|
10
|
+
|
11
|
+
This repo is created while waiting the owner to support latest version
|
12
|
+
|
13
|
+
### CSV based Ruby decision tables
|
14
|
+
|
15
|
+
`csv_decision2` is a RubyGem for CSV based
|
16
|
+
[decision tables](https://en.wikipedia.org/wiki/Decision_table).
|
17
|
+
It accepts decision tables implemented as a
|
18
|
+
[CSV file](https://en.wikipedia.org/wiki/Comma-separated_values),
|
19
|
+
which can then be used to execute complex conditional logic against an input hash,
|
20
|
+
producing a decision as an output hash.
|
21
|
+
|
22
|
+
### Why use `csv_decision2`?
|
23
|
+
|
24
|
+
Typical "business logic" is notoriously illogical - full of corner cases and one-off
|
25
|
+
exceptions.
|
26
|
+
A decision table can express data-based decisions in a way that comes naturally
|
27
|
+
to subject matter experts, who typically use spreadsheet models.
|
28
|
+
Business logic can be encapsulated in a table, avoiding the need for tortuous conditional
|
29
|
+
expressions.
|
30
|
+
|
31
|
+
This gem and the examples below take inspiration from
|
32
|
+
[rufus/decision](https://github.com/jmettraux/rufus-decision).
|
33
|
+
(That gem is no longer maintained and CSV Decision has better
|
34
|
+
decision-time performance, at the expense of slower table parse times and more memory --
|
35
|
+
see `benchmarks/rufus_decision.rb`.)
|
36
|
+
|
37
|
+
### Installation
|
38
|
+
|
39
|
+
To get started, just add `csv_decision2` to your `Gemfile`, and then run `bundle`:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
gem 'csv_decision2'
|
43
|
+
```
|
44
|
+
|
45
|
+
or simply
|
46
|
+
|
47
|
+
```bash
|
48
|
+
gem install csv_decision2
|
49
|
+
```
|
50
|
+
|
51
|
+
### Simple example
|
52
|
+
|
53
|
+
This table considers two input conditions: `topic` and `region`, labeled `in:`.
|
54
|
+
Certain combinations yield an output value for `team_member`, labeled `out:`.
|
55
|
+
|
56
|
+
```
|
57
|
+
in:topic | in:region | out:team_member
|
58
|
+
---------+------------+----------------
|
59
|
+
sports | Europe | Alice
|
60
|
+
sports | | Bob
|
61
|
+
finance | America | Charlie
|
62
|
+
finance | Europe | Donald
|
63
|
+
finance | | Ernest
|
64
|
+
politics | Asia | Fujio
|
65
|
+
politics | America | Gilbert
|
66
|
+
politics | | Henry
|
67
|
+
| | Zach
|
68
|
+
```
|
69
|
+
|
70
|
+
When the topic is `finance` and the region is `Europe` the team member `Donald`
|
71
|
+
is selected. This is a "first match" decision table in that as soon as a match is made
|
72
|
+
execution stops and a single output row (hash) is returned.
|
73
|
+
|
74
|
+
The ordering of rows matters. `Ernest`, who is in charge of `finance` for the rest of
|
75
|
+
the world, except for `America` and `Europe`, _must_ come after his colleagues
|
76
|
+
`Charlie` and `Donald`. `Zach` has been placed last, catching all the input combos
|
77
|
+
not matching any other row.
|
78
|
+
|
79
|
+
Here's the example as code:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# Valid CSV string
|
83
|
+
data = <<~DATA
|
84
|
+
in :topic, in :region, out :team_member
|
85
|
+
sports, Europe, Alice
|
86
|
+
sports, , Bob
|
87
|
+
finance, America, Charlie
|
88
|
+
finance, Europe, Donald
|
89
|
+
finance, , Ernest
|
90
|
+
politics, Asia, Fujio
|
91
|
+
politics, America, Gilbert
|
92
|
+
politics, , Henry
|
93
|
+
, , Zach
|
94
|
+
DATA
|
95
|
+
|
96
|
+
table = CSVDecision.parse(data)
|
97
|
+
|
98
|
+
table.decide(topic: 'finance', region: 'Europe') #=> { team_member: 'Donald' }
|
99
|
+
table.decide(topic: 'sports', region: nil) #=> { team_member: 'Bob' }
|
100
|
+
table.decide(topic: 'culture', region: 'America') #=> { team_member: 'Zach' }
|
101
|
+
```
|
102
|
+
|
103
|
+
An empty `in:` cell means "matches any value".
|
104
|
+
|
105
|
+
Note that all column header names are symbolized, so it's actually more accurate to write
|
106
|
+
`in :topic`; however spaces before and after the `:` do not matter.
|
107
|
+
|
108
|
+
If you have cloned this gem's git repo, then the example can also be run by loading
|
109
|
+
the table from a CSV file:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
table = CSVDecision.parse(Pathname('spec/data/valid/simple_example.csv'))
|
113
|
+
```
|
114
|
+
|
115
|
+
We can also load this same table using the option: `first_match: false`, which means that
|
116
|
+
_all_ matching rows will be accumulated into an array of hashes.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
table = CSVDecision.parse(data, first_match: false)
|
120
|
+
table.decide(topic: 'finance', region: 'Europe') #=> { team_member: %w[Donald Ernest Zach] }
|
121
|
+
```
|
122
|
+
|
123
|
+
For more examples see `spec/csv_decision/table_spec.rb`.
|
124
|
+
Complete documentation of all table parameters is in the code - see
|
125
|
+
`lib/csv_decision/parse.rb` and `lib/csv_decision/table.rb`.
|
126
|
+
|
127
|
+
### CSV Decision features
|
128
|
+
|
129
|
+
- Either returns the first matching row as a hash (default), or accumulates all matches
|
130
|
+
as an array of hashes (i.e., `parse` option `first_match: false` or CSV file option
|
131
|
+
`accumulate`).
|
132
|
+
- Fast decision-time performance (see `benchmarks` folder). Automatically indexes all
|
133
|
+
constants-only columns that do not contain any empty strings.
|
134
|
+
- In addition to strings, can match basic Ruby constants (e.g., `=nil`),
|
135
|
+
regular expressions (e.g., `=~ on|off`), comparisons (e.g., `> 100.0` ) and
|
136
|
+
Ruby-style ranges (e.g., `1..10`)
|
137
|
+
- Can compare an input column versus another input hash key - e.g., `> :column`.
|
138
|
+
- Any cell starting with `#` is treated as a comment, and comments may appear anywhere in
|
139
|
+
the table.
|
140
|
+
- Column symbol expressions or Ruby methods (0-arity) may be used in input columns for
|
141
|
+
matching - e.g., `:column.zero?` or `:column == 0`.
|
142
|
+
- May also use Ruby methods in output columns - e.g., `:column.length`.
|
143
|
+
- Accepts data as a file, CSV string or an array of arrays.
|
144
|
+
|
145
|
+
#### Constants other than strings
|
146
|
+
|
147
|
+
Although `csv_decision` is string oriented, it does recognise other types of constant
|
148
|
+
present in the input hash. Specifically, the following classes are recognized:
|
149
|
+
`Integer`, `BigDecimal`, `NilClass`, `TrueClass` and `FalseClass`.
|
150
|
+
|
151
|
+
This is accomplished by prefixing the value with one of the operators `=`, `==` or `:=`.
|
152
|
+
(The syntax is intentionally lax.)
|
153
|
+
|
154
|
+
For example:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
data = <<~DATA
|
158
|
+
in :constant, out :value
|
159
|
+
:=nil, :=nil
|
160
|
+
==false, ==false
|
161
|
+
=true, =true
|
162
|
+
= 0, = 0
|
163
|
+
:=100.0, :=100.0
|
164
|
+
DATA
|
165
|
+
|
166
|
+
table = CSVDecision.parse(data)
|
167
|
+
table.decide(constant: nil) # returns value: nil
|
168
|
+
table.decide(constant: 0) # returns value: 0
|
169
|
+
table.decide(constant: BigDecimal('100.0')) # returns value: BigDecimal('100.0')
|
170
|
+
```
|
171
|
+
|
172
|
+
#### Column header symbols
|
173
|
+
|
174
|
+
All input and output column names are symbolized, and those symbols may be used to form
|
175
|
+
simple expressions that refer to values in the input hash.
|
176
|
+
|
177
|
+
For example:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
data = <<~DATA
|
181
|
+
in :node, in :parent, out :top?
|
182
|
+
, == :node, yes
|
183
|
+
, , no
|
184
|
+
DATA
|
185
|
+
|
186
|
+
table = CSVDecision.parse(data)
|
187
|
+
table.decide(node: 0, parent: 0) # returns top?: 'yes'
|
188
|
+
table.decide(node: 1, parent: 0) # returns top?: 'no'
|
189
|
+
```
|
190
|
+
|
191
|
+
Note that there is no need to include an input column for `:node` in the decision
|
192
|
+
table - it just needs to be present in the input hash. The expression, `== :node` should be
|
193
|
+
read as `:parent == :node`. It can also be shortened to just `:node`, so the above decision
|
194
|
+
table may be simplified to:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
data = <<~DATA
|
198
|
+
in :parent, out :top?
|
199
|
+
:node, yes
|
200
|
+
, no
|
201
|
+
DATA
|
202
|
+
```
|
203
|
+
|
204
|
+
These comparison operators are also supported: `!=`, `>`, `>=`, `<`, `<=`.
|
205
|
+
In addition, you can also apply a Ruby 0-arity method - e.g., `.present?` or `.nil?`. Negation is
|
206
|
+
also supported - e.g., `!.nil?`. Note that `.nil?` can also be written as `:= nil?`, and `!.nil?`
|
207
|
+
as `:= !nil?`, depending on preference.
|
208
|
+
|
209
|
+
For more simple examples see `spec/csv_decision/examples_spec.rb`.
|
210
|
+
|
211
|
+
#### Input `guard` conditions
|
212
|
+
|
213
|
+
Sometimes it's more convenient to write guard expressions in a single column specialized for that purpose.
|
214
|
+
For example:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
data = <<~DATA
|
218
|
+
in :country, guard:, out :ID, out :ID_type, out :len
|
219
|
+
US, :CUSIP.present?, :CUSIP, CUSIP, :ID.length
|
220
|
+
GB, :SEDOL.present?, :SEDOL, SEDOL, :ID.length
|
221
|
+
, :ISIN.present?, :ISIN, ISIN, :ID.length
|
222
|
+
, :SEDOL.present?, :SEDOL, SEDOL, :ID.length
|
223
|
+
, :CUSIP.present?, :CUSIP, CUSIP, :ID.length
|
224
|
+
, , := nil, := nil, := nil
|
225
|
+
DATA
|
226
|
+
|
227
|
+
table = CSVDecision.parse(data)
|
228
|
+
table.decide(country: 'US', CUSIP: '123456789') #=> { ID: '123456789', ID_type: 'CUSIP', len: 9 }
|
229
|
+
table.decide(country: 'EU', CUSIP: '123456789', ISIN:'123456789012')
|
230
|
+
#=> { ID: '123456789012', ID_type: 'ISIN', len: 12 }
|
231
|
+
```
|
232
|
+
|
233
|
+
Input `guard:` columns may be anonymous, and must contain non-constant expressions. In addition to
|
234
|
+
0-arity Ruby methods, the following comparison operators are allowed: `==`, `!=`,
|
235
|
+
`>`, `>=`, `<` and `<=`. Also, regular expressions are supported - i.e., `=~` and `!~`.
|
236
|
+
|
237
|
+
#### Output `if` conditions
|
238
|
+
|
239
|
+
In some situations it is useful to apply filter conditions _after_ all the output
|
240
|
+
columns have been derived. For example:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
data = <<~DATA
|
244
|
+
in :country, guard:, out :ID, out :ID_type, out :len, if:
|
245
|
+
US, :CUSIP.present?, :CUSIP, CUSIP8, :ID.length, :len == 8
|
246
|
+
US, :CUSIP.present?, :CUSIP, CUSIP9, :ID.length, :len == 9
|
247
|
+
US, :CUSIP.present?, :CUSIP, DUMMY, :ID.length,
|
248
|
+
, :ISIN.present?, :ISIN, ISIN, :ID.length, :len == 12
|
249
|
+
, :ISIN.present?, :ISIN, DUMMY, :ID.length,
|
250
|
+
, :CUSIP.present?, :CUSIP, DUMMY, :ID.length,
|
251
|
+
DATA
|
252
|
+
|
253
|
+
table = CSVDecision.parse(data)
|
254
|
+
table.decide(country: 'US', CUSIP: '123456789') #=> {ID: '123456789', ID_type: 'CUSIP9', len: 9}
|
255
|
+
table.decide(CUSIP: '12345678', ISIN:'1234567890') #=> {ID: '1234567890', ID_type: 'DUMMY', len: 10}
|
256
|
+
```
|
257
|
+
|
258
|
+
Output `if:` columns may be anonymous, and must contain non-constant expressions. In addition to
|
259
|
+
0-arity Ruby methods, the following comparison operators are allowed: `==`, `!=`,
|
260
|
+
`>`, `>=`, `<` and `<=`. Also, regular expressions are supported - i.e., `=~` and `!~`.
|
261
|
+
|
262
|
+
#### Input `set` columns
|
263
|
+
|
264
|
+
If you wish to set default values in the input hash, you can use a `set` column rather
|
265
|
+
than an `in` column. The data row beneath the header is used to specify the default expression.
|
266
|
+
There are three variations: `set` (unconditional default) `set/nil?`(set if `nil?` true)
|
267
|
+
and `set/blank?` (set if `blank?` true).
|
268
|
+
Note that the `decide!` method will mutate the input hash.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
data = <<~DATA
|
272
|
+
set/nil? :country, guard:, set: class, out :PAID, out: len, if:
|
273
|
+
US, , :class.upcase,
|
274
|
+
US, :CUSIP.present?, != PRIVATE, :CUSIP, :PAID.length, :len == 9
|
275
|
+
!=US, :ISIN.present?, != PRIVATE, :ISIN, :PAID.length, :len == 12
|
276
|
+
US, :CUSIP.present?, PRIVATE, :CUSIP, :PAID.length,
|
277
|
+
!=US, :ISIN.present?, PRIVATE, :ISIN, :PAID.length,
|
278
|
+
DATA
|
279
|
+
|
280
|
+
table = CSVDecision.parse(data)
|
281
|
+
table.decide(CUSIP: '1234567890', class: 'Private') #=> {PAID: '1234567890', len: 10}
|
282
|
+
table.decide(ISIN: '123456789012', country: 'GB', class: 'private') #=> {PAID: '123456789012', len: 12}
|
283
|
+
|
284
|
+
```
|
285
|
+
|
286
|
+
#### Input `path` columns
|
287
|
+
|
288
|
+
For hashes that contain sub-hashes, it's possible to specify a path for the purposes
|
289
|
+
of matching. (Arrays are currently not supported.)
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
data = <<~DATA
|
293
|
+
path:, path:, out :value
|
294
|
+
header, , :source_name
|
295
|
+
header, metrics, :service_name
|
296
|
+
payload, , :amount
|
297
|
+
payload, ref_data, :account_id
|
298
|
+
DATA
|
299
|
+
table = CSVDecision.parse(data, first_match: false)
|
300
|
+
|
301
|
+
input = {
|
302
|
+
header: {
|
303
|
+
id: 1, type_cd: 'BUY', source_name: 'Client', client_name: 'AAPL',
|
304
|
+
metrics: { service_name: 'Trading', receive_time: '12:00' }
|
305
|
+
},
|
306
|
+
payload: {
|
307
|
+
tran_id: 9, amount: '100.00',
|
308
|
+
ref_data: { account_id: '5010', type_id: 'BUYL' }
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
table.decide(input) #=> { value: %w[Client Trading 100.00 5010] }
|
313
|
+
```
|
314
|
+
|
315
|
+
### Testing
|
316
|
+
|
317
|
+
`csv_decision` includes thorough [RSpec](http://rspec.info) tests:
|
318
|
+
|
319
|
+
```bash
|
320
|
+
# Execute within a clone of the csv_decision Git repository:
|
321
|
+
bundle install
|
322
|
+
rspec
|
323
|
+
```
|
324
|
+
|
325
|
+
### Planned features
|
326
|
+
|
327
|
+
`csv_decision` is still a work in progress, and will be enhanced to support
|
328
|
+
the following features:
|
329
|
+
|
330
|
+
- Supply a pre-defined library of functions that may be called within input columns to
|
331
|
+
implement custom matching logic, or from the output columns to formulate the final
|
332
|
+
decision.
|
333
|
+
- Built-in lookup functions evaluate other decision tables to implement guard conditions,
|
334
|
+
or supply output values.
|
335
|
+
- Available functions may be extended with a user-supplied library of Ruby methods
|
336
|
+
for custom logic.
|
337
|
+
- Output columns may construct interpolated strings containing references to column
|
338
|
+
symbols.
|
339
|
+
|
340
|
+
### Reasons for the limitations of column expressions
|
341
|
+
|
342
|
+
The simple column expressions allowed by `csv_decision` are purposely limited for reasons of
|
343
|
+
understandability and maintainability. The whole point of this gem is to make decision rules
|
344
|
+
easier to express and comprehend as declarative, tabular logic.
|
345
|
+
While Ruby makes it easy to execute arbitrary code embedded within a CSV file,
|
346
|
+
this could easily result in hard to debug logic that also poses safety risks.
|
347
|
+
|
348
|
+
## Changelog
|
349
|
+
|
350
|
+
See [CHANGELOG.md](./CHANGELOG.md) for a list of changes.
|
351
|
+
|
352
|
+
## License
|
353
|
+
|
354
|
+
CSV Decision © 2017-2018 by [Brett Vickers](mailto:brett@phillips-vickers.com).
|
355
|
+
CSV Decision is licensed under the MIT license. Please see the [LICENSE](./LICENSE)
|
356
|
+
document for more information.
|