universalid 0.0.1 → 0.1.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +959 -186
  3. data/Rakefile +1 -5
  4. data/config/default.yml +12 -0
  5. data/config/example.yml +45 -0
  6. data/lib/universal_id/contrib/active_record/base_message_pack_type.rb +11 -0
  7. data/lib/universal_id/contrib/active_record/base_packer.rb +130 -0
  8. data/lib/universal_id/contrib/active_record/base_unpacker.rb +52 -0
  9. data/lib/universal_id/contrib/active_record/relation_message_pack_type.rb +16 -0
  10. data/lib/universal_id/contrib/active_record.rb +4 -0
  11. data/lib/universal_id/contrib/active_support/time_with_zone_message_pack_type.rb +14 -0
  12. data/lib/universal_id/contrib/active_support.rb +3 -0
  13. data/lib/universal_id/contrib/global_id/global_id_model.rb +24 -0
  14. data/lib/universal_id/contrib/global_id/global_id_uid_extension.rb +36 -0
  15. data/lib/universal_id/contrib/global_id/message_pack_type.rb +15 -0
  16. data/lib/universal_id/contrib/global_id.rb +3 -0
  17. data/lib/universal_id/contrib/rails.rb +6 -0
  18. data/lib/universal_id/contrib/signed_global_id/message_pack_type.rb +8 -0
  19. data/lib/universal_id/contrib/signed_global_id.rb +3 -0
  20. data/lib/universal_id/encoder.rb +27 -0
  21. data/lib/universal_id/message_pack_factory.rb +51 -0
  22. data/lib/universal_id/message_pack_types/ruby/composites/open_struct.rb +8 -0
  23. data/lib/universal_id/message_pack_types/ruby/composites/set.rb +9 -0
  24. data/lib/universal_id/message_pack_types/ruby/composites/struct.rb +23 -0
  25. data/lib/universal_id/message_pack_types/ruby/scalars/complex.rb +8 -0
  26. data/lib/universal_id/message_pack_types/ruby/scalars/date.rb +8 -0
  27. data/lib/universal_id/message_pack_types/ruby/scalars/date_time.rb +8 -0
  28. data/lib/universal_id/message_pack_types/ruby/scalars/range.rb +20 -0
  29. data/lib/universal_id/message_pack_types/ruby/scalars/rational.rb +8 -0
  30. data/lib/universal_id/message_pack_types/ruby/scalars/regexp.rb +15 -0
  31. data/lib/universal_id/message_pack_types/uri/uid/type.rb +8 -0
  32. data/lib/universal_id/message_pack_types.rb +24 -0
  33. data/lib/universal_id/prepack_database_options.rb +63 -0
  34. data/lib/universal_id/prepack_options.rb +74 -0
  35. data/lib/universal_id/prepacker.rb +28 -0
  36. data/lib/universal_id/refinements/array_refinement.rb +17 -0
  37. data/lib/universal_id/refinements/hash_refinement.rb +19 -0
  38. data/lib/universal_id/refinements/kernel_refinement.rb +19 -0
  39. data/lib/universal_id/refinements/open_struct_refinement.rb +12 -0
  40. data/lib/universal_id/refinements/set_refinement.rb +12 -0
  41. data/lib/universal_id/refinements.rb +9 -0
  42. data/lib/universal_id/settings.rb +82 -0
  43. data/lib/universal_id/version.rb +1 -1
  44. data/lib/universal_id.rb +25 -10
  45. data/lib/uri/uid.rb +95 -0
  46. metadata +105 -28
  47. data/lib/universal_id/active_model_serializer.rb +0 -53
  48. data/lib/universal_id/config.rb +0 -18
  49. data/lib/universal_id/errors.rb +0 -11
  50. data/lib/universal_id/portable.rb +0 -24
  51. data/lib/universal_id/portable_hash.rb +0 -85
data/Rakefile CHANGED
@@ -6,9 +6,5 @@ require "minitest/test_task"
6
6
  task default: :test
7
7
 
