universalid 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +929 -188
  3. data/Rakefile +1 -5
  4. data/config/default.yml +12 -0
  5. data/config/example.yml +45 -0
  6. data/contrib/active_record/base_message_pack_type.rb +11 -0
  7. data/contrib/active_record/base_packer.rb +130 -0
  8. data/contrib/active_record/base_unpacker.rb +52 -0
  9. data/contrib/active_record/relation_message_pack_type.rb +16 -0
  10. data/contrib/active_record.rb +8 -0
  11. data/contrib/active_support/time_with_zone_message_pack_type.rb +14 -0
  12. data/contrib/active_support.rb +7 -0
  13. data/contrib/global_id/global_id_model.rb +24 -0
  14. data/contrib/global_id/global_id_uid_extension.rb +36 -0
  15. data/contrib/global_id/message_pack_type.rb +15 -0
  16. data/contrib/global_id.rb +7 -0
  17. data/contrib/signed_global_id/message_pack_type.rb +8 -0
  18. data/contrib/signed_global_id.rb +7 -0
  19. data/contrib/tags +75 -0
  20. data/lib/universal_id/contrib.rb +14 -0
  21. data/lib/universal_id/encoder.rb +27 -0
  22. data/lib/universal_id/message_pack_factory.rb +37 -0
  23. data/lib/universal_id/message_pack_types/ruby/composites/open_struct.rb +8 -0
  24. data/lib/universal_id/message_pack_types/ruby/composites/set.rb +9 -0
  25. data/lib/universal_id/message_pack_types/ruby/composites/struct.rb +23 -0
  26. data/lib/universal_id/message_pack_types/ruby/scalars/complex.rb +8 -0
  27. data/lib/universal_id/message_pack_types/ruby/scalars/date.rb +8 -0
  28. data/lib/universal_id/message_pack_types/ruby/scalars/date_time.rb +8 -0
  29. data/lib/universal_id/message_pack_types/ruby/scalars/range.rb +20 -0
  30. data/lib/universal_id/message_pack_types/ruby/scalars/rational.rb +8 -0
  31. data/lib/universal_id/message_pack_types/ruby/scalars/regexp.rb +15 -0
  32. data/lib/universal_id/message_pack_types/uri/uid/type.rb +8 -0
  33. data/lib/universal_id/message_pack_types.rb +26 -0
  34. data/lib/universal_id/prepack_database_options.rb +63 -0
  35. data/lib/universal_id/prepack_options.rb +74 -0
  36. data/lib/universal_id/prepacker.rb +28 -0
  37. data/lib/universal_id/refinements/array_refinement.rb +17 -0
  38. data/lib/universal_id/refinements/hash_refinement.rb +19 -0
  39. data/lib/universal_id/refinements/kernel_refinement.rb +19 -0
  40. data/lib/universal_id/refinements/open_struct_refinement.rb +12 -0
  41. data/lib/universal_id/refinements/set_refinement.rb +12 -0
  42. data/lib/universal_id/refinements.rb +9 -0
  43. data/lib/universal_id/settings.rb +82 -0
  44. data/lib/universal_id/version.rb +1 -1
  45. data/lib/universal_id.rb +23 -10
  46. data/lib/uri/uid.rb +95 -0
  47. metadata +106 -28
  48. data/lib/universal_id/active_model_serializer.rb +0 -53
  49. data/lib/universal_id/config.rb +0 -18
  50. data/lib/universal_id/errors.rb +0 -11
  51. data/lib/universal_id/portable.rb +0 -24
  52. 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && defined?(ActiveRecord::Relation)
