hash_math 0.0.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 634c0b1692a46994e23fc56cc31346a128182d19da45adaa8913d23898d91b5b
4
- data.tar.gz: 7cf7a06c956965072b2dcb0dfe4e8bbccd962a44380c60289d9e9b377d7c8f6a
3
+ metadata.gz: a13830be433114e0438c34e2e15e136a01ab61c93b57c14e9be2b39a4dc9a7bf
4
+ data.tar.gz: 14a4d2765184d10f6149dd446be54f1cea68c505e308fbf884a982cecc0ad161
5
5
  SHA512:
6
- metadata.gz: e5dfeff921d808b1f77d7e303a076ca5a6d3f50760fb7b9296332c443950679858c3498ba6e2629fbe13cdc5fbc5de5cc5c4a74d491299c434d14b529ef46a29
7
- data.tar.gz: bc92933f546e34bdb244215e715a690db94cf3d85ff156e05c5383406ab831616162567b6862be89ad6e555d77a64504988f760e2b197825013d21e7e8966351
6
+ metadata.gz: b5294dee4cb58c9d28cc829b2d18a51edb6036bf110c2ad94bc827e52d80e6b7417cf96e5b699af812b91d6b9ee9be75e2ba2f796a65234e333f68f3fb587033
7
+ data.tar.gz: '0706231920aa50e791e43820e9544a399d132ff9f5a1ee29cd0fc2c70414ecb181d28350a4aa3f1b5df67bdf904dc93b876246cfdb4e53507e33358e1100de71'
@@ -1,4 +1,8 @@
1
- Metrics/LineLength:
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ NewCops: enable
4
+
5
+ Layout/LineLength:
2
6
  Max: 100
3
7
 
4
8
  Metrics/BlockLength:
@@ -13,9 +17,6 @@ Metrics/BlockLength:
13
17
  Metrics/MethodLength:
14
18
  Max: 25
15
19
 
16
- AllCops:
17
- TargetRubyVersion: 2.3
18
-
19
20
  Metrics/AbcSize:
20
21
  Max: 16
21
22
 
@@ -1 +1 @@
1
- 2.6.3
1
+ 2.6.6
@@ -4,10 +4,9 @@ env:
4
4
  language: ruby
5
5
  rvm:
6
6
  # Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
7
- - 2.3.8
8
- - 2.4.6
9
- - 2.5.5
10
- - 2.6.3
7
+ - 2.5.8
8
+ - 2.6.6
9
+ - 2.7.1
11
10
  cache: bundler
12
11
  before_script:
13
12
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
@@ -1,3 +1,31 @@
1
+ # 1.2.0 (August 19th, 2020)
2
+
3
+ Additions:
4
+
5
+ * HashMath::Mapper
6
+
7
+ # 1.1.0 (August 13th, 2020)
8
+
9
+ Additions:
10
+
11
+ * HashMath::Unpivot
12
+
13
+ Removals:
14
+
15
+ * Support for Ruby < 2.5
16
+
17
+ # 1.0.0 (September 18th, 2019)
18
+
19
+ Initial release.
20
+
21
+ # 1.0.0-alpha (September 16th, 2019)
22
+
23
+ Added initial implementation of:
24
+
25
+ * HashMath::Matrix
26
+ * HashMath::Record
27
+ * HashMath::Table
28
+
1
29
  # 0.0.1 (September 16th, 2019)
2
30
 
3
31
  Published initial shell. Library has no functionality yet.
