universalid 0.1.4 → 0.1.6

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.
Files changed (22) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -8
  3. data/lib/universalid/extensions/active_record/base_unpacker.rb +5 -1
  4. data/lib/universalid/extensions/active_support/time_with_zone_message_pack_type.rb +5 -5
  5. data/lib/universalid/extensions/global_id/global_id_model.rb +4 -6
  6. data/lib/universalid/extensions/global_id/global_id_uid_extension.rb +1 -1
  7. data/lib/universalid/message_pack_types/{ruby/composites → composites}/struct.rb +2 -1
  8. data/lib/universalid/message_pack_types.rb +12 -12
  9. data/lib/universalid/settings.rb +1 -1
  10. data/lib/universalid/version.rb +1 -1
  11. data/lib/uri/uid.rb +24 -4
  12. metadata +29 -15
  13. /data/lib/universalid/message_pack_types/{ruby/composites → composites}/module.rb +0 -0
  14. /data/lib/universalid/message_pack_types/{ruby/composites → composites}/open_struct.rb +0 -0
  15. /data/lib/universalid/message_pack_types/{ruby/composites → composites}/set.rb +0 -0
  16. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/bigdecimal.rb +0 -0
  17. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/complex.rb +0 -0
  18. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/date.rb +0 -0
  19. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/date_time.rb +0 -0
  20. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/range.rb +0 -0
  21. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/rational.rb +0 -0
  22. /data/lib/universalid/message_pack_types/{ruby/scalars → scalars}/regexp.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21f09da739851c57a622a5c625310dfc99f4770f35f16077a7c5871354ec3312
4
- data.tar.gz: 062e4995f803b9e64db5012fb8aa0676c36e903ef3a54731d9e1a6d638943bc8
3
+ metadata.gz: 626443be00555a2fac0c698df128de29ad2169003bc293e1e1df63ff83cfaf19
4
+ data.tar.gz: d4f5f42845c42332abbe2b64e5a2fa4139f08e457f9766cce700735003d88e02
5
5
  SHA512:
6
- metadata.gz: 1de1576929cf3f1c43d74a78aa1b91cf5e5bab778e35677a17819f79ce1c69918c52ef9d003c22248a2d779d6d8bf87cef577fb1c60254e0bf3e7a846d5dc7b2
7
- data.tar.gz: 2338b9f4d77f70b122b5993e3659725d3eeb9c695ea884a1a46aff9e45cd3b23a74ade1ad09c6eeb3f3d26185988befb670619566b4a28b19b8146adbb195cc3
6
+ metadata.gz: e52fef154f9e9dd5660d413707cb2b05619cde795aaf9034ddc311bebe699a40332be0cdc638cc5305bd357473a61f96c15855f88cad8e0e0cbfe33ce56bfb49
7
+ data.tar.gz: 65f63606908e4a22117285d463b5d96878108320ee92fba133d22ec0fe4f8673cddab66a6a61e95fc3a3bf1e876364d03ee2de3541473da8b3f169899cd865bd
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>
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-795-47d299.svg" />
5
+ <img alt="Lines of Code" src="https://img.shields.io/badge/loc-811-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" />
@@ -33,12 +33,12 @@
33
33
  </a>
34
34
  </p>
35
35
 
36
- ## Fast, recursive, and URL-Safe serialization for any Ruby object.
36
+ ## Fast, recursive, optimized, URL-Safe serialization for any Ruby object
37
37
 
