universalid 0.1.1 → 0.1.2

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: e55004b7c0b0346e1186784b7962857ae5a010dc85011093703c8e616359c7a9
4
- data.tar.gz: c3ed66a1d494c536851cb642c7b8d538092792ec2e4b78d6b3b4db9f3628d666
3
+ metadata.gz: da76bb6c2a7189ce9dfdd3c51e823bd4953d4e808d87f19bf3454071b6b12b38
4
+ data.tar.gz: 9e5e859338c65f59f6a2db08c7c518591b234ff2351a3e23ecfa34044d6443a3
5
5
  SHA512:
6
- metadata.gz: 8bad79ee6a4b23619b894846e9499493d37d59ee88772a86327ba5d822bbc2436df05c16b5dfbdc39e276002f0703476365a45b79590af95cd2ef7558531cbd5
7
- data.tar.gz: d4a48f15ea08f0cb2f0291f74ff20202c8fd2af431f0e73ba701af178c18f3219d73bc10d8269c9360d6b2f4ffe4a1a521bcbe92ca7688bd7f208acd827ec1a8
6
+ metadata.gz: eaa7ecb09b580b5aa197f3ae806027ea7a403b141617631f4036da0aaf1acd4d6af77a9a0776f45f5d8de192a81445a8d6ca358c6fd3b0bfce0edff31466754e
7
+ data.tar.gz: e43c45212acf57c0cadfea74b18eeeb2f241efd2b9eced81af3e0013aaa76f605d671122fd8c9b2969a513fde62c1a27427e6823c2aa6b6259c7c41ca540f617
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <h1 align="center">Universal ID</h1>
3
3
  <p align="center">
4
4
  <a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
5
- <img alt="Lines of Code" src="https://img.shields.io/badge/loc-721-47d299.svg" />
5
+ <img alt="Lines of Code" src="https://img.shields.io/badge/loc-750-47d299.svg" />
6
6
  </a>
7
7
  <a href="https://codeclimate.com/github/hopsoft/universalid/maintainability">
8
8
  <img src="https://api.codeclimate.com/v1/badges/567624cbe733fafc2330/maintainability" />
