csv_decision2 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/csv_decision.svg)](https://badge.fury.io/rb/csv_decision)
|
4
|
+
[![Build Status](https://travis-ci.org/bpvickers/csv_decision.svg?branch=master)](https://travis-ci.org/bpvickers/csv_decision)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/github/bpvickers/csv_decision/badge.svg?branch=master)](https://coveralls.io/github/bpvickers/csv_decision?branch=master)
|
6
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/466a6c52e8f6a3840967/maintainability)](https://codeclimate.com/github/bpvickers/csv_decision/maintainability)
|
7
|
+
[![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#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.
|