38
38
  Universal ID leverages both [MessagePack](https://msgpack.org/) and [Brotli](https://github.com/google/brotli) _(a combo built for speed and best-in-class data compression)_.
39
39
  When combined, these libraries are up to 30% faster and within 2-5% compression rates compared to Protobuf. <a title="Source" href="https://g.co/bard/share/e5bdb17aee91">↗</a>
40
40
 
41
- Universal ID opens the flood gates with a deluge of powerful yet easily implemented [**solutions** ↗](docs/use_cases.md) across a variety of problem domains.
41
+ Universal ID introduces a paradigm shift that enables straightforward simple [**solutions** ↗](docs/use_cases.md) for a variety of complex problem domains.
42
42
 
43
43
  > [!TIP]
44
44
  > All the code examples below can be tested on your local machine. Just clone the repo _(↑or use Gitpod above↑)_ and run `bin/console` to begin exploring.
@@ -48,6 +48,7 @@ Universal ID opens the flood gates with a deluge of powerful yet easily implemen
48
48
 
49
49
  ## Table of Contents
50
50
 
51
+ - [URI::UID](#uriuid)
51
52
  - [Supported Data Types](#supported-data-types)
52
53
  - [Primitive Types](#primitive-types)
53
54
  - [Composite Types](#composite-types)
@@ -64,6 +65,46 @@ Universal ID opens the flood gates with a deluge of powerful yet easily implemen
64
65
 
65
66
  <!-- Tocer[finish]: Auto-generated, don't remove. -->
66
67
 
68
+ ## URI::UID
69
+
70
+ Universal ID introduces a new URI defintion that can recursively serialize any Ruby object into an URL-safe string
71
+ which can be safely transported via HTTP.
72
+
73
+ > [!NOTE]
74
+ > The payload is optimized to be as small as possible... _especially notable with large objects._
75
+
76
+ The best part: **The API is simple.**
77
+
78
+ ```ruby
79
+ data = :ANY_OBJECT_YOU_CAN_IMAGINE
80
+
81
+ uid = URI::UID.build(data)
82
+ #<URI::UID payload=Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQ..., fingerprint=CwWAkccHf6ZTeW1ib2wD>
83
+
84
+ uid.payload
85
+ "Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQUdJTkUD"
86
+
87
+ uid.fingerprint
88
+ "CwWAkccHf6ZTeW1ib2wD"
89
+
90
+ uri = uid.to_s
91
+ "uid://universalid/Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQUdJTkUD#CwWAkccHf6ZTeW1ib2wD"
92
+
93
+ parsed = URI::UID.parse(uri)
94
+ #<URI::UID payload=Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQ..., fingerprint=CwWAkccHf6ZTeW1ib2wD>
95
+
96
+ parsed.decode
97
+ :ANY_OBJECT_YOU_CAN_IMAGINE
98
+
99
+ # it's also possible to parse the payload by itself
100
+
101
+ parsed = URI::UID.from_payload(uid.payload)
102
+ #<URI::UID payload=Cw6AxxoAQU5ZX09CSkVDVF9ZT1VfQ0FOX0lNQ..., fingerprint=CwWAkccHf6ZTeW1ib2wD>
103
+
104
+ parsed.decode
105
+ :ANY_OBJECT_YOU_CAN_IMAGINE
106
+ ```
107
+
67
108
  ## Supported Data Types
68
109
 
69
110
  ### Primitive Types
@@ -101,7 +142,7 @@ uid.decode
101
142
 
102
143
  ### Composite Types
103
144
 
104
- Composite _(or compound, complex, etc.)_ datatype support is where things start to get interesting.
145
+ Composite _(or complex, compound, etc.)_ datatype support is where things start to get interesting.
105
146
  Universal ID supports the following native Ruby composite datatypes:
106
147
 
107
148
  - `Array`
@@ -175,7 +216,7 @@ The following extension datatypes ship with Universal ID:
175
216
  > [!IMPORTANT]
176
217
  > **Why Universal ID with ActiveRecord?**
177
218
  > ActiveRecord already has GlobalID, a robust library for serializing individual models.
178
- > Universal ID covers a much **wider range of use cases**.
219
+ > **Universal ID covers a much wider range of use cases**.
179
220
 
180
221
  Here are a few reasons you may want to consider Universal ID with ActiveRecord.
181
222
 
@@ -195,7 +236,7 @@ Here are a few reasons you may want to consider Universal ID with ActiveRecord.
195
236
  Universal ID gives you control over the serialization process. You can choose which columns to include/exclude, allowing for tailored, optimized payloads to fit your needs.
196
237
 
197
238
  - **Queries/Relations**:
198
- Universal ID extends also supports ActiveRecord::Relation, enabling the serialization of complex database queries and scopes.
239
+ Universal ID also supports ActiveRecord::Relations, enabling the serialization of complex database queries and scopes.
199
240
 
200
241
  In summary, while GlobalID excels in its specific use case, Universal ID offers more power for use-cases that involve unsaved records, complex associations, data cloning, and database queries.
201
242
 
@@ -395,7 +436,7 @@ Fingerprints are comprised of the following components:
395
436
  1. `Class (Class)` - The encoded object's class
396
437
  2. `Timestamp (Time)` - The `mtime` (UTC) of the file that defined the object's class
397
438
 
398
- Fingerprints providate a simple mechanism to help manage versions of the data format... **without the need for explicit versioning**.
439
+ Fingerprints provide a simple mechanism to help manage data format versions... **minimizing the need for custom versioning solutions**.
399
440
  Whenever the class definition changes, the `mtime` updates, resulting in a different fingerprint.
400
441
  This is especially useful in scenarios where the data format evolves over time, such as in long-lived applications.
401
442
 
@@ -466,7 +507,7 @@ copy.save #=> true
466
507
  ```
467
508
 
468
509
  > [!TIP]
469
- > If you don't need a URL-Safe UID, you can use `UniversalID::Packer` to speed things up.
510
+ > If you don't need a URL-Safe UID, you can use `UniversalID::Packer` to speed things up a bit.
470
511
 
471
512
  ```ruby
472
513
  packed = UniversalID::Packer.pack(campaign, options)
@@ -45,6 +45,7 @@ if defined? ActiveRecord
45
45
 
46
46
  new_models = models.select(&:new_record?)
47
47
  models -= new_models
48
+ association_collection = record.public_send(name)
48
49
 
49
50
  # restore persisted models
50
51
  # NOTE: ActiveRecord is smart enough to not re-create or re-add
@@ -52,7 +53,10 @@ if defined? ActiveRecord
52
53
  record.public_send :"#{name}=", models if models.any?
53
54
 
54
55
  # restore new unsaved models
55
- record.public_send(name).target.concat new_models if new_models.any?
56
+ association_collection.target.concat new_models if new_models.any?
57
+
58
+ # mark association relation as loaded
59
+ association_collection.proxy_association.instance_variable_set :@loaded, true
56
60
  end
57
61
  end
58
62
  end
@@ -5,13 +5,13 @@ if defined? ActiveSupport::TimeWithZone
5
5
  UniversalID::MessagePackFactory.register(
6
6
  type: ActiveSupport::TimeWithZone,
7
7
  packer: ->(obj, packer) do
8
- packer.write obj.iso8601(9)
9
- packer.write obj.zone
8
+ packer.write obj.to_time.utc
9
+ packer.write obj.time_zone.tzinfo.identifier
10
10
  end,
11
11
  unpacker: ->(unpacker) do
12
- time = Time.parse(unpacker.read)
13
- zone = unpacker.read
14
- ActiveSupport::TimeWithZone.new time, ActiveSupport::TimeZone[zone]
12
+ utc = unpacker.read
13
+ tz = unpacker.read
14
+ utc.in_time_zone ActiveSupport::TimeZone[tz]
15
15
  end
16
16
  )
17
17
 
@@ -14,14 +14,12 @@ if defined? GlobalID::Identification
14
14
  def initialize(universal_id)
15
15
  @uid = case universal_id
16
16
  when URI::UID then universal_id
17
- when String
18
- case universal_id
19
- when /\A#{URI::UID::SCHEME}/o then URI::UID.parse(universal_id)
20
- else URI::UID.parse(URI::UID.build_string(universal_id, self))
21
- end
17
+ when String then URI::UID.match?(universal_id) ?
18
+ URI::UID.parse(universal_id) :
19
+ URI::UID.from_payload(universal_id)
22
20
  end
23
21
 
24
- @id = @uid&.payload
22
+ @id = uid&.payload
25
23
  end
26
24
  end
27
25
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if defined? GlobalID::Identification && defined? SignedGlobalID
3
+ if defined?(GlobalID::Identification) && defined?(SignedGlobalID)
4
4
 
5
5
  require "forwardable"
6
6
  require_relative "global_id_model"
@@ -11,7 +11,8 @@ UniversalID::MessagePackFactory.register(
11
11
  unpacker: ->(unpacker) do
12
12
  class_name = unpacker.read
13
13
  hash = unpacker.read
14
- klass = Object.const_get(class_name) if Object.const_defined?(class_name)
14
+ klass = Object.const_get(class_name) if class_name && Object.const_defined?(class_name)
15
+ klass ||= Struct.new(*hash.keys)
15
16
 
16
17
  if klass
17
18
  # shenanigans to support ::Ruby 3.0.X and 3.1.X
@@ -3,21 +3,21 @@
3
3
  # NOTE: MessagePack scans registered type in linear order and first match wins
4
4
 
5
5
  # scalars
6
- require_relative "message_pack_types/ruby/scalars/bigdecimal"
7
- require_relative "message_pack_types/ruby/scalars/complex"
8
- require_relative "message_pack_types/ruby/scalars/rational"
9
- require_relative "message_pack_types/ruby/scalars/date_time"
10
- require_relative "message_pack_types/ruby/scalars/date"
11
- require_relative "message_pack_types/ruby/scalars/range"
12
- require_relative "message_pack_types/ruby/scalars/regexp"
6
+ require_relative "message_pack_types/scalars/bigdecimal"
7
+ require_relative "message_pack_types/scalars/complex"
8
+ require_relative "message_pack_types/scalars/rational"
9
+ require_relative "message_pack_types/scalars/date_time"
10
+ require_relative "message_pack_types/scalars/date"
11
+ require_relative "message_pack_types/scalars/range"
12
+ require_relative "message_pack_types/scalars/regexp"
13
13
 
14
14
  # composites
15
- require_relative "message_pack_types/ruby/composites/module"
16
- require_relative "message_pack_types/ruby/composites/open_struct"
17
- require_relative "message_pack_types/ruby/composites/struct"
18
- require_relative "message_pack_types/ruby/composites/set"
15
+ require_relative "message_pack_types/composites/module"
16
+ require_relative "message_pack_types/composites/open_struct"
17
+ require_relative "message_pack_types/composites/struct"
18
+ require_relative "message_pack_types/composites/set"
19
19
 
20
20
  # extensions
21
- Dir["#{__dir__}/extensions/**/*.rb"].each { |f| require f }
21
+ Dir["#{__dir__}/extensions/**/*.rb"].sort.each { |f| require f }
22
22
 
23
23
  UniversalID::MessagePackFactory.create_msgpack_pool
@@ -39,7 +39,7 @@ class UniversalID::Settings
39
39
  in include_keys: include_keys then to.prepack.database.include_keys = !!include_keys
40
40
  in include_timestamps: include_timestamps then to.prepack.database.include_timestamps = !!include_timestamps
41
41
  in include_changes: include_changes then to.prepack.database.include_changes = !!include_changes
42
- in include_unsaved_changes: include_changes then to.prepack.database.include_changes = !!include_unsaved_changes # TODO: Remove in v1.0
42
+ in include_unsaved_changes: include_changes then to.prepack.database.include_changes = !!include_changes # TODO: Remove in v1.0
43
43
  in include_descendants: include_descendants then to.prepack.database.include_descendants = !!include_descendants
44
44
  in descendant_depth: descendant_depth then to.prepack.database.descendant_depth = descendant_depth
45
45
  else # ignore key
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UniversalID
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
data/lib/uri/uid.rb CHANGED
@@ -9,6 +9,7 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
9
9
  VERSION = UniversalID::VERSION
10
10
  SCHEME = "uid"
11
11
  HOST = "universalid"
12
+ PATTERN = /\A#{SCHEME}:\/\/#{HOST}\/[-_0-9A-Z]+#[-_0-9A-Z]+\z/io
12
13
 
13
14
  class << self
14
15
  def encoder
@@ -29,6 +30,11 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
29
30
  new(*::URI.split(value))
30
31
  end
31
32
 
33
+ def match?(uri)
34
+ return true if uri.is_a?(self)
35
+ uri.to_s.match? PATTERN
36
+ end
37
+
32
38
  def build_string(payload, object = nil)
33
39
  "#{SCHEME}://#{HOST}/#{payload}##{fingerprint(object)}"
34
40
  end
@@ -38,6 +44,17 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
38
44
  parse "#{SCHEME}://#{HOST}#{path}##{fingerprint(object)}"
39
45
  end
40
46
 
47
+ def from_payload(payload, object = nil)
48
+ parse(build_string(payload, object)).tap do |uid|
49
+ # NOTE: fingerprint mismatch can happen when building from a UID payload
50
+ # ensure the fingerprint is correct
51
+ if uid&.valid? && URI::UID.fingerprint(uid.decode) != uid.fingerprint
52
+ remove_instance_variable :@decoded_fingerprint if instance_variable_defined?(:@decoded_fingerprint)
53
+ uid.instance_variable_set :@fragment, URI::UID.build(uid.decode).fingerprint
54
+ end
55
+ end
56
+ end
57
+
41
58
  def encode(object, options = {})
42
59
  return yield(object, options) if block_given?
43
60
  encoder.encode object, options
@@ -99,7 +116,7 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
99
116
  end
100
117
 
101
118
  def fingerprint(decode: false)
102
- return decode_fingerprint if decode
119
+ return @decoded_fingerprint ||= decode_fingerprint if decode
103
120
  fragment
104
121
  end
105
122
 
@@ -114,11 +131,14 @@ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
114
131
  !valid?
115
132
  end
116
133
 
117
- def decode
134
+ def decode(force: false)
118
135
  return nil unless valid?
119
- return yield(decode_payload, *decode_fingerprint) if block_given?
120
136
 
121
- decode_payload
137
+ remove_instance_variable :@decoded if force && instance_variable_defined?(:@decoded)
138
+ return @decoded if defined?(@decoded)
139
+
140
+ @decoded ||= yield(decode_payload, *decode_fingerprint) if block_given?
141
+ @decoded ||= decode_payload
122
142
  end
123
143
 
124
144
  def deconstruct_keys(_keys)
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.4
4
+ version: 0.1.6
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: 2024-01-11 00:00:00.000000000 Z
11
+ date: 2024-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -332,6 +332,20 @@ dependencies:
332
332
  - - ">="
333
333
  - !ruby/object:Gem::Version
334
334
  version: '1.32'
335
+ - !ruby/object:Gem::Dependency
336
+ name: timecop
337
+ requirement: !ruby/object:Gem::Requirement
338
+ requirements:
339
+ - - ">="
340
+ - !ruby/object:Gem::Version
341
+ version: '0'
342
+ type: :development
343
+ prerelease: false
344
+ version_requirements: !ruby/object:Gem::Requirement
345
+ requirements:
346
+ - - ">="
347
+ - !ruby/object:Gem::Version
348
+ version: '0'
335
349
  description: |
336
350
  Universal ID opens the flood gates with a deluge of profoundly powerful
337
351
  yet easily implemented new use-cases for your apps and scripts.
@@ -358,17 +372,17 @@ files:
358
372
  - lib/universalid/extensions/signed_global_id/message_pack_type.rb
359
373
  - lib/universalid/message_pack_factory.rb
360
374
  - lib/universalid/message_pack_types.rb
361
- - lib/universalid/message_pack_types/ruby/composites/module.rb
362
- - lib/universalid/message_pack_types/ruby/composites/open_struct.rb
363
- - lib/universalid/message_pack_types/ruby/composites/set.rb
364
- - lib/universalid/message_pack_types/ruby/composites/struct.rb
365
- - lib/universalid/message_pack_types/ruby/scalars/bigdecimal.rb
366
- - lib/universalid/message_pack_types/ruby/scalars/complex.rb
367
- - lib/universalid/message_pack_types/ruby/scalars/date.rb
368
- - lib/universalid/message_pack_types/ruby/scalars/date_time.rb
369
- - lib/universalid/message_pack_types/ruby/scalars/range.rb
370
- - lib/universalid/message_pack_types/ruby/scalars/rational.rb
371
- - lib/universalid/message_pack_types/ruby/scalars/regexp.rb
375
+ - lib/universalid/message_pack_types/composites/module.rb
376
+ - lib/universalid/message_pack_types/composites/open_struct.rb
377
+ - lib/universalid/message_pack_types/composites/set.rb
378
+ - lib/universalid/message_pack_types/composites/struct.rb
379
+ - lib/universalid/message_pack_types/scalars/bigdecimal.rb
380
+ - lib/universalid/message_pack_types/scalars/complex.rb
381
+ - lib/universalid/message_pack_types/scalars/date.rb
382
+ - lib/universalid/message_pack_types/scalars/date_time.rb
383
+ - lib/universalid/message_pack_types/scalars/range.rb
384
+ - lib/universalid/message_pack_types/scalars/rational.rb
385
+ - lib/universalid/message_pack_types/scalars/regexp.rb
372
386
  - lib/universalid/message_pack_types/uri/uid/type.rb
373
387
  - lib/universalid/packer.rb
374
388
  - lib/universalid/prepack_database_options.rb
@@ -404,8 +418,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
404
418
  - !ruby/object:Gem::Version
405
419
  version: '0'
406
420
  requirements: []
407
- rubygems_version: 3.2.32
421
+ rubygems_version: 3.5.3
408
422
  signing_key:
409
423
  specification_version: 4
410
- summary: Fast, recursive, and URL-Safe serialization for any Ruby object.
424
+ summary: Fast, recursive, optimized, URL-Safe serialization for any Ruby object
411
425
  test_files: []