data/README.md CHANGED
@@ -2,4 +2,355 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/hash_math.svg)](https://badge.fury.io/rb/hash_math) [![Build Status](https://travis-ci.org/bluemarblepayroll/hash_math.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/hash_math) [![Maintainability](https://api.codeclimate.com/v1/badges/9f9a504b3f5df199a253/maintainability)](https://codeclimate.com/github/bluemarblepayroll/hash_math/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/9f9a504b3f5df199a253/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/hash_math/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
 
5
- Under Construction.
5
+ Ruby's Hash data structure is ubiquitous and highly flexible. This library contains common Hash patterns widely used within our code-base:
6
+
7
+ * Matrix: build a hash up then expand into hash products
8
+ * Record: define a list of keys and a base value (for each key) to use as a blueprint for hashes
9
+ * Table: key-value pair builder for constructing a two-dimensional array (rows by fields)
10
+
11
+ See examples for more information on each data structure.
12
+
13
+ ## Installation
14
+
15
+ To install through Rubygems:
16
+
17
+ ````bash
18
+ gem install install hash_math
19
+ ````
20
+
21
+ You can also add this to your Gemfile:
22
+
23
+ ````bash
24
+ bundle add hash_math
25
+ ````
26
+
27
+ ## Examples
28
+
29
+ ### Matrix: The Hash Combination Calculator
30
+
31
+ HashMath::Matrix is a key-value builder that outputs all the different hash combinations.
32
+
33
+ Say we have this type of matrix:
34
+
35
+ ````ruby
36
+ {
37
+ a: [1,2,3],
38
+ b: [4,5,6],
39
+ c: [7,8,9]
40
+ }
41
+ ````
42
+
43
+ We could code this as:
44
+
45
+ ````ruby
46
+ matrix = HashMath::Matrix.new
47
+ matrix.add_each(:a, [1,2,3])
48
+ matrix.add_each(:b, [4,5,6])
49
+ matrix.add_each(:c, [7,8,9])
50
+ ````
51
+
52
+ Note: you can also call `Matrix#add` to only add a single value instead of add_each.
53
+
54
+ and get the combinations by calling to_a:
55
+
56
+ ````ruby
57
+ combinations = matrix.to_a
58
+ ````
59
+
60
+ which would yield the following hashes:
61
+
62
+ ````ruby
63
+ [
64
+ { a: 1, b: 4, c: 7 },
65
+ { a: 1, b: 4, c: 8 },
66
+ { a: 1, b: 4, c: 9 },
67
+ { a: 1, b: 5, c: 7 },
68
+ { a: 1, b: 5, c: 8 },
69
+ { a: 1, b: 5, c: 9 },
70
+ { a: 1, b: 6, c: 7 },
71
+ { a: 1, b: 6, c: 8 },
72
+ { a: 1, b: 6, c: 9 },
73
+
74
+ { a: 2, b: 4, c: 7 },
75
+ { a: 2, b: 4, c: 8 },
76
+ { a: 2, b: 4, c: 9 },
77
+ { a: 2, b: 5, c: 7 },
78
+ { a: 2, b: 5, c: 8 },
79
+ { a: 2, b: 5, c: 9 },
80
+ { a: 2, b: 6, c: 7 },
81
+ { a: 2, b: 6, c: 8 },
82
+ { a: 2, b: 6, c: 9 },
83
+
84
+ { a: 3, b: 4, c: 7 },
85
+ { a: 3, b: 4, c: 8 },
86
+ { a: 3, b: 4, c: 9 },
87
+ { a: 3, b: 5, c: 7 },
88
+ { a: 3, b: 5, c: 8 },
89
+ { a: 3, b: 5, c: 9 },
90
+ { a: 3, b: 6, c: 7 },
91
+ { a: 3, b: 6, c: 8 },
92
+ { a: 3, b: 6, c: 9 },
93
+ ]
94
+ ````
95
+
96
+ Notes:
97
+
98
+ * Matrix implements Ruby's Enumerable, which means you have the ability to iterate over each hash combination like you would any other Enumerable object.
99
+ * Values can be arrays and it still works. For example, `#add` (singular form) will honor the array data type: `matrix.add(:a, [1,2,3])` is **not** the same as: `matrix.add_each(:a, [1,2,3])` but it **is** the same as: `matrix.add(:a, [[1,2,3]])`
100
+ * Keys are type-sensitive and work just like Hash keys work.
101
+
102
+ ### Record: The Hash Prototype
103
+
104
+ HashMath::Record generates a prototype hash and ensures all derived hashes conform to its shape.
105
+
106
+ Say we have a person hash, for example:
107
+
108
+ ````ruby
109
+ {
110
+ id: 1,
111
+ name: 'Matt',
112
+ location: 'Chicago'
113
+ }
114
+ ````
115
+
116
+ we could create a record modeling this:
117
+
118
+ ````ruby
119
+ record = HashMath::Record.new(%i[id name location], 'UNKNOWN')
120
+ ````
121
+
122
+ Then, we could shape all other hashes using it to ensure each key is populated. If a key is not populated, the base value will be used ('UNKNOWN' in our case.) For example:
123
+
124
+ ````ruby
125
+ records = [
126
+ record.make(id: 1, name: 'Matt', location: 'Chicago', dob: nil),
127
+ record.make(id: 2, age: 24),
128
+ record.make(id: 3, location: 'Los Angeles')
129
+ ]
130
+ ````
131
+
132
+ Note: The keys `dob` and `age` appeared in the input but will be ignored as they do not conform to the Record. You could make this strict and raise an error by calling `#make!` instead of `#make`.
133
+
134
+ our `records` would now equate to:
135
+
136
+ ````ruby
137
+ [
138
+ { id: 1, name: 'Matt', location: 'Chicago' },
139
+ { id: 2, name: 'UNKNOWN', location: 'UNKNOWN' },
140
+ { id: 3, name: 'UNKNOWN', location: 'Los Angeles' },
141
+ ]
142
+ ````
143
+
144
+ Notes:
145
+
146
+ * keys are type-sensitive and works just like Hash keys work.
147
+
148
+ ### Table: The Double Hash (Hash of Hashes)
149
+
150
+ HashMath::Table builds on top of HashMath::Record. It constructs a table data-structure using a key-value pair builder method: `#add(row_id, field_id, value)`. It ultimately outputs an array of Row objects where each Row object has a row_id and fields (field_id => value) hash. The value proposition here is you can iterate over a key-value pair and construct each row any way you wish.
151
+
152
+ Building on our Record example above:
153
+
154
+ ````ruby
155
+ record = HashMath::Record.new(%i[name location], 'UNKNOWN')
156
+
157
+ table = HashMath::Table.new(record)
158
+ .add(1, :name, 'Matt')
159
+ .add(1, :location, 'Chicago')
160
+ .add(2, :name, 'Nick')
161
+ .add(3, :location, 'Los Angeles')
162
+ .add(2, :name 'Nicholas') # notice the secondary call to "2, :name"
163
+
164
+ rows = table.to_a
165
+ ````
166
+
167
+ which would set our variable `rows` to:
168
+
169
+ ````ruby
170
+ [
171
+ HashMath::Row.new(1, { name: 'Matt', location: 'Chicago' }),
172
+ HashMath::Row.new(2, { name: 'Nicholas', location: 'UNKNOWN' }),
173
+ HashMath::Row.new(3, { name: 'UNKNOWN', location: 'Los Angeles' })
174
+ ]
175
+ ````
176
+
177
+ Notes:
178
+
179
+ * `#add` will throw a KeyOutOfBoundsError if the key is not found.
180
+ * key is type-sensitive and works just like Hash keys work.
181
+
182
+ ### Unpivot: Hash Key Coalescence and Row Extrapolation
183
+
184
+ Sometimes the expected interface is column-based, but the actual data store is row-based. This causes an impedance between the input set and the persistable data set. HashMath::Unpivot has the ability to extrapolate one hash (row) into multiple hashes (rows) while unpivoting specific keys into key-value pairs.
185
+
186
+ For example: say we have a database table persisting key-value pairs of patient attributes:
187
+
188
+ patient_id | field | value
189
+ ---------- | --------------- | ----------
190
+ 2 | first_exam_date | 2020-01-03
191
+ 2 | last_exam_date | 2020-04-05
192
+ 2 | consent_date | 2020-01-02
193
+
194
+ But our input data looks like this:
195
+
196
+ ````ruby
197
+ patient = {
198
+ patient_id: 2,
199
+ first_exam_date: '2020-01-03',
200
+ last_exam_date: '2020-04-05',
201
+ consent_date: '2020-01-02'
202
+ }
203
+ ````
204
+
205
+ We could use a HashMath::Unpivot to go from one hash to three hashes:
206
+
207
+ ````ruby
208
+ pivot_set = {
209
+ pivots: [
210
+ {
211
+ keys: %i[first_exam_date last_exam_date consent_date],
212
+ coalesce_key: :field,
213
+ coalesce_key_value: :value
214
+ }
215
+ ]
216
+ }
217
+
218
+ rows = HashMath::Unpivot.new(pivot_set).perform(patient)
219
+ ````
220
+
221
+ The `rows` variable should now be equivalent to:
222
+
223
+ ````ruby
224
+ [
225
+ { patient_id: 2, field: :first_exam_date, value: '2020-01-03' },
226
+ { patient_id: 2, field: :last_exam_date, value: '2020-04-05' },
227
+ { patient_id: 2, field: :consent_date, value: '2020-01-02' }
228
+ ]
229
+ ````
230
+
231
+ Note: `HashMath::Unpivot#add` also exists to update an already instantiated object with new pivot_sets.
232
+
233
+ ### Mapper: Constant-Time Cross-Mapper
234
+
235
+ The general use-case for `HashMath::Mapper` is to store pre-materialized lookups and then use those lookups efficiently to fill in missing data for a hash.
236
+
237
+ For example: say we have incoming data representing patients, but this incoming data is value-oriented, not entity/identity-oriented, which may be what we need to ingest it properly:
238
+
239
+ ````ruby
240
+ patient = { patient_id: 2, patient_status: 'active', marital_status: 'married' }
241
+ ````
242
+
243
+ It is not sufficient for us to use patient_status and marital_status, what we need is the entity identifiers for those values. What we can do is load up a HashMath::Mapper instance with these values and let it do the work for us:
244
+
245
+ ````ruby
246
+ patient_statuses = [
247
+ { id: 1, name: 'active' },
248
+ { id: 2, name: 'inactive' },
249
+ { id: 3, name: 'archived' }
250
+ ]
251
+
252
+ marital_statuses = [
253
+ { id: 1, code: 'single' },
254
+ { id: 2, code: 'married' },
255
+ { id: 3, code: 'divorced' }
256
+ ]
257
+
258
+ mappings = [
259
+ {
260
+ lookup: {
261
+ name: :patient_statuses,
262
+ by: :name
263
+ },
264
+ set: :patient_status_id,
265
+ value: :patient_status,
266
+ with: :id
267
+ },
268
+ {
269
+ lookup: {
270
+ name: :marital_statuses,
271
+ by: :code
272
+ },
273
+ set: :marital_status_id,
274
+ value: :marital_status,
275
+ with: :id
276
+ }
277
+ ]
278
+
279
+ mapper = HashMath::Mapper
280
+ .new(mappings)
281
+ .add_each(:patient_statuses, patient_statuses)
282
+ .add_each(:marital_statuses, marital_statuses)
283
+
284
+ mapped_patient = mapper.map(patient)
285
+ ````
286
+
287
+ The variable `mapped_patient` should now be equal to:
288
+
289
+ ````ruby
290
+ {
291
+ patient_id: 2,
292
+ patient_status: 'active',
293
+ marital_status: 'single',
294
+ patient_status_id: 1,
295
+ marital_status_id: 2
296
+ }
297
+ ````
298
+
299
+ ## Contributing
300
+
301
+ ### Development Environment Configuration
302
+
303
+ Basic steps to take to get this repository compiling:
304
+
305
+ 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check hash_math.gemspec for versions supported)
306
+ 2. Install bundler (gem install bundler)
307
+ 3. Clone the repository (git clone git@github.com:bluemarblepayroll/hash_math.git)
308
+ 4. Navigate to the root folder (cd hash_math)
309
+ 5. Install dependencies (bundle)
310
+
311
+ ### Running Tests
312
+
313
+ To execute the test suite and code-coverage tool, run:
314
+
315
+ ````bash
316
+ bundle exec rspec spec --format documentation
317
+ ````
318
+
319
+ Alternatively, you can have Guard watch for changes:
320
+
321
+ ````bash
322
+ bundle exec guard
323
+ ````
324
+
325
+ Also, do not forget to run Rubocop:
326
+
327
+ ````bash
328
+ bundle exec rubocop
329
+ ````
330
+
331
+ or run all three in one command:
332
+
333
+ ````bash
334
+ bundle exec rake
335
+ ````
336
+
337
+ ### Publishing
338
+
339
+ Note: ensure you have proper authorization before trying to publish new versions.
340
+
341
+ After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
342
+
343
+ 1. Merge Pull Request into master
344
+ 2. Update `lib/hash_math/version.rb` using [semantic versioning](https://semver.org/)
345
+ 3. Install dependencies: `bundle`
346
+ 4. Update `CHANGELOG.md` with release notes
347
+ 5. Commit & push master to remote and ensure CI builds master successfully
348
+ 6. Run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
349
+
350
+ ## Code of Conduct
351
+
352
+ Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bluemarblepayroll/hash_math/blob/master/CODE_OF_CONDUCT.md).
353
+
354
+ ## License
355
+
356
+ This project is MIT Licensed.
@@ -19,13 +19,16 @@ Gem::Specification.new do |s|
19
19
  s.homepage = 'https://github.com/bluemarblepayroll/hash_math'
20
20
  s.license = 'MIT'
21
21
 
22
- s.required_ruby_version = '>= 2.3.8'
22
+ s.required_ruby_version = '>= 2.5'
23
+
24
+ s.add_dependency('acts_as_hashable', '~>1')
23
25
 
24
26
  s.add_development_dependency('guard-rspec', '~>4.7')
25
27
  s.add_development_dependency('pry', '~>0')
26
- s.add_development_dependency('rake', '~> 12')
28
+ s.add_development_dependency('pry-byebug', '~>3')
29
+ s.add_development_dependency('rake', '~>13.0')
27
30
  s.add_development_dependency('rspec')
28
- s.add_development_dependency('rubocop', '~>0.74.0')
29
- s.add_development_dependency('simplecov', '~>0.17.0')
30
- s.add_development_dependency('simplecov-console', '~>0.5.0')
31
+ s.add_development_dependency('rubocop', '~>0.88.0')
32
+ s.add_development_dependency('simplecov', '~>0.18.5')
33
+ s.add_development_dependency('simplecov-console', '~>0.7.0')
31
34
  end