8
8
  Minitest::TestTask.create(:test) do |t|
9
- t.test_globs = if ARGV.size > 1
10
- ARGV[1..]
11
- else
12
- ["test/**/*_test.rb"]
13
- end
9
+ t.test_globs = ENV["GLOBS"] ? ENV["GLOBS"].split(",") : ["test/**/*_test.rb"]
14
10
  end
@@ -0,0 +1,12 @@
1
+ ---
2
+ prepack:
3
+ exclude: []
4
+ include: []
5
+ include_blank: true
6
+
7
+ database:
8
+ include_keys: true
9
+ include_timestamps: true
10
+ include_unsaved_changes: false
11
+ include_descendants: false
12
+ descendant_depth: 0
@@ -0,0 +1,45 @@
1
+ ---
2
+ ##############################################################################################################
3
+ # Prepack options applied before packing with MessagePack
4
+ ##############################################################################################################
5
+ prepack:
6
+ # ..........................................................................................................
7
+ # A list of attributes to exclude (for objects like Hash, OpenStruct, Struct, etc.)
8
+ # Takes prescedence over the`include` list
9
+ exclude: []
10
+
11
+ # ..........................................................................................................
12
+ # A list of attributes to include (for objects like Hash, OpenStruct, Struct, etc.)
13
+ include: []
14
+
15
+ # ..........................................................................................................
16
+ # Whether or not to omit blank values when packing (nil, {}, [], "", etc.)
17
+ include_blank: true
18
+
19
+ # ==========================================================================================================
20
+ # Database records
21
+ database:
22
+ # ......................................................................................................
23
+ # Whether or not to include primary/foreign keys
24
+ # Setting this to `false` can be used to make a copy of an existing record
25
+ include_keys: true
26
+
27
+ # ......................................................................................................
28
+ # Whether or not to include date/time timestamps (created_at, updated_at, etc.)
29
+ # Setting this to `false` can be used to make a copy of an existing record
30
+ include_timestamps: true
31
+
32
+ # ......................................................................................................
33
+ # Whether or not to include unsaved changes
34
+ # Assign to `true` when packing new records
35
+ include_unsaved_changes: false
36
+
37
+ # ......................................................................................................
38
+ # Whether or not to include loaded in-memory descendants (i.e. child associations)
39
+ include_descendants: false
40
+
41
+ # ......................................................................................................
42
+ # The max depth (number) of loaded in-memory descendants to include when `include_descendants == true`
43
+ # For example, a value of (2) would include the following:
44
+ # Parent > Child > Grandchild
45
+ descendant_depth: 0
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_packer"
4
+ require_relative "base_unpacker"
5
+
6
+ UniversalID::MessagePackFactory.register(
7
+ type: ActiveRecord::Base,
8
+ recreate_pool: false,
9
+ packer: ->(obj, packer) { UniversalID::Contrib::ActiveRecordBasePacker.new(obj).pack_with packer },
10
+ unpacker: ->(unpacker) { UniversalID::Contrib::ActiveRecordBaseUnpacker.unpack_with unpacker }
11
+ )
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UniversalID::Contrib::ActiveRecordBasePacker
4
+ using UniversalID::Refinements::HashRefinement
5
+
6
+ # TODO: implement support for has_one
7
+ # ActiveRecord::Reflection::HasOneReflection
8
+ #
9
+ # TODO: implement support for has_and_belongs_to_many
10
+ # ActiveRecord::Reflection::HasAndBelongsToManyReflection
11
+ #
12
+ HAS_MANY_ASSOCIATIONS = [
13
+ ActiveRecord::Reflection::HasManyReflection
14
+ ]
15
+
16
+ DESCENDANTS_KEY = "uid:descendants"
17
+
18
+ attr_reader :record
19
+
20
+ def initialize(record)
21
+ @record = record
22
+ end
23
+
24
+ def pack_with(packer)
25
+ packer.write record.class.name
26
+ packer.write packable_attributes
27
+ end
28
+
29
+ def prepack_options
30
+ options = record.instance_variable_get(:@_uid_prepack_options)
31
+ options = UniversalID::PrepackOptions.new unless options.is_a?(UniversalID::PrepackOptions)
32
+ options
33
+ end
34
+
35
+ def prepack_database_options
36
+ prepack_options.database_options
37
+ end
38
+
39
+ private
40
+
41
+ def packable_attributes
42
+ return record.attributes.slice(record.class.primary_key) if id_only?(prepack_database_options)
43
+
44
+ hash = record.attributes
45
+ reject_keys! hash if prepack_database_options.exclude_keys?
46
+ reject_timestamps! hash if prepack_database_options.exclude_timestamps?
47
+ reject_unsaved_changes! hash if prepack_database_options.exclude_unsaved_changes?
48
+ add_descendants! hash if include_descendants?
49
+
50
+ hash.prepack prepack_options
51
+ end
52
+
53
+ # helpers ..................................................................................................
54
+ def id_only?(prepack_database_options)
55
+ return false if record.new_record?
56
+ return false if prepack_database_options.include_descendants?
57
+ prepack_database_options.exclude_unsaved_changes?
58
+ end
59
+
60
+ def include_descendants?
61
+ return false unless prepack_database_options.include_descendants?
62
+
63
+ max_depth = prepack_database_options.descendant_depth.to_i
64
+ record_depth = record.instance_variable_get(:@_uid_depth).to_i
65
+ record_depth < max_depth
66
+ end
67
+
68
+ # attribute mutators .......................................................................................
69
+
70
+ def reject_keys!(hash)
71
+ hash.delete record.class.primary_key
72
+ foreign_key_column_names.each { |key| hash.delete key }
73
+ end
74
+
75
+ def reject_timestamps!(hash)
76
+ timestamp_column_names.each { |key| hash.delete key }
77
+ end
78
+
79
+ def reject_unsaved_changes!(hash)
80
+ record.changes_to_save.each do |key, (original_value, _)|
81
+ hash[key] = original_value
82
+ end
83
+ end
84
+
85
+ def add_descendants!(hash)
86
+ hash[DESCENDANTS_KEY] ||= {}
87
+
88
+ loaded_has_many_relations_by_name.each do |name, relation|
89
+ descendants = relation.map do |descendant|
90
+ descendant.instance_variable_set(:@_uid_depth, prepack_database_options.current_depth + 1)
91
+ UniversalID::Encoder.encode descendant, prepack_options
92
+ ensure
93
+ prepack_database_options.decrement_current_depth!
94
+ descendant.remove_instance_variable :@_uid_depth
95
+ end
96
+ hash[DESCENDANTS_KEY][name] = descendants
97
+ end
98
+
99
+ prepack_database_options.increment_current_depth!
100
+ end
101
+
102
+ # active record helpers ....................................................................................
103
+
104
+ def timestamp_column_names
105
+ record.class.all_timestamp_attributes_in_model
106
+ end
107
+
108
+ def foreign_key_column_names
109
+ record.class.reflections
110
+ .each_with_object([]) do |(name, reflection), memo|
111
+ memo << reflection.foreign_key if reflection.macro == :belongs_to
112
+ end
113
+ end
114
+
115
+ def associations
116
+ record.class.reflect_on_all_associations
117
+ end
118
+
119
+ def has_many_associations
120
+ associations.select { |a| HAS_MANY_ASSOCIATIONS.include? a.class }
121
+ end
122
+
123
+ def loaded_has_many_relations_by_name
124
+ has_many_associations.each_with_object({}) do |association, memo|
125
+ relation = record.public_send(association.name)
126
+ next unless relation.loaded?
127
+ memo[association.name] = relation
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UniversalID::Contrib::ActiveRecordBaseUnpacker
4
+ using UniversalID::Refinements::KernelRefinement
5
+
6
+ class << self
7
+ def unpack_with(unpacker)
8
+ class_name = unpacker.read
9
+ attributes = unpacker.read || {}
10
+ create_instance class_name, attributes
11
+ end
12
+
13
+ private
14
+
15
+ def create_instance(class_name, attributes)
16
+ klass = const_find(class_name)
17
+ return nil unless klass
18
+
19
+ record = if attributes[klass.primary_key]
20
+ klass.find_by(klass.primary_key => attributes[klass.primary_key])
21
+ else
22
+ klass.new
23
+ end
24
+
25
+ assign_attributes record, attributes
26
+ assign_descendants record, attributes
27
+
28
+ record
29
+ end
30
+
31
+ def assign_attributes(record, attributes)
32
+ attributes.each do |key, value|
33
+ record.public_send "#{key}=", value if record.respond_to? "#{key}="
34
+ end
35
+ end
36
+
37
+ def assign_descendants(record, attributes)
38
+ descendants = attributes[UniversalID::Contrib::ActiveRecordBasePacker::DESCENDANTS_KEY] || {}
39
+ descendants.each do |name, list|
40
+ next unless record.respond_to?(name) && record.respond_to?("#{name}=")
41
+
42
+ models = list.map { |encoded| UniversalID::Encoder.decode encoded }
43
+ models.compact!
44
+ next unless models.any?
45
+
46
+ # NOTE: ActiveRecord is smart enough to not re-create or re-add
47
+ # existing records for has_many associations
48
+ record.public_send "#{name}=", models
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: Revisit ActiveRecord::Relation serialization strategy,
4
+ # and attempt to optimize without falling back to Marshal.dump/load
5
+ UniversalID::MessagePackFactory.register(
6
+ type: ActiveRecord::Relation,
7
+ recreate_pool: false,
8
+ packer: ->(obj, packer) do
9
+ # NOTE: packing a relation will reset the loaded state and internal cache of the relation
10
+ # this ensures minimal payload size
11
+ obj = obj.dup # clear internal cached state (loaded results, etc.)
12
+ obj.reset # dup should clear any internal caching, but we call reset just in case
13
+ packer.write Marshal.dump(obj)
14
+ end,
15
+ unpacker: ->(unpacker) { Marshal.load unpacker.read }
16
+ )
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_record/base_message_pack_type"
4
+ require_relative "active_record/relation_message_pack_type"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
4
+ type: ActiveSupport::TimeWithZone,
5
+ packer: ->(obj, packer) do
6
+ packer.write obj.iso8601(9)
7
+ packer.write obj.zone
8
+ end,
9
+ unpacker: ->(unpacker) do
10
+ time = Time.parse(unpacker.read)
11
+ zone = unpacker.read
12
+ ActiveSupport::TimeWithZone.new time, ActiveSupport::TimeZone[zone]
13
+ end
14
+ )
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_support/time_with_zone_message_pack_type"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UniversalID::Contrib::GlobalIDModel
4
+ include GlobalID::Identification
5
+
6
+ def self.find(value)
7
+ new value
8
+ end
9
+
10
+ attr_reader :id, :uid
11
+
12
+ def initialize(universal_id)
13
+ @uid = case universal_id
14
+ when URI::UID then universal_id
15
+ when String
16
+ case universal_id
17
+ when /\A#{URI::UID::SCHEME}/o then URI::UID.parse(universal_id)
18
+ else URI::UID.parse(URI::UID.build_string(universal_id))
19
+ end
20
+ end
21
+
22
+ @id = @uid&.payload
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Contrib::GlobalIDUIDExtension
4
+ extend Forwardable
5
+
6
+ def self.included(mixer)
7
+ mixer.extend ClassMethods
8
+ end
9
+
10
+ # Adds all GlobalID::Identification methods
11
+ def_delegators(:to_global_id_model, *GlobalID::Identification.instance_methods(false))
12
+
13
+ # Returns a UniversalID::Contrib::GlobalIDModel instance
14
+ # which implements the GlobalID::Identification interface/protocol
15
+ def to_global_id_model
16
+ UniversalID::Contrib::GlobalIDModel.new self
17
+ end
18
+
19
+ module ClassMethods
20
+ def from_global_id_record(gid_record)
21
+ gid_record&.find&.uid
22
+ end
23
+
24
+ def from_global_id(gid, options = {})
25
+ from_global_id_record GlobalID.parse(gid, options)
26
+ end
27
+
28
+ alias_method :from_gid, :from_global_id
29
+
30
+ def from_signed_global_id(sgid, options = {})
31
+ from_global_id_record SignedGlobalID.parse(sgid, options)
32
+ end
33
+
34
+ alias_method :from_sgid, :from_signed_global_id
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Contrib::GlobalID; end
4
+
5
+ require_relative "global_id_model"
6
+ require_relative "global_id_uid_extension"
7
+
8
+ URI::UID.include UniversalID::Contrib::GlobalIDUIDExtension
9
+
10
+ UniversalID::MessagePackFactory.register(
11
+ type: GlobalID,
12
+ recreate_pool: false,
13
+ packer: ->(obj, packer) { packer.write obj.to_param },
14
+ unpacker: ->(unpacker) { GlobalID.parse unpacker.read }
15
+ )
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "global_id/message_pack_type"
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "active_record" if defined? ActiveRecord
4
+ require_relative "active_support" if defined? ActiveSupport
5
+ require_relative "global_id" if defined? GlobalID
6
+ require_relative "signed_global_id" if defined? SignedGlobalID
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
4
+ type: SignedGlobalID,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.to_param },
7
+ unpacker: ->(unpacker) { SignedGlobalID.parse unpacker.read }
8
+ )
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "signed_global_id/message_pack_type"
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "brotli"
5
+
6
+ # This module provides the ability to encode and decode objects into a compressed, URL-safe string
7
+ module UniversalID::Encoder
8
+ class << self
9
+ def encode(object, options = {})
10
+ object = UniversalID::Prepacker.prepack(object, options)
11
+
12
+ # This is basically the same call as UniversalID::MessagePackFactory.pack(object),
13
+ # but it uses a pool of pre-initialized packers/unpackers instead of creating a new one each time
14
+ packed = UniversalID::MessagePackFactory.msgpack_pool.dump(object)
15
+ deflated = Brotli.deflate(packed)
16
+ Base64.urlsafe_encode64 deflated, padding: false
17
+ end
18
+
19
+ def decode(string)
20
+ decoded = Base64.urlsafe_decode64(string)
21
+ inflated = Brotli.inflate(decoded)
22
+ # This is basically the same call as UniversalID::MessagePackFactory.unpack(object),
23
+ # but it uses a pool of pre-initialized packers/unpackers instead of creating a new one each time
24
+ UniversalID::MessagePackFactory.msgpack_pool.load inflated
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+ require "msgpack"
5
+
6
+ UniversalID::MessagePackFactory = MessagePack::Factory.new.tap do |factory|
7
+ class << factory
8
+ attr_reader :msgpack_pool
9
+
10
+ def create_msgpack_pool
11
+ @msgpack_pool = UniversalID::MessagePackFactory.pool([Etc.nprocessors.to_i, 1].max)
12
+ end
13
+
14
+ def register_scalar(type:, recreate_pool: true, **options)
15
+ register id: next_type_id(order: :asc), type: type, **options
16
+ end
17
+
18
+ def register(type:, id: nil, recreate_pool: true, **options)
19
+ options[:recursive] = true unless options.key?(:recursive)
20
+ register_type(id || next_type_id(order: :desc), type, options)
21
+ create_msgpack_pool if recreate_pool
22
+ end
23
+
24
+ def next_type_id(order:)
25
+ range = 0..127
26
+
27
+ case order
28
+ when :asc
29
+ id = range.first
30
+ id += 1 while type_registered?(id)
31
+ when :desc
32
+ id = range.last
33
+ id -= 1 while type_registered?(id)
34
+ end
35
+
36
+ id = nil unless range.cover?(id)
37
+ id
38
+ end
39
+ end
40
+ end
41
+
42
+ # Register MessagePack built-in types
43
+ UniversalID::MessagePackFactory.register_type MessagePack::Timestamp::TYPE, ::Time,
44
+ packer: MessagePack::Time::Packer,
45
+ unpacker: MessagePack::Time::Unpacker
46
+
47
+ # Register MessagePack built-in extensions
48
+ UniversalID::MessagePackFactory.register_type 0x00, ::Symbol
49
+
50
+ # Register UniversalID types/extensions
51
+ require_relative "message_pack_types"
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
4
+ type: OpenStruct,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.to_h },
7
+ unpacker: ->(unpacker) { OpenStruct.new unpacker.read }
8
+ )
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
4
+ type: Set,
5
+ recusive: true,
6
+ recreate_pool: false,
7
+ packer: ->(obj, packer) { packer.write obj.to_a },
8
+ unpacker: ->(unpacker) { Set.new unpacker.read }
9
+ )
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ using UniversalID::Refinements::KernelRefinement
4
+
5
+ UniversalID::MessagePackFactory.register(
6
+ type: Struct,
7
+ recreate_pool: false,
8
+ packer: ->(obj, packer) do
9
+ packer.write obj.class.name
10
+ packer.write obj.to_h
11
+ end,
12
+
13
+ unpacker: ->(unpacker) do
14
+ class_name = unpacker.read
15
+ hash = unpacker.read
16
+ klass = const_find(class_name)
17
+
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)
22
+ end
23
+ )
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register_scalar(
4
+ type: Complex,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.to_s },
7
+ unpacker: ->(unpacker) { Kernel.Complex unpacker.read }
8
+ )
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register_scalar(
4
+ type: Date,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.iso8601 },
7
+ unpacker: ->(unpacker) { Date.parse unpacker.read }
8
+ )
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register_scalar(
4
+ type: DateTime,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.iso8601(9) },
7
+ unpacker: ->(unpacker) { DateTime.parse unpacker.read }
8
+ )
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register_scalar(
4
+ type: Range,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) do
7
+ packer.write obj.first
8
+ packer.write obj.to_s.scan(/\.{2,3}/).first
9
+ packer.write obj.last
10
+ end,
11
+ unpacker: ->(unpacker) do
12
+ first = unpacker.read
13
+ operator = unpacker.read
14
+ last = unpacker.read
15
+ case operator
16
+ when ".." then first..last
17
+ when "..." then first...last
18
+ end
19
+ end
20
+ )
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register_scalar(
4
+ type: Rational,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.to_s },
7
+ unpacker: ->(unpacker) { Kernel.Rational unpacker.read }
8
+ )
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register_scalar(
4
+ type: Regexp,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) do
7
+ packer.write obj.source
8
+ packer.write obj.options
9
+ end,
10
+ unpacker: ->(unpacker) do
11
+ source = unpacker.read
12
+ options = unpacker.read
13
+ Regexp.new source, options
14
+ end
15
+ )
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
4
+ type: URI::UID,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.to_s },
7
+ unpacker: ->(unpacker) { URI::UID.parse unpacker.read }
8
+ )
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: MessagePack scans registered type in linear order and first match wins
4
+
5
+ # scalars
6
+ require_relative "message_pack_types/ruby/scalars/complex"
7
+ require_relative "message_pack_types/ruby/scalars/rational"
8
+ require_relative "message_pack_types/ruby/scalars/date_time"
9
+ require_relative "message_pack_types/ruby/scalars/date"
10
+ require_relative "message_pack_types/ruby/scalars/range"
11
+ require_relative "message_pack_types/ruby/scalars/regexp"
12
+
13
+ # composites
14
+ require_relative "message_pack_types/ruby/composites/open_struct"
15
+ require_relative "message_pack_types/ruby/composites/struct"
16
+ require_relative "message_pack_types/ruby/composites/set"
17
+
18
+ # uid
19
+ require_relative "message_pack_types/uri/uid/type"
20
+
21
+ # contribs
22
+ require_relative "contrib/rails" if defined? Rails
23
+
24
+ UniversalID::MessagePackFactory.create_msgpack_pool