
Security News
CISA’s 2025 SBOM Guidance Adds Hashes, Licenses, Tool Metadata, and Context
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.
Ruby's Hash data structure is ubiquitous and highly flexible. This library contains common Hash patterns widely used within our code-base:
See examples for more information on each data structure.
To install through Rubygems:
gem install install hash_math
You can also add this to your Gemfile:
bundle add hash_math
HashMath::Matrix is a key-value builder that outputs all the different hash combinations.
Say we have this type of matrix:
{
a: [1,2,3],
b: [4,5,6],
c: [7,8,9]
}
We could code this as:
matrix = HashMath::Matrix.new
matrix.add_each(:a, [1,2,3])
matrix.add_each(:b, [4,5,6])
matrix.add_each(:c, [7,8,9])
Note: you can also call Matrix#add
to only add a single value instead of add_each.
and get the combinations by calling to_a:
combinations = matrix.to_a
which would yield the following hashes:
[
{ a: 1, b: 4, c: 7 },
{ a: 1, b: 4, c: 8 },
{ a: 1, b: 4, c: 9 },
{ a: 1, b: 5, c: 7 },
{ a: 1, b: 5, c: 8 },
{ a: 1, b: 5, c: 9 },
{ a: 1, b: 6, c: 7 },
{ a: 1, b: 6, c: 8 },
{ a: 1, b: 6, c: 9 },
{ a: 2, b: 4, c: 7 },
{ a: 2, b: 4, c: 8 },
{ a: 2, b: 4, c: 9 },
{ a: 2, b: 5, c: 7 },
{ a: 2, b: 5, c: 8 },
{ a: 2, b: 5, c: 9 },
{ a: 2, b: 6, c: 7 },
{ a: 2, b: 6, c: 8 },
{ a: 2, b: 6, c: 9 },
{ a: 3, b: 4, c: 7 },
{ a: 3, b: 4, c: 8 },
{ a: 3, b: 4, c: 9 },
{ a: 3, b: 5, c: 7 },
{ a: 3, b: 5, c: 8 },
{ a: 3, b: 5, c: 9 },
{ a: 3, b: 6, c: 7 },
{ a: 3, b: 6, c: 8 },
{ a: 3, b: 6, c: 9 },
]
Notes:
#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]])
HashMath::Record generates a prototype hash and ensures all derived hashes conform to its shape.
Say we have a person hash, for example:
{
id: 1,
name: 'Matt',
location: 'Chicago'
}
we could create a record modeling this:
record = HashMath::Record.new(%i[id name location], 'UNKNOWN')
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:
records = [
record.make(id: 1, name: 'Matt', location: 'Chicago', dob: nil),
record.make(id: 2, age: 24),
record.make(id: 3, location: 'Los Angeles')
]
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
.
our records
would now equate to:
[
{ id: 1, name: 'Matt', location: 'Chicago' },
{ id: 2, name: 'UNKNOWN', location: 'UNKNOWN' },
{ id: 3, name: 'UNKNOWN', location: 'Los Angeles' },
]
Notes:
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.
Building on our Record example above:
record = HashMath::Record.new(%i[name location], 'UNKNOWN')
table = HashMath::Table.new(record)
.add(1, :name, 'Matt')
.add(1, :location, 'Chicago')
.add(2, :name, 'Nick')
.add(3, :location, 'Los Angeles')
.add(2, :name 'Nicholas') # notice the secondary call to "2, :name"
rows = table.to_a
which would set our variable rows
to:
[
HashMath::Row.new(1, { name: 'Matt', location: 'Chicago' }),
HashMath::Row.new(2, { name: 'Nicholas', location: 'UNKNOWN' }),
HashMath::Row.new(3, { name: 'UNKNOWN', location: 'Los Angeles' })
]
Notes:
#add
will throw a KeyOutOfBoundsError if the key is not found.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.
For example: say we have a database table persisting key-value pairs of patient attributes:
patient_id | field | value |
---|---|---|
2 | first_exam_date | 2020-01-03 |
2 | last_exam_date | 2020-04-05 |
2 | consent_date | 2020-01-02 |
But our input data looks like this:
patient = {
patient_id: 2,
first_exam_date: '2020-01-03',
last_exam_date: '2020-04-05',
consent_date: '2020-01-02'
}
We could use a HashMath::Unpivot to go from one hash to three hashes:
pivot_set = {
pivots: [
{
keys: %i[first_exam_date last_exam_date consent_date],
coalesce_key: :field,
coalesce_key_value: :value
}
]
}
rows = HashMath::Unpivot.new(pivot_set).perform(patient)
The rows
variable should now be equivalent to:
[
{ patient_id: 2, field: :first_exam_date, value: '2020-01-03' },
{ patient_id: 2, field: :last_exam_date, value: '2020-04-05' },
{ patient_id: 2, field: :consent_date, value: '2020-01-02' }
]
Note: HashMath::Unpivot#add
also exists to update an already instantiated object with new pivot_sets.
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.
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:
patient = { patient_id: 2, patient_status: 'active', marital_status: 'married' }
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:
patient_statuses = [
{ id: 1, name: 'active' },
{ id: 2, name: 'inactive' },
{ id: 3, name: 'archived' }
]
marital_statuses = [
{ id: 1, code: 'single' },
{ id: 2, code: 'married' },
{ id: 3, code: 'divorced' }
]
mappings = [
{
lookup: {
name: :patient_statuses,
by: :name
},
set: :patient_status_id,
value: :patient_status,
with: :id
},
{
lookup: {
name: :marital_statuses,
by: :code
},
set: :marital_status_id,
value: :marital_status,
with: :id
}
]
mapper = HashMath::Mapper
.new(mappings)
.add_each(:patient_statuses, patient_statuses)
.add_each(:marital_statuses, marital_statuses)
mapped_patient = mapper.map(patient)
The variable mapped_patient
should now be equal to:
{
patient_id: 2,
patient_status: 'active',
marital_status: 'single',
patient_status_id: 1,
marital_status_id: 2
}
Basic steps to take to get this repository compiling:
To execute the test suite and code-coverage tool, run:
bundle exec rspec spec --format documentation
Alternatively, you can have Guard watch for changes:
bundle exec guard
Also, do not forget to run Rubocop:
bundle exec rubocop
or run all three in one command:
bundle exec rake
Note: ensure you have proper authorization before trying to publish new versions.
After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
lib/hash_math/version.rb
using semantic versioningbundle
CHANGELOG.md
with release notesbundle 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.Everyone interacting in this codebase, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
This project is MIT Licensed.
FAQs
Unknown package
We found that hash_math demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.
Security News
A clarification on our recent research investigating 60 malicious Ruby gems.
Security News
ESLint now supports parallel linting with a new --concurrency flag, delivering major speed gains and closing a 10-year-old feature request.