hash_math 0.0.1 → 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 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