@@ -42,44 +42,151 @@ This innovative library transforms any Ruby object into a URL-safe string, enabl
42
42
  It leverages both [MessagePack](https://msgpack.org/) and [Brotli](https://github.com/google/brotli) _(a combo built for speed and best-in-class data compression)_.
43
43
  MessagePack + Brotli is up to 30% faster and within 2-5% compression rates compared to Protobuf. <a title="Source" href="https://g.co/bard/share/e5bdb17aee91">↗</a>
44
44
 
45
- ## Example Use Cases
46
-
47
- - **State Preservation in Web Apps**: Maintain the state of a user's session in web applications without storing data server-side
48
- - **API Data Transfer**: Serialize complex data structures into a URI format for easy and efficient transfer via RESTful APIs
49
- - **Bookmarkable Configurations**: Allow users to bookmark configurations of a web application by embedding the state in the URL
50
- - **Deep Linking in Web Apps**: Create deep links that carry complex state information, allowing users to return to a specific state within an application
51
- - **Debugging Tools**: Serialize objects and their state for logging purposes, aiding in debugging and error tracking
52
- - **Shareable Reports and Views**: Encode the state of reports or customized views in web applications, making them shareable
53
- - **Inter-Service Communication**: Facilitate communication between different services by passing complex objects in a standardized, URL-safe format
54
- - **Client-Side Storage Optimization**: Reduce the need for client-side storage by keeping serialized state in URLs or Cookies
55
- - **Versioning Serialized Objects**: Enable versioning of serialized objects in URLs, allowing users to access different states or versions of data
56
- - **Data Export/Import**: Simplify the export and import process of complex objects between different environments or systems by using URI-encoded data
57
-
58
- This is just a fraction of what's possible with Universal ID. It's an invaluable tool for a range of development needs. API design, data management, user experience, and more. **Endless possibilities!**
59
-
60
45
  <!-- Tocer[start]: Auto-generated, don't remove. -->
61
46
 
62
47
  ## Table of Contents
63
48
 
49
+ - [Example Use Cases](#example-use-cases)
64
50
  - [Supported Data Types](#supported-data-types)
65
51
  - [Scalars](#scalars)
66
52
  - [Composites](#composites)
53
+ - [Custom Types](#custom-types)
67
54
  - [Contributed Types](#contributed-types)
68
- - [ActiveRecord](#activerecord)
69
- - [Why Universal ID with ActiveRecord?](#why-universal-id-with-activerecord)
70
- - [Custom Datatypes](#custom-datatypes)
71
55
  - [Settings and Prepack Options](#settings-and-prepack-options)
72
56
  - [Advanced ActiveRecord](#advanced-activerecord)
73
57
  - [ActiveRecord::Relation Support](#activerecordrelation-support)
74
58
  - [SignedGlobalID](#signedglobalid)
59
+ - [Fingerprinting (Implicit Versioning)](#fingerprinting-implicit-versioning)
75
60
  - [Performance and Benchmarks](#performance-and-benchmarks)
76
61
  - [Sponsors](#sponsors)
77
62
  - [License](#license)
78
63
 
79
64
  <!-- Tocer[finish]: Auto-generated, don't remove. -->
80
65
 
81
- > :rocket: **Ready to Dive In?**: All the code examples below can be tested on your local machine. Simply clone the repo and run `bin/console` to begin exploring. Don't forget to execute `bundle` first to ensure all dependencies are up to date. Happy coding!
66
+ ## Example Use Cases
67
+
68
+ Universal ID's powerful serialization capabilities unlock a myriad of possibilities across various domains.
69
+ Here are a few possibilities.
70
+
71
+ - **State Management for Web Applications**:
72
+ Facilitate seamless user experiences in web applications by preserving and transferring UI states, even across different sessions.
73
+
74
+ - **Data Serialization for Distributed Systems**:
75
+ Enable efficient communication in distributed systems by serializing complex data structures for network transmission.
76
+
77
+ - **Configuration Settings for Software Applications**:
78
+ Store and manage configuration settings for software applications, allowing easy transfer and versioning of settings across installations.
79
+
80
+ - **Session Continuity in Cloud Services**:
81
+ Ensure continuity of user sessions in cloud-based applications, enabling users to pick up their work exactly where they left off, regardless of the device or location.
82
+
83
+ - **Audit Logging for Complex Transactions**:
84
+ Record detailed states of complex transactions in audit logs, providing a comprehensive and reversible record of actions for compliance and analysis.
85
+
86
+ - **Machine-to-Machine Communication**:
87
+ Standardize data formats for machine-to-machine communication, facilitating interoperability and data exchange in IoT and other automated systems.
88
+
89
+ These use cases demonstrate the versatility and power of Universal ID in various application and business scenarios, offering solutions that enhance efficiency, user experience, and system reliability.
90
+
91
+ <details>
92
+ <summary><b>See More Use Cases</b>... ▾</summary>
93
+ <p></p>
94
+
95
+ Harnessing the capabilities of this advanced low-level tool in your libraries and applications might initially seem daunting.
96
+ To assist you in unlocking its full potential, here are some additional suggestions to help you get started.
97
+
98
+ - **API Response Caching**
99
+ Cache complex API responses as serialized strings, allowing for efficient storage and quick retrieval.
100
+
101
+ - **Asset Management in Enterprises**
102
+ Serialize asset information, including status and location, for efficient tracking and management.
103
+
104
+ - **Audit Logging for Financial Transactions**
105
+ Serialize transaction states for audit trails in financial applications, providing detailed and reversible records for compliance.
106
+
107
+ - **Automated Testing of Web Applications**
108
+ Serialize application states to reproduce and test various scenarios automatically.
109
+
110
+ - **Backup and Restore of Application States**
111
+ Create snapshots of application states that can be backed up and later restored.
112
+
113
+ - **Configuration Management in DevOps**
114
+ Serialize configuration settings for software deployments, enabling easy versioning and rollback.
115
+
116
+ - **Content Management Systems (CMS)**
117
+ Serialize page or post states in CMS, enabling advanced versioning and preview functionalities.
118
+
119
+ - **Customer Support Tools**
120
+ Serialize user issues and their context, helping support teams to quickly understand and resolve customer problems.
121
+
122
+ - **Data Migration Between Databases**
123
+ Serialize entire database records for easy transfer between different database systems or formats.
124
+
125
+ - **Educational Platforms**
126
+ Serialize user progress and states in educational platforms, allowing students to pause and resume their learning activities.
127
+
128
+ - **E-commerce Cart Persistence**
129
+ Serialize shopping cart contents, enabling users to return to a filled cart even after their session expires.
130
+
131
+ - **Energy Management Systems**
132
+ Serialize energy usage data from various sensors for analysis and monitoring.
133
+
134
+ - **Environmental Monitoring Systems**
135
+ Serialize sensor data from environmental monitoring systems for analysis and historical record keeping.
136
+
137
+ - **Event Sourcing in Applications**
138
+ Use serialized states for event sourcing, maintaining an immutable log of state changes over time.
139
+
140
+ - **Gaming State Preservation**
141
+ Store game states as serialized strings, allowing players to resume games exactly where they left off.
142
+
143
+ - **Healthcare Data Exchange**
144
+ Securely transfer patient data between different healthcare systems while maintaining the integrity of complex data structures.
145
+
146
+ - **IoT Device State Management**
147
+ Serialize the state of IoT devices for efficient transmission over networks, aiding in remote monitoring and control.
148
+
149
+ - **Legal Document Management**
150
+ Serialize versions of legal documents, maintaining a trail of edits and changes for auditing.
151
+
152
+ - **Machine Learning Data Preparation**
153
+ Serialize complex data structures used in machine learning pipelines for efficient processing.
154
+
155
+ - **Microservice Communication**
156
+ Serialize complex objects for inter-service communication, ensuring efficient data transfer and reducing the need for complex parsing logic.
157
+
158
+ - **Real Estate Portfolio Management**
159
+ Serialize complex property data for portfolio management and analysis.
160
+
161
+ - **Real-time Collaboration Tools**
162
+ Serialize document or application states for real-time collaboration tools, ensuring consistency across different user sessions.
163
+
164
+ - **Research Data Management**
165
+ Serialize research data and experimental setups for ease of sharing and replication of experiments.
166
+
167
+ - **Retail Inventory Management**
168
+ Serialize inventory data, including details of products, for efficient management and tracking.
169
+
170
+ - **Session Continuity Across Devices**
171
+ Store user session data as a serialized string, enabling users to resume their session on a different device without loss of context.
172
+
173
+ - **State Management for Single Page Applications (SPAs)**
174
+ Serialize UI states into URL-safe strings, enabling bookmarking or sharing of specific application states.
175
+
176
+ - **Supply Chain Logistics**
177
+ Serialize complex logistics and shipment data, aiding in efficient tracking and management.
178
+
179
+ - **Telecommunication Network Management**
180
+ Serialize configurations and states of network devices for efficient management and troubleshooting.
181
+
182
+ - **Travel Itinerary Planning**
183
+ Serialize travel plans and itineraries, allowing users to save and share their travel details easily.
82
184
 
185
+ - **Version Control of Design Files**
186
+ Serialize design artifacts for version control in graphic design and CAD applications.
187
+ </details>
188
+
189
+ > :rocket: **Ready to Dive In?**: All the code examples below can be tested on your local machine. Simply clone the repo and run `bin/console` to begin exploring. Don't forget to execute `bundle` first to ensure all dependencies are up to date. Happy coding!
83
190
 
84
191
  ## Supported Data Types
85
192
 
@@ -94,7 +201,6 @@ Universal ID supports most Ruby primitives.
94
201
  - `FalseClass`
95
202
  - `Float`
96
203
  - `Integer`
97
- - `NilClass`
98
204
  - `Range`
99
205
  - `Rational`
100
206
  - `Regexp`
@@ -217,19 +323,79 @@ Composite support is where things start to get interesting. All of the composite
217
323
  ```
218
324
  </details>
219
325
 
326
+ ### Custom Types
327
+
328
+ Universal ID is **extensible** so you can register your own datatypes with specialized serialization rules.
329
+ It couldn't be simpler. Just convert the required data to a Ruby scalar or composite value.
330
+
331
+ <details>
332
+ <summary><b>How to Register your own Datatype</b>... ▾</summary>
333
+ <p></p>
334
+
335
+ ```ruby
336
+ class UserSettings
337
+ attr_accessor :user_id, :preferences
338
+
339
+ def initialize(user_id, preferences = {})
340
+ @user_id = user_id
341
+ @preferences = preferences
342
+ end
343
+ end
344
+
345
+ UniversalID::MessagePackFactory.register(
346
+ type: UserSettings,
347
+ packer: ->(user_preferences, packer) do
348
+ packer.write user_preferences.user_id
349
+ packer.write user_preferences.preferences
350
+ end,
351
+ unpacker: ->(unpacker) do
352
+ user_id = unpacker.read
353
+ preferences = unpacker.read
354
+ UserSettings.new user_id, preferences
355
+ end
356
+ )
357
+
358
+ settings = UserSettings.new(1,
359
+ theme: "dark",
360
+ notifications: "email",
361
+ language: "en",
362
+ layout: "grid",
363
+ privacy: "private"
364
+ )
365
+
366
+ uri = URI::UID.build(settings).to_s
367
+ #=> "uid://universal-id/G1QAQAT-bfcGW1QOgadJwJF06yL8gDnGgfs1Xdti20TDDvG5STPqzbYcQ6TBqVKhdZ39CdQZUwEGe..."
368
+
369
+ uid = URI::UID.parse(uri)
370
+ #=> #<URI::UID uid://universal-id/G1QAQAT-bfcGW1QOgadJwJF06yL8gDnGgfs1Xdti20TDDvG5STPqzbYcQ6TBqVKhdZ39CdQZUwEGe..."
371
+
372
+ uid.decode
373
+ => #<UserSettings:0x0000000139157dd8 @preferences={:theme=>"dark", :notifications=>"email", :language=>"en", :layout=>"grid", :privacy=>"private"}, @user_id=1>
374
+ ```
375
+ </details>
376
+
220
377
  ### Contributed Types
221
378
 
222
379
  Universal ID is designed to be highly extensible, allowing for third-party contributions to enhance its capabilities.
223
380
  These contributions can introduce support for additional data types, further broadening the scope of Universal ID’s utility.
224
381
  The following are some notable contrib extensions:
225
382
 
226
- - **ActiveRecord::Base**: Integrates Universal ID with ActiveRecord base models, enabling intelligent serialization of database records
227
- - **ActiveRecord::Relation**: Supports the serialization of ActiveRecord relations, making it possible to encode complex query structures
228
- - **ActiveSupport::TimeWithZone**: Adds the ability to serialize ActiveSupport's TimeWithZone objects
229
- - **GlobalID**: Extends support to include GlobalIDs
230
- - **SignedGlobalID**: Extends support to include SignedGlobalIDs
383
+ - **ActiveRecord::Base**:
384
+ Integrates Universal ID with ActiveRecord base models, enabling intelligent serialization of database records.
385
+
386
+ - **ActiveRecord::Relation**:
387
+ Supports the serialization of ActiveRecord relations, making it possible to encode complex query structures.
388
+
389
+ - **ActiveSupport::TimeWithZone**:
390
+ Adds the ability to serialize ActiveSupport's TimeWithZone objects.
231
391
 
232
- #### Requiring Contributed Types
392
+ - **GlobalID**:
393
+ Extends support to include GlobalIDs.
394
+
395
+ - **SignedGlobalID**:
396
+ Extends support to include SignedGlobalIDs.
397
+
398
+ **Requiring Contributed Types**
233
399
 
234
400
  To utilize the contributed types, you must explicitly require them in your application.
235
401
  This ensures the extensions are loaded and available for use.
@@ -248,20 +414,29 @@ require "universal_id/contrib/rails"
248
414
 
249
415
  > :bulb: **Implicit Contribs**: Whenever the `Rails` constant is defined, the related contribs are auto-loaded.
250
416
 
251
- ### ActiveRecord
252
-
253
- > :information_source: **Broad Compatibility**: Universal ID has built-in support for ActiveRecord, yet it maintains independence from Rails-specific dependencies. This versatile design enables integration into **any Ruby project**.
417
+ > :bulb: **Broad Compatibility**: Universal ID has built-in support for ActiveRecord, yet it maintains independence from Rails-specific dependencies. This versatile design enables integration into **any Ruby project**.
254
418
 
255
- #### Why Universal ID with ActiveRecord?
419
+ **Why Universal ID with ActiveRecord?**
256
420
 
257
421
  While ActiveRecord already supports GlobalID, a robust library for serializing individual ActiveRecord models, Universal ID extends this functionality to cover a wider range of use cases. Here are a few reasons you may want to consider Universal ID.
258
422
 
259
- - **Support for New Records**: Unlike GlobalID, Universal ID can serialize models that haven't been saved to the database yet
260
- - **Capturing Unsaved Changes**: It can serialize ActiveRecord models with unsaved changes, ensuring that even transient states are captured
261
- - **Association Handling**: Universal ID goes beyond single models. It can serialize associated records, including those with unsaved changes, creating a comprehensive snapshot of complex object states
262
- - **Cloning Existing Records**: Need to make a copy of a record, including its associations? Universal ID handles this effortlessly, making it ideal for duplicating complex datasets
263
- - **Granular Data Control**: With Universal ID, you gain explicit control over the serialization process. You can precisely choose which columns to include or exclude, allowing for tailored, optimized payloads that fit your specific needs
264
- - **Efficient Query Serialization**: Universal ID extends its capabilities to ActiveRecord relations, enabling the serialization of complex queries and scopes. This feature allows for seamless sharing of query logic between processes, ensuring consistency and reducing redundancy in data handling tasks.
423
+ - **Support for New Records**:
424
+ Unlike GlobalID, Universal ID can serialize models that haven't been saved to the database yet.
425
+
426
+ - **Capturing Unsaved Changes**:
427
+ It can serialize ActiveRecord models with unsaved changes, ensuring that even transient states are captured.
428
+
429
+ - **Association Handling**:
430
+ Universal ID goes beyond single models. It can serialize associated records, including those with unsaved changes, creating a comprehensive snapshot of complex object states.
431
+
432
+ - **Cloning Existing Records**:
433
+ Need to make a copy of a record, including its associations? Universal ID handles this effortlessly, making it ideal for duplicating complex datasets.
434
+
435
+ - **Granular Data Control**:
436
+ With Universal ID, you gain explicit control over the serialization process. You can precisely choose which columns to include or exclude, allowing for tailored, optimized payloads that fit your specific needs.
437
+
438
+ - **Efficient Query Serialization**:
439
+ Universal ID extends its capabilities to ActiveRecord relations, enabling the serialization of complex queries and scopes. This feature allows for seamless sharing of query logic between processes, ensuring consistency and reducing redundancy in data handling tasks.
265
440
 
266
441
  In summary, while GlobalID excels in its specific use case, Universal ID offers extended capabilities, particularly useful in scenarios involving unsaved records, complex associations, and data cloning.
267
442
 
@@ -297,57 +472,6 @@ In summary, while GlobalID excels in its specific use case, Universal ID offers
297
472
  ```
298
473
  </details>
299
474
 
300
- ### Custom Datatypes
301
-
302
- Universal ID is **extensible** so you can register your own datatypes with specialized serialization rules.
303
- It couldn't be simpler. Just convert the required data to a Ruby scalar or composite value.
304
-
305
- <details>
306
- <summary><b>How to Register your own Datatype</b>... ▾</summary>
307
- <p></p>
308
-
309
- ```ruby
310
- class UserSettings
311
- attr_accessor :user_id, :preferences
312
-
313
- def initialize(user_id, preferences = {})
314
- @user_id = user_id
315
- @preferences = preferences
316
- end
317
- end
318
-
319
- UniversalID::MessagePackFactory.register(
320
- type: UserSettings,
321
- packer: ->(user_preferences, packer) do
322
- packer.write user_preferences.user_id
323
- packer.write user_preferences.preferences
324
- end,
325
- unpacker: ->(unpacker) do
326
- user_id = unpacker.read
327
- preferences = unpacker.read
328
- UserSettings.new user_id, preferences
329
- end
330
- )
331
-
332
- settings = UserSettings.new(1,
333
- theme: "dark",
334
- notifications: "email",
335
- language: "en",
336
- layout: "grid",
337
- privacy: "private"
338
- )
339
-
340
- uri = URI::UID.build(settings).to_s
341
- #=> "uid://universal-id/G1QAQAT-bfcGW1QOgadJwJF06yL8gDnGgfs1Xdti20TDDvG5STPqzbYcQ6TBqVKhdZ39CdQZUwEGe..."
342
-
343
- uid = URI::UID.parse(uri)
344
- #=> #<URI::UID uid://universal-id/G1QAQAT-bfcGW1QOgadJwJF06yL8gDnGgfs1Xdti20TDDvG5STPqzbYcQ6TBqVKhdZ39CdQZUwEGe..."
345
-
346
- uid.decode
347
- => #<UserSettings:0x0000000139157dd8 @preferences={:theme=>"dark", :notifications=>"email", :language=>"en", :layout=>"grid", :privacy=>"private"}, @user_id=1>
348
- ```
349
- </details>
350
-
351
475
  ## Settings and Prepack Options
352
476
 
353
477
  Universal ID supports a small but powerful set of configuration options for transforming objects before being
@@ -478,7 +602,7 @@ It's also possible to register frequently used options as reusable settings to f
478
602
  ```
479
603
 
480
604
  ```ruby
481
- UniversalID::Settings.register :unsaved, YAML.safe_load("app/config/unsaved.yml")
605
+ UniversalID::Settings.register :unsaved, File.expand_path("app/config/unsaved.yml", __dir__)
482
606
  URI::UID.build @record, UniversalID::Settings[:small_record]
483
607
  ```
484
608
  </details>
@@ -860,6 +984,74 @@ simply convert your UniversalID to a SignedGlobalID to add these features to any
860
984
  ```
861
985
  </details>
862
986
 
987
+ ## Fingerprinting (Implicit Versioning)
988
+
989
+ Fingerprinting adds an extra layer of intelligence to the serialization process.
990
+ UIDs automatically include a "fingerprint" for each serialized object based on the target object's class and
991
+ its modification time _(mtime)_.
992
+
993
+ Fingerprints are comprised of the following components:
994
+
995
+ 1. `Class (Class)` - The encoded object's class
996
+ 2. `Timestamp (Time)` - The mtime (UTC) of the file that defined the object's class
997
+
998
+ > :bulb: **Modification Timestamp**: The `mtime` is detected and captured the moment a UID is created.
999
+
1000
+ Fingerprints allow developers to manage different versions of serialized data effectively...**without the need for custom versioning**.
1001
+ Whenever the class definition changes, the mtime updates, resulting in a different fingerprint.
1002
+ This is especially useful in scenarios where the data format evolves over time, such as in long-lived applications.
1003
+
1004
+ <details>
1005
+ <summary><b>How to Use Fingerprinting</b>... ▾</summary>
1006
+ <p></p>
1007
+
1008
+ 1. Build a UID using a custom handler _(optional Ruby block)_. This allows you to take control of the encoding process.
1009
+
1010
+ ```ruby
1011
+ # NOTE: The campaign model instance was setup earlier in the "Model Instances" section above
1012
+ campaign.save!
1013
+
1014
+ # the uid build target (campaign in this case)
1015
+ # |
1016
+ # | encoding options (whatever was passed to URI::UID.build or {})
1017
+ # | |
1018
+ uid = URI::UID.build(campaign) do |record, options|
1019
+ data = { id: record.id, demo: true }
1020
+ URI::UID.encode data, options.merge(include: %w[id demo]) # block returns the encoded payload
1021
+ end
1022
+ ```
1023
+
1024
+ 2. Decode the UID using a custom handler _(optional Ruby block)_. This allows you to take control of the decoding process.
1025
+
1026
+ ```ruby
1027
+ # fingerprint components
1028
+ # ____|______
1029
+ # the decoded payload from above | |
1030
+ # | | |
1031
+ decoded = URI::UID.parse(uid.to_s).decode do |data, klass, timestamp|
1032
+ record = klass.find_by(id: data[:id])
1033
+ record.instance_variable_set(:@demo, data[:demo])
1034
+
1035
+ case Time.parse(timestamp)
1036
+ when 3.months.ago..Time.now
1037
+ # current data format
1038
+ # return the record as-is
1039
+ record
1040
+ when 1.year.ago..3.months.ago
1041
+ # outdated data format
1042
+ # apply an ETL process to bring the outdated data current
1043
+ # then return the modified record
1044
+ record
1045
+ end
1046
+ end
1047
+ ```
1048
+ </details>
1049
+
1050
+ Fingerprinting allows for seamless handling of different data versions and formats,
1051
+ so you can maintain consistency and reliability in applications dealing with serialized data over time.
1052
+
1053
+ > :bulb: **Optional Usage**: While fingerpint creation is automatic and implicit, using it is optional... ready whenever you want more control.
1054
+
863
1055
  ## Performance and Benchmarks
864
1056
 
865
1057
  <details>
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UniversalID::Contrib::ActiveRecordBaseUnpacker
4
- using UniversalID::Refinements::KernelRefinement
5
-
6
4
  class << self
7
5
  def unpack_with(unpacker)
8
6
  class_name = unpacker.read
@@ -13,7 +11,7 @@ class UniversalID::Contrib::ActiveRecordBaseUnpacker
13
11
  private
14
12
 
15
13
  def create_instance(class_name, attributes)
16
- klass = const_find(class_name)
14
+ klass = Object.const_get(class_name) if Object.const_defined?(class_name)
17
15
  return nil unless klass
18
16
 
19
17
  record = if attributes[klass.primary_key]
@@ -15,7 +15,7 @@ class UniversalID::Contrib::GlobalIDModel
15
15
  when String
16
16
  case universal_id
17
17
  when /\A#{URI::UID::SCHEME}/o then URI::UID.parse(universal_id)
18
- else URI::UID.parse(URI::UID.build_string(universal_id))
18
+ else URI::UID.parse(URI::UID.build_string(universal_id, self))
19
19
  end
20
20
  end
21
21
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
4
+ type: Module,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.name },
7
+ unpacker: ->(unpacker) do
8
+ name = unpacker.read
9
+ Object.const_defined?(name) ? Object.const_get(name) : nil
10
+ end
11
+ )
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- using UniversalID::Refinements::KernelRefinement
4
-
5
3
  UniversalID::MessagePackFactory.register(
6
4
  type: Struct,
7
5
  recreate_pool: false,
@@ -13,11 +11,13 @@ UniversalID::MessagePackFactory.register(
13
11
  unpacker: ->(unpacker) do
14
12
  class_name = unpacker.read
15
13
  hash = unpacker.read
16
- klass = const_find(class_name)
14
+ klass = Object.const_get(class_name) if Object.const_defined?(class_name)
17
15
 
18
- # shenanigans to support ::Ruby 3.0.X and 3.1.X
19
- RUBY_VERSION.start_with?("3.0", "3.1") ?
20
- klass.new.tap { |struct| hash.each { |key, val| struct[key] = hash[key] } } :
21
- klass.new(**hash)
16
+ if klass
17
+ # shenanigans to support ::Ruby 3.0.X and 3.1.X
18
+ RUBY_VERSION.start_with?("3.0", "3.1") ?
19
+ klass.new.tap { |struct| hash.each { |key, val| struct[key] = hash[key] } } :
20
+ klass.new(**hash)
21
+ end
22
22
  end
23
23
  )
@@ -11,6 +11,7 @@ require_relative "message_pack_types/ruby/scalars/range"
11
11
  require_relative "message_pack_types/ruby/scalars/regexp"
12
12
 
13
13
  # composites
14
+ require_relative "message_pack_types/ruby/composites/module"
14
15
  require_relative "message_pack_types/ruby/composites/open_struct"
15
16
  require_relative "message_pack_types/ruby/composites/struct"
16
17
  require_relative "message_pack_types/ruby/composites/set"
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "refinements"
4
-
5
3
  class UniversalID::Prepacker
6
- using UniversalID::Refinements::KernelRefinement
7
4
  using UniversalID::Refinements::ArrayRefinement
8
5
  using UniversalID::Refinements::HashRefinement
9
6
  using UniversalID::Refinements::SetRefinement
@@ -2,7 +2,6 @@
2
2
 
3
3
  module UniversalID::Refinements; end
4
4
 
5
- require_relative "refinements/kernel_refinement"
6
5
  require_relative "refinements/array_refinement"
7
6
  require_relative "refinements/hash_refinement"
8
7
  require_relative "refinements/set_refinement"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UniversalID
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/universal_id.rb CHANGED
@@ -6,6 +6,7 @@ require "ostruct"
6
6
  require "uri"
7
7
 
8
8
  require_relative "universal_id/version"
9
+ require_relative "universal_id/refinements"
9
10
  require_relative "universal_id/settings"
10
11
  require_relative "universal_id/encoder"
11
12
  require_relative "uri/uid"
data/lib/uri/uid.rb CHANGED
@@ -4,24 +4,40 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
4
4
 
5
5
  module URI
6
6
  class UID < ::URI::Generic
7
- extend Forwardable
8
-
7
+ VERSION = UniversalID::VERSION
9
8
  SCHEME = "uid"
10
9
  HOST = "universal-id"
11
10
 
12
11
  class << self
12
+ def encoder
13
+ UniversalID::Encoder
14
+ end
15
+
16
+ def fingerprint(object)
17
+ encode fingerprint_components(object)
18
+ end
19
+
13
20
  def parse(value)
14
21
  components = ::URI.split(value.to_s)
15
22
  new(*components)
16
23
  end
17
24
 
18
- def build_string(payload)
19
- "#{SCHEME}://#{HOST}/#{payload}"
25
+ def build_string(payload, object = nil)
26
+ "#{SCHEME}://#{HOST}/#{payload}##{fingerprint(object)}"
20
27
  end
21
28
 
22
- def build(object, options = {})
23
- path = "/#{UniversalID::Encoder.encode(object, options)}"
24
- parse "#{SCHEME}://#{HOST}#{path}"
29
+ def build(object, options = {}, &block)
30
+ path = "/#{encode(object, options, &block)}"
31
+ parse "#{SCHEME}://#{HOST}#{path}##{fingerprint(object)}"
32
+ end
33
+
34
+ def encode(object, options = {})
35
+ return yield(object, options) if block_given?
36
+ encoder.encode object, options
37
+ end
38
+
39
+ def decode(...)
40
+ encoder.decode(...)
25
41
  end
26
42
 
27
43
  # Creates a new URI::UID with the given URI components.
@@ -53,15 +69,33 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
53
69
  end
54
70
  end
55
71
  end
72
+
73
+ private
74
+
75
+ def fingerprint_components(object)
76
+ klass = object.is_a?(Class) ? object : object.class
77
+ tokens = [klass]
78
+
79
+ begin
80
+ path = const_source_location(klass.name).first.to_s
81
+ tokens << ::File.mtime(path).utc if ::File.exist?(path)
82
+ rescue => e
83
+ UniversalID.logger&.warn "URI::UID#fingerprint: Unable to determine the source location for #{klass.name}!\n#{e.message}}"
84
+ end
85
+
86
+ tokens
87
+ end
56
88
  end
57
89
 
90
+ alias_method :fingerprint, :fragment
91
+
58
92
  def payload(truncate: false)
59
93
  (truncate && path.length > 80) ? "#{path[1..77]}..." : path[1..]
60
94
  end
61
95
 
62
96
  def valid?
63
97
  case self
64
- in scheme: SCHEME, host: HOST, path: p if p.size >= 8 then return true
98
+ in scheme: SCHEME, host: HOST, path: p, fragment: _ if p.size >= 8 then return true
65
99
  else false
66
100
  end
67
101
  end
@@ -71,16 +105,29 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
71
105
  end
72
106
 
73
107
  def decode
74
- UniversalID::Encoder.decode(payload) if valid?
108
+ return nil unless valid?
109
+ return yield(decode_payload, *decode_fingerprint) if block_given?
110
+
111
+ decode_payload
75
112
  end
76
113
 
77
114
  def deconstruct_keys(_keys)
78
- {scheme: scheme, host: host, path: path}
115
+ {scheme: scheme, host: host, path: path, fragment: fragment}
79
116
  end
80
117
 
81
118
  def inspect
82
119
  "#<URI::UID scheme=#{scheme}, host=#{host}, payload=#{payload truncate: true}>"
83
120
  end
121
+
122
+ private
123
+
124
+ def decode_payload
125
+ self.class.decode payload
126
+ end
127
+
128
+ def decode_fingerprint
129
+ self.class.decode fingerprint
130
+ end
84
131
  end
85
132
 
86
133
  # Register the URI scheme
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: universalid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Hopkins (hopsoft)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-14 00:00:00.000000000 Z
11
+ date: 2023-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: brotli
@@ -281,6 +281,7 @@ files:
281
281
  - lib/universal_id/encoder.rb
282
282
  - lib/universal_id/message_pack_factory.rb
283
283
  - lib/universal_id/message_pack_types.rb
284
+ - lib/universal_id/message_pack_types/ruby/composites/module.rb
284
285
  - lib/universal_id/message_pack_types/ruby/composites/open_struct.rb
285
286
  - lib/universal_id/message_pack_types/ruby/composites/set.rb
286
287
  - lib/universal_id/message_pack_types/ruby/composites/struct.rb
@@ -297,7 +298,6 @@ files:
297
298
  - lib/universal_id/refinements.rb
298
299
  - lib/universal_id/refinements/array_refinement.rb
299
300
  - lib/universal_id/refinements/hash_refinement.rb
300
- - lib/universal_id/refinements/kernel_refinement.rb
301
301
  - lib/universal_id/refinements/open_struct_refinement.rb
302
302
  - lib/universal_id/refinements/set_refinement.rb
303
303
  - lib/universal_id/settings.rb
@@ -326,7 +326,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
326
326
  - !ruby/object:Gem::Version
327
327
  version: '0'
328
328
  requirements: []
329
- rubygems_version: 3.2.33
329
+ rubygems_version: 3.4.15
330
330
  signing_key:
331
331
  specification_version: 4
332
332
  summary: URL-Safe String Serialization for any Ruby Object
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module UniversalID::Refinements::KernelRefinement
4
- refine Kernel do
5
- # Finds a constant by name, starting at the root namespace (i.e. ::Object)
6
- def const_find(name)
7
- return nil unless name.is_a?(String)
8
- names = name.split("::")
9
- constant = Object
10
-
11
- while names.any?
12
- value = names.shift
13
- constant = constant.const_get(value) if constant.const_defined?(value)
14
- end
15
-
16
- (constant.name == name) ? constant : nil
17
- end
18
- end
19
- end