README.adoc
= id_pack
image:https://img.shields.io/gem/v/id_pack.svg[
Gem Version, link="https://rubygems.org/gems/id_pack"]
image:https://github.com/riboseinc/id_pack/actions/workflows/tests.yml/badge.svg[
Build Status, link="https://github.com/riboseinc/id_pack/actions/workflows/tests.yml"]
image:https://api.codeclimate.com/v1/badges/655d7aa547daa7b45148/maintainability[
"Code Climate - Maintainability", link="https://codeclimate.com/github/riboseinc/id_pack/maintainability"]
image:https://img.shields.io/codecov/c/github/riboseinc/id_pack.svg[
"Test Coverage", link="https://codecov.io/gh/riboseinc/id_pack"]
== Introduction
This gem provides functionality to compress and decompress two different types
of objects:
1. a contiguous range or multiple contiguous ranges of integer ID
2. a set of UUID
as a single string.
Both Ruby and Javascript implementations are provided.
Javascript support for Rails is provided by means of a Rails engine (`IdPack::Engine`).
Further, for integer ID ranges, it provides serialization of a concept called
`sync_str`, which stands for "synchronization string".
`sync_str` represents a mapping from ID to timestamp.
It is typically generated on the client side for the server side.
Armed with the `sync_str`, the server knows which objects the client requires,
by comparing the timestamps from the `sync_str` with the update times of the
corresponding objects from the database, so synchronization could be done with
minimal waste of bandwidth.
Timestamps are really just integers, _e.g._ `Time.now.to_i`.
== Installation
There are at least two ways to install this:
A. The quickest way is to `gem install` on the terminal:
+
[source,bash]
....
gem install id_pack
....
B. If your application has a `Gemfile`, add the following line:
+
[source,ruby]
....
gem 'id_pack'
....
+
And then execute:
+
[source,bash]
....
bundle
....
== Usage
To experiment with the code, run `bin/console` for an interactive prompt.
[source,ruby]
----
require 'id_pack'
----
=== IdPacker
[source,ruby]
----
id_packer = IdPack::IdPacker.new
----
To encode/decode a set of IDs:
[source,ruby]
----
ids = [5, 6, 21, 23, 25]
encoded_ids = id_packer.encode(ids) #=> "_F~C_P.V"
decoded_ids = id_packer.decode(encoded_ids) #=> [5, 6, 21, 23, 25]
ids == decoded_ids #=> true
----
To work with `sync_str`:
[source,ruby]
----
ids_synced_at = {
1 => 1510294889,
2 => 1510292639,
10 => 1510279639,
}
sync_str = id_packer.encode_sync_str(ids_synced_at)
#=> "IwVmAYCYHYE4DYDMsg,IxA,IwVgTCAMQ,ExA,IwZgDBQ,IwBiA,AxA"
decoded_sync_map = id_packer.decode_sync_str(sync_str)
#=> {1=>1510294889, 2=>1510292639, 10=>1510279639}
ids_synced_at == decoded_sync_map
#=> true
----
==== Advanced `sync_str` usage
`IdPacker#decode_sync_str` optionally supports a `base_timestamp`:
[source,ruby]
----
base_timestamp2 = 1000
decoded_sync_map2 = id_packer.decode_sync_str(sync_str, base_timestamp2)
#=> {1=>1510295889, 2=>1510293639, 10=>1510280639}
base_timestamp3 = - ids_synced_at.min[1]
decoded_sync_map3 = id_packer.decode_sync_str(sync_str, base_timestamp3)
#=> {1=>0, 2=>-2250, 10=>-15250}
base_timestamp4 = - ids_synced_at.max[1]
decoded_sync_map4 = id_packer.decode_sync_str(sync_str, base_timestamp4)
#=> {1=>15250, 2=>13000, 10=>0}
----
`base_timestamp` is typically specified to reverse any normalization of
timestamps done on the client side (not shown in this README).
=== UuidPacker
[source,ruby]
----
uuid_packer = IdPack::UuidPacker.new
----
[source,ruby]
----
uuids = [
'ea8bed36-a73d-4fff-af36-32162274dfd1',
'22347af1-7c60-48e0-8cc5-a30746812267',
'3e514775-bfdb-44f9-92b2-c4c53a7dc89d',
]
----
To encode/decode a set of UUIDs, first, specify the set of encoding characters:
[source,ruby]
----
base_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-_'
----
Also, decide whether we want to preserve the order of the UUIDs:
[source,ruby]
----
ordered = true
----
[source,ruby]
----
encoded_uuids = uuid_packer.alphanum_compress(uuids, base_string, ordered)
#=> "Rf2XIjQmNnr_8rzlbLfRV+ZEgWLgxaMxBxIGPo9eLES5E75coyNNSZ8i2_tdxRT4"
decoded_uuids = uuid_packer.alphanum_decompress(encoded_uuids, base_string)
#=> ["ea8bed36-a73d-4fff-af36-32162274dfd1", "22347af1-7c60-48e0-8cc5-a30746812267", "3e514775-bfdb-44f9-92b2-c4c53a7dc89d"]
uuids == decoded_uuids
#=> true
----
If we don't care for the order:
[source,ruby]
----
ordered = false
----
[source,ruby]
----
unordered_encoded_uuids = uuid_packer.alphanum_compress(uuids, base_string, ordered)
unordered_decoded_uuids = uuid_packer.alphanum_decompress(unordered_encoded_uuids, base_string)
uuids == unordered_decoded_uuids
#=> false
uuids.sort == unordered_decoded_uuids.sort
#=> true
----
== Development
After checking out the repo, run `bin/setup` to install dependencies.
Then, run `bundle exec rake spec` to run the tests.
You can also run `bin/console` for an interactive prompt that will allow you to
experiment.
== Contributing
Bug reports and pull requests are welcome on GitHub at
https://github.com/riboseinc/id_pack. This project is intended to be a
safe, welcoming space for collaboration, and contributors are expected
to adhere to the http://contributor-covenant.org[Contributor Covenant]
code of conduct.
== License
The gem is available as open source under the terms of the
http://opensource.org/licenses/MIT[MIT License].