universalid 0.1.5 → 0.1.7

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 +45 -2
  3. data/lib/universalid/extensions/active_record/base_unpacker.rb +5 -1
  4. data/lib/universalid/extensions/active_support/cache/entry_message_pack_type.rb +20 -0
  5. data/lib/universalid/extensions/active_support/cache/store_message_pack_type.rb +22 -0
  6. data/lib/universalid/extensions/active_support/time_with_zone_message_pack_type.rb +5 -5
  7. data/lib/universalid/extensions/global_id/global_id_model.rb +4 -6
  8. data/lib/universalid/message_pack_types/{ruby/composites → composites}/struct.rb +2 -1
  9. data/lib/universalid/message_pack_types.rb +12 -12
  10. data/lib/universalid/version.rb +1 -1
  11. data/lib/uri/uid.rb +24 -4
  12. metadata +30 -14
  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: 86ff30c07adad3183e75fcc333e5fbfe4ff060dae6ad7457fa2d0007f1a24dfa
4
- data.tar.gz: 61d5b6fff2f88c78daa6c65797edd56e2770bf253bef8bd14414636b89ae4b76
3
+ metadata.gz: 8adc71d500029efbc4c0c388042413a4676c101da9f2390a300134a6bde37386
4
+ data.tar.gz: be284fbc808de8624d2ae7b622439380785af1bce3c50d5bb2f80e229472a1c2
5
5
  SHA512:
6
- metadata.gz: 03b04be308330e1ff136354af14cdf961ce33bc689a972f246bc4fe131e81b3aa1d482463827c95291f073a22e3377ab16470bee87e6c6c7ab744b52decea695
7
- data.tar.gz: 17533452664536cc5ba5a89437fd877ed7eeaee69ac81354013139d87fd8bde7386b0e087159e455492fc7a402d88ff423ff63e73eb21c51dc84d2389007c2d5
6
+ metadata.gz: 2b40d14b7e93f5559fb5b24f9f47be634a03e083f7d335e951ac9ade937ceca6fc5e382c7e208dcf04d4cd7ab7bb8f0c6ff16748900bca67b60d534afea25651
7
+ data.tar.gz: 5a15af009bb6a4ea4a148c0787d46b94e795a3debed2af7e9d986a0c0b00b48177fd178653c075a6f28874cb8fc91a3d6446b7a3b891294c396ad0a2538449e4
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" />
@@ -38,7 +38,7 @@
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
@@ -165,6 +206,8 @@ The following extension datatypes ship with Universal ID:
165
206
 
166
207
  - `ActiveRecord::Base`
167
208
  - `ActiveRecord::Relation`
209
+ - `ActiveSupport::Cache::Entry`
210
+ - `ActiveSupport::Cache::Store`
168
211
  - `ActiveSupport::TimeWithZone`
169
212
  - `GlobalID`
170
213
  - `SignedGlobalID`
@@ -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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined? ActiveSupport::Cache::Entry
4
+
5
+ UniversalID::MessagePackFactory.register(
6
+ type: ActiveSupport::Cache::Entry,
7
+ packer: ->(obj, packer) do
8
+ packer.write obj.value
9
+ packer.write obj.version
10
+ packer.write obj.expires_at
11
+ end,
12
+ unpacker: ->(unpacker) do
13
+ value = unpacker.read
14
+ version = unpacker.read
15
+ expires_at = unpacker.read
16
+ ActiveSupport::Cache::Entry.new value, version: version, expires_at: expires_at
17
+ end
18
+ )
19
+
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined? ActiveSupport::Cache::Store
4
+
5
+ UniversalID::MessagePackFactory.register(
6
+ type: ActiveSupport::Cache::Store,
7
+ packer: ->(obj, packer) do
8
+ packer.write obj.class.name
9
+ packer.write obj.options
10
+ packer.write obj.instance_variable_get(:@data)
11
+ end,
12
+ unpacker: ->(unpacker) do
13
+ class_name = unpacker.read
14
+ options = unpacker.read
15
+ data = unpacker.read
16
+ Object.const_get(class_name).new(options).tap do |store|
17
+ store.instance_variable_set :@data, data
18
+ end
19
+ end
20
+ )
21
+
22
+ 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
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UniversalID
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.7"
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.5
4
+ version: 0.1.7
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-10 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.
@@ -351,6 +365,8 @@ files:
351
365
  - lib/universalid/extensions/active_record/base_packer.rb
352
366
  - lib/universalid/extensions/active_record/base_unpacker.rb
353
367
  - lib/universalid/extensions/active_record/relation_message_pack_type.rb
368
+ - lib/universalid/extensions/active_support/cache/entry_message_pack_type.rb
369
+ - lib/universalid/extensions/active_support/cache/store_message_pack_type.rb
354
370
  - lib/universalid/extensions/active_support/time_with_zone_message_pack_type.rb
355
371
  - lib/universalid/extensions/global_id/global_id_model.rb
356
372
  - lib/universalid/extensions/global_id/global_id_uid_extension.rb
@@ -358,17 +374,17 @@ files:
358
374
  - lib/universalid/extensions/signed_global_id/message_pack_type.rb
359
375
  - lib/universalid/message_pack_factory.rb
360
376
  - 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
377
+ - lib/universalid/message_pack_types/composites/module.rb
378
+ - lib/universalid/message_pack_types/composites/open_struct.rb
379
+ - lib/universalid/message_pack_types/composites/set.rb
380
+ - lib/universalid/message_pack_types/composites/struct.rb
381
+ - lib/universalid/message_pack_types/scalars/bigdecimal.rb
382
+ - lib/universalid/message_pack_types/scalars/complex.rb
383
+ - lib/universalid/message_pack_types/scalars/date.rb
384
+ - lib/universalid/message_pack_types/scalars/date_time.rb
385
+ - lib/universalid/message_pack_types/scalars/range.rb
386
+ - lib/universalid/message_pack_types/scalars/rational.rb
387
+ - lib/universalid/message_pack_types/scalars/regexp.rb
372
388
  - lib/universalid/message_pack_types/uri/uid/type.rb
373
389
  - lib/universalid/packer.rb
374
390
  - lib/universalid/prepack_database_options.rb
@@ -404,7 +420,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
404
420
  - !ruby/object:Gem::Version
405
421
  version: '0'
406
422
  requirements: []
407
- rubygems_version: 3.3.21
423
+ rubygems_version: 3.2.32
408
424
  signing_key:
409
425
  specification_version: 4
410
426
  summary: Fast, recursive, optimized, URL-Safe serialization for any Ruby object