4
+
5
+ require_relative "active_record/base_message_pack_type"
6
+ require_relative "active_record/relation_message_pack_type"
7
+
8
+ end
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(ActiveSupport) && defined?(ActiveSupport::TimeWithZone)
4
+
5
+ require_relative "active_support/time_with_zone_message_pack_type"
6
+
7
+ end
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(GlobalID) && defined?(GlobalID::Identification)
4
+
5
+ require_relative "global_id/message_pack_type"
6
+
7
+ end
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined? SignedGlobalID
4
+
5
+ require_relative "signed_global_id/message_pack_type"
6
+
7
+ end
data/contrib/tags ADDED
@@ -0,0 +1,75 @@
1
+ !_TAG_EXTRA_DESCRIPTION anonymous /Include tags for non-named objects like lambda/
2
+ !_TAG_EXTRA_DESCRIPTION fileScope /Include tags of file scope/
3
+ !_TAG_EXTRA_DESCRIPTION pseudo /Include pseudo tags/
4
+ !_TAG_EXTRA_DESCRIPTION subparser /Include tags generated by subparsers/
5
+ !_TAG_FIELD_DESCRIPTION epoch /the last modified time of the input file (only for F\/file kind tag)/
6
+ !_TAG_FIELD_DESCRIPTION file /File-restricted scoping/
7
+ !_TAG_FIELD_DESCRIPTION input /input file/
8
+ !_TAG_FIELD_DESCRIPTION name /tag name/
9
+ !_TAG_FIELD_DESCRIPTION pattern /pattern/
10
+ !_TAG_FIELD_DESCRIPTION typeref /Type and name of a variable or typedef/
11
+ !_TAG_FIELD_DESCRIPTION!Ruby mixin /how the class or module is mixed in (mixin:HOW:MODULE)/
12
+ !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
13
+ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
14
+ !_TAG_KIND_DESCRIPTION!Ruby A,accessor /accessors/
15
+ !_TAG_KIND_DESCRIPTION!Ruby C,constant /constants/
16
+ !_TAG_KIND_DESCRIPTION!Ruby L,library /libraries/
17
+ !_TAG_KIND_DESCRIPTION!Ruby S,singletonMethod /singleton methods/
18
+ !_TAG_KIND_DESCRIPTION!Ruby a,alias /aliases/
19
+ !_TAG_KIND_DESCRIPTION!Ruby c,class /classes/
20
+ !_TAG_KIND_DESCRIPTION!Ruby f,method /methods/
21
+ !_TAG_KIND_DESCRIPTION!Ruby m,module /modules/
22
+ !_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/
23
+ !_TAG_OUTPUT_FILESEP slash /slash or backslash/
24
+ !_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
25
+ !_TAG_OUTPUT_VERSION 0.0 /current.age/
26
+ !_TAG_PARSER_VERSION!Ruby 0.0 /current.age/
27
+ !_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/
28
+ !_TAG_PROC_CWD /Users/nathan/work/hopsoft/universalid/contrib/ //
29
+ !_TAG_PROGRAM_AUTHOR Universal Ctags Team //
30
+ !_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
31
+ !_TAG_PROGRAM_URL https://ctags.io/ /official site/
32
+ !_TAG_PROGRAM_VERSION 6.0.0 //
33
+ !_TAG_ROLE_DESCRIPTION!Ruby!library loaded /loaded by "load" method/
34
+ !_TAG_ROLE_DESCRIPTION!Ruby!library required /loaded by "require" method/
35
+ !_TAG_ROLE_DESCRIPTION!Ruby!library requiredRel /loaded by "require_relative" method/
36
+ ActiveRecordPacker active_record/active_record_packer.rb /^class UniversalID::Contrib::ActiveRecordPacker$/;" c module:UniversalID.Contrib
37
+ ActiveRecordUnpacker active_record/active_record_unpacker.rb /^class UniversalID::Contrib::ActiveRecordUnpacker$/;" c module:UniversalID.Contrib
38
+ ClassMethods global_id/global_id_uid_extension.rb /^ module ClassMethods$/;" m module:GlobalIDUIDExtension
39
+ DESCENDANTS_KEY active_record/active_record_packer.rb /^ DESCENDANTS_KEY = "uid:descendants"$/;" C class:ActiveRecordPacker
40
+ GlobalID global_id/message_pack_type.rb /^module UniversalID::Contrib::GlobalID; end$/;" m module:UniversalID.Contrib
41
+ GlobalIDModel global_id/global_id_model.rb /^class UniversalID::Contrib::GlobalIDModel$/;" c module:UniversalID.Contrib mixin:include:GlobalID.Identification
42
+ GlobalIDUIDExtension global_id/global_id_uid_extension.rb /^module UniversalID::Contrib::GlobalIDUIDExtension$/;" m module:UniversalID.Contrib mixin:extend:Forwardable
43
+ HAS_MANY_ASSOCIATIONS active_record/active_record_packer.rb /^ HAS_MANY_ASSOCIATIONS = [$/;" C class:ActiveRecordPacker
44
+ add_descendants! active_record/active_record_packer.rb /^ def add_descendants!(hash)$/;" f class:ActiveRecordPacker
45
+ assign_attributes active_record/active_record_unpacker.rb /^ def assign_attributes(record, attributes)$/;" S class:ActiveRecordUnpacker
46
+ assign_descendants active_record/active_record_unpacker.rb /^ def assign_descendants(record, attributes)$/;" S class:ActiveRecordUnpacker
47
+ associations active_record/active_record_packer.rb /^ def associations$/;" f class:ActiveRecordPacker
48
+ create_instance active_record/active_record_unpacker.rb /^ def create_instance(class_name, attributes)$/;" S class:ActiveRecordUnpacker
49
+ find global_id/global_id_model.rb /^ def self.find(value)$/;" S class:GlobalIDModel
50
+ foreign_key_column_names active_record/active_record_packer.rb /^ def foreign_key_column_names$/;" f class:ActiveRecordPacker
51
+ from_gid global_id/global_id_uid_extension.rb /^ alias_method :from_gid, :from_global_id$/;" a module:GlobalIDUIDExtension.ClassMethods
52
+ from_global_id global_id/global_id_uid_extension.rb /^ def from_global_id(gid, options = {})$/;" f module:GlobalIDUIDExtension.ClassMethods
53
+ from_global_id_record global_id/global_id_uid_extension.rb /^ def from_global_id_record(gid_record)$/;" f module:GlobalIDUIDExtension.ClassMethods
54
+ from_sgid global_id/global_id_uid_extension.rb /^ alias_method :from_sgid, :from_signed_global_id$/;" a module:GlobalIDUIDExtension.ClassMethods
55
+ from_signed_global_id global_id/global_id_uid_extension.rb /^ def from_signed_global_id(sgid, options = {})$/;" f module:GlobalIDUIDExtension.ClassMethods
56
+ has_many_associations active_record/active_record_packer.rb /^ def has_many_associations$/;" f class:ActiveRecordPacker
57
+ id global_id/global_id_model.rb /^ attr_reader :id, :uid$/;" A class:GlobalIDModel
58
+ id_only? active_record/active_record_packer.rb /^ def id_only?(prepack_database_options)$/;" f class:ActiveRecordPacker
59
+ include_descendants? active_record/active_record_packer.rb /^ def include_descendants?$/;" f class:ActiveRecordPacker
60
+ included global_id/global_id_uid_extension.rb /^ def self.included(mixer)$/;" S module:GlobalIDUIDExtension
61
+ initialize active_record/active_record_packer.rb /^ def initialize(record)$/;" f class:ActiveRecordPacker
62
+ initialize global_id/global_id_model.rb /^ def initialize(universal_id)$/;" f class:GlobalIDModel
63
+ loaded_has_many_relations_by_name active_record/active_record_packer.rb /^ def loaded_has_many_relations_by_name$/;" f class:ActiveRecordPacker
64
+ pack_with active_record/active_record_packer.rb /^ def pack_with(packer)$/;" f class:ActiveRecordPacker
65
+ packable_attributes active_record/active_record_packer.rb /^ def packable_attributes$/;" f class:ActiveRecordPacker
66
+ prepack_database_options active_record/active_record_packer.rb /^ def prepack_database_options$/;" f class:ActiveRecordPacker
67
+ prepack_options active_record/active_record_packer.rb /^ def prepack_options$/;" f class:ActiveRecordPacker
68
+ record active_record/active_record_packer.rb /^ attr_reader :record$/;" A class:ActiveRecordPacker
69
+ reject_keys! active_record/active_record_packer.rb /^ def reject_keys!(hash)$/;" f class:ActiveRecordPacker
70
+ reject_timestamps! active_record/active_record_packer.rb /^ def reject_timestamps!(hash)$/;" f class:ActiveRecordPacker
71
+ reject_unsaved_changes! active_record/active_record_packer.rb /^ def reject_unsaved_changes!(hash)$/;" f class:ActiveRecordPacker
72
+ timestamp_column_names active_record/active_record_packer.rb /^ def timestamp_column_names$/;" f class:ActiveRecordPacker
73
+ to_global_id_model global_id/global_id_uid_extension.rb /^ def to_global_id_model$/;" f module:GlobalIDUIDExtension
74
+ uid global_id/global_id_model.rb /^ attr_reader :id, :uid$/;" A class:GlobalIDModel
75
+ unpack_with active_record/active_record_unpacker.rb /^ def unpack_with(unpacker)$/;" S class:ActiveRecordUnpacker
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Contrib; end
4
+
5
+ # A list of all MessagePack types in the preferred registration order
6
+ #
7
+ # IMPORTANT: More specific types should be registered before more general types
8
+ # because MessagePack will use the first registered type that matches
9
+ # MessagePack scans registered type in linear order and first match wins
10
+
11
+ require_relative "../../contrib/signed_global_id"
12
+ require_relative "../../contrib/global_id"
13
+ require_relative "../../contrib/active_support"
14
+ require_relative "../../contrib/active_record"
@@ -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,37 @@
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(type:, type_id: nil, recreate_pool: true, **options)
15
+ id = type_id || next_type_id
16
+ options[:recursive] = true unless options.key?(:recursive)
17
+ register_type(id, type, options)
18
+ create_msgpack_pool if recreate_pool
19
+ end
20
+
21
+ def next_type_id
22
+ max_type_id = registered_types.map { |type| type[:type].to_i }.max
23
+ max_type_id.nil? ? 0 : max_type_id + 1
24
+ end
25
+ end
26
+ end
27
+
28
+ # Register MessagePack built-in types
29
+ UniversalID::MessagePackFactory.register_type MessagePack::Timestamp::TYPE, ::Time,
30
+ packer: MessagePack::Time::Packer,
31
+ unpacker: MessagePack::Time::Unpacker
32
+
33
+ # Register MessagePack built-in extensions
34
+ UniversalID::MessagePackFactory.register_type 0x00, ::Symbol
35
+
36
+ # Register UniversalID types/extensions
37
+ 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(
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(
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(
4
+ type: DateTime,
5
+ recreate_pool: false,
6
+ packer: ->(obj, packer) { packer.write obj.iso8601(9) },
7
+ unpacker: ->(unpacker) { DateTime.parse unpacker.read }
8
+ )