universalid 0.0.1 → 0.1.0

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 (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
+ )