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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ UniversalID::MessagePackFactory.register(
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(
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(
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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A list of all MessagePack types in the preferred registration order
4
+ #
5
+ # IMPORTANT: More specific types should be registered before more general types
6
+ # because MessagePack will use the first registered type that matches
7
+ # MessagePack scans registered type in linear order and first match wins
8
+
9
+ require_relative "contrib"
10
+
11
+ paths = %w[
12
+ message_pack_types/uri/uid/type.rb
13
+ message_pack_types/ruby/composites/set.rb
14
+ message_pack_types/ruby/composites/open_struct.rb
15
+ message_pack_types/ruby/composites/struct.rb
16
+ message_pack_types/ruby/scalars/complex.rb
17
+ message_pack_types/ruby/scalars/rational.rb
18
+ message_pack_types/ruby/scalars/date_time.rb
19
+ message_pack_types/ruby/scalars/date.rb
20
+ message_pack_types/ruby/scalars/range.rb
21
+ message_pack_types/ruby/scalars/regexp.rb
22
+ ]
23
+
24
+ paths.each { |path| require_relative path }
25
+
26
+ UniversalID::MessagePackFactory.create_msgpack_pool
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UniversalID::PrepackDatabaseOptions
4
+ def initialize(settings)
5
+ @settings = settings
6
+ end
7
+
8
+ def to_h
9
+ @settings.to_h
10
+ end
11
+
12
+ def include_keys?
13
+ !!@settings.include_keys
14
+ end
15
+
16
+ def exclude_keys?
17
+ !include_keys?
18
+ end
19
+
20
+ def include_timestamps?
21
+ !!@settings.include_timestamps
22
+ end
23
+
24
+ def exclude_timestamps?
25
+ !include_timestamps?
26
+ end
27
+
28
+ def include_unsaved_changes?
29
+ !!@settings.include_unsaved_changes
30
+ end
31
+
32
+ def exclude_unsaved_changes?
33
+ !include_unsaved_changes?
34
+ end
35
+
36
+ def descendant_depth
37
+ @settings.descendant_depth ||= 0
38
+ end
39
+
40
+ attr_writer :current_depth
41
+
42
+ def current_depth
43
+ @settings.current_depth ||= 0
44
+ end
45
+
46
+ def increment_current_depth!
47
+ @settings.current_depth ||= 0
48
+ @settings.current_depth = @settings.current_depth += 1
49
+ end
50
+
51
+ def decrement_current_depth!
52
+ @settings.current_depth ||= 0
53
+ @settings.current_depth = @settings.current_depth -= 1
54
+ end
55
+
56
+ def include_descendants?
57
+ !!@settings.include_descendants
58
+ end
59
+
60
+ def exclude_descentants?
61
+ !include_descendants?
62
+ end
63
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "prepack_database_options"
4
+
5
+ class UniversalID::PrepackOptions
6
+ attr_reader :excludes, :includes, :database_options
7
+
8
+ def initialize(options = {})
9
+ options ||= {}
10
+ @settings = UniversalID::Settings.build(**options).prepack
11
+ @database_options = UniversalID::PrepackDatabaseOptions.new(@settings.database)
12
+ @references = Set.new
13
+ @excludes ||= @settings.exclude.to_h { |key| [key.to_s, true] }
14
+ @includes ||= @settings.include.to_h { |key| [key.to_s, true] }
15
+ end
16
+
17
+ def to_h
18
+ @settings.to_h
19
+ end
20
+
21
+ def prevent_self_reference!(object)
22
+ raise UniversalID::Prepacker::CircularReferenceError if @references.include?(object.object_id)
23
+ @references << object.object_id
24
+ end
25
+
26
+ def include_blank?
27
+ !!@settings.include_blank
28
+ end
29
+
30
+ def exclude_blank?
31
+ !include_blank?
32
+ end
33
+
34
+ def keep_key?(key)
35
+ return false if excludes[key.to_s]
36
+ includes.none? || includes[key.to_s]
37
+ end
38
+
39
+ def reject_key?(key)
40
+ excludes[key.to_s]
41
+ end
42
+
43
+ def keep_value?(value)
44
+ include_blank? || present?(value)
45
+ end
46
+
47
+ def reject_value?(value)
48
+ !keep_value?(value)
49
+ end
50
+
51
+ def keep_keypair?(key, value)
52
+ keep_key?(key) && keep_value?(value)
53
+ end
54
+
55
+ def reject_keypair?(key, value)
56
+ reject_key?(key) || reject_value?(value)
57
+ end
58
+
59
+ def blank?(value)
60
+ return true if value.nil?
61
+ return false if !!value == value # booleans
62
+
63
+ result = false
64
+ result ||= value.empty? if value.respond_to?(:empty?)
65
+ result ||= value.blank? if value.respond_to?(:blank?)
66
+ result ||= value.strip.empty? if value.is_a?(String)
67
+ result ||= value.compact.empty? if value.is_a?(Array) || value.is_a?(Hash)
68
+ result
69
+ end
70
+
71
+ def present?(value)
72
+ !blank?(value)
73
+ end
74
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "refinements"
4
+
5
+ class UniversalID::Prepacker
6
+ using UniversalID::Refinements::KernelRefinement
7
+ using UniversalID::Refinements::ArrayRefinement
8
+ using UniversalID::Refinements::HashRefinement
9
+ using UniversalID::Refinements::SetRefinement
10
+ using UniversalID::Refinements::OpenStructRefinement
11
+
12
+ class CircularReferenceError < StandardError
13
+ def initialize(message = "Prepacking not supported on self referencing objects!")
14
+ super
15
+ end
16
+ end
17
+
18
+ class << self
19
+ def prepack(object, options = {})
20
+ options = UniversalID::PrepackOptions.new(options) unless options.is_a?(UniversalID::PrepackOptions)
21
+
22
+ return object.prepack(options) if object.respond_to?(:prepack)
23
+
24
+ object.instance_variable_set(:@_uid_prepack_options, options) unless object.frozen?
25
+ object
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Refinements::ArrayRefinement
4
+ refine Array do
5
+ def prepack(options)
6
+ options.prevent_self_reference! self
7
+
8
+ copy = each_with_object([]) do |val, memo|
9
+ val = UniversalID::Prepacker.prepack(val, options)
10
+ memo << val if options.keep_value?(val)
11
+ end
12
+
13
+ copy.compact! if options.exclude_blank?
14
+ copy
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Refinements::HashRefinement
4
+ refine Hash do
5
+ using UniversalID::Refinements::ArrayRefinement
6
+
7
+ def prepack(options)
8
+ options.prevent_self_reference! self
9
+
10
+ copy = each_with_object({}) do |(key, val), memo|
11
+ next unless options.keep_keypair?(key, val)
12
+ memo[key] = UniversalID::Prepacker.prepack(val, options)
13
+ end
14
+
15
+ copy.compact! if options.exclude_blank?
16
+ copy
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Refinements::KernelRefinement
4
+ refine Kernel do
5
+ # Finds a constant by name, starting at the root namespace (i.e. ::Object)
6
+ def const_find(name)
7
+ return nil unless name.is_a?(String)
8
+ names = name.split("::")
9
+ constant = Object
10
+
11
+ while names.any?
12
+ value = names.shift
13
+ constant = constant.const_get(value) if constant.const_defined?(value)
14
+ end
15
+
16
+ (constant.name == name) ? constant : nil
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Refinements::OpenStructRefinement
4
+ refine OpenStruct do
5
+ using UniversalID::Refinements::HashRefinement
6
+
7
+ def prepack(options)
8
+ options.prevent_self_reference! self
9
+ OpenStruct.new to_h.prepack(options)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Refinements::SetRefinement
4
+ refine Set do
5
+ using UniversalID::Refinements::ArrayRefinement
6
+
7
+ def prepack(options)
8
+ options.prevent_self_reference! self
9
+ Set.new to_a.prepack(options)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module UniversalID::Refinements; end
4
+
5
+ require_relative "refinements/kernel_refinement"
6
+ require_relative "refinements/array_refinement"
7
+ require_relative "refinements/hash_refinement"
8
+ require_relative "refinements/set_refinement"
9
+ require_relative "refinements/open_struct_refinement"
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+ require "singleton"
5
+ require "config"
6
+
7
+ class UniversalID::Settings
8
+ include MonitorMixin
9
+ include Singleton
10
+
11
+ DEFAULT_FILE_PATH = File.expand_path("../../config/default.yml", __dir__)
12
+
13
+ class << self
14
+ def build(**options)
15
+ instance.default_copy.tap do |settings|
16
+ options.each { |key, val| assign key, val, to: settings }
17
+ end
18
+ end
19
+
20
+ def register(...)
21
+ instance.register(...)
22
+ end
23
+
24
+ def [](key)
25
+ instance[key]
26
+ end
27
+
28
+ private
29
+
30
+ def assign(key, value, to:)
31
+ return if value.nil?
32
+
33
+ case {key.to_sym => value}
34
+ in prepack: prepack then prepack.each { |k, v| assign k, v, to: to }
35
+ in exclude: exclude then to.prepack.exclude = exclude
36
+ in include: inc then to.prepack.include = inc
37
+ in include_blank: include_blank then to.prepack.include_blank = !!include_blank
38
+ in database: database then database.each { |k, v| assign k, v, to: to }
39
+ in include_keys: include_keys then to.prepack.database.include_keys = !!include_keys
40
+ in include_timestamps: include_timestamps then to.prepack.database.include_timestamps = !!include_timestamps
41
+ in include_unsaved_changes: include_unsaved_changes then to.prepack.database.include_unsaved_changes = !!include_unsaved_changes
42
+ in include_descendants: include_descendants then to.prepack.database.include_descendants = !!include_descendants
43
+ in descendant_depth: descendant_depth then to.prepack.database.descendant_depth = descendant_depth
44
+ else # ignore key
45
+ end
46
+ end
47
+ end
48
+
49
+ def register(key, options = {})
50
+ key = key.to_s.strip.downcase.to_sym
51
+ synchronize do
52
+ raise ArgumentError, "Already registered! key: #{key}" if registry.key? key
53
+ config = case options
54
+ when String then Config.load_files(options)
55
+ when Hash then Config::Options.new(options)
56
+ when Config::Options then options
57
+ else raise ArgumentError, "Invalid options! Must be a String, Hash, or Config::Options."
58
+ end
59
+
60
+ config = self.class.build(**config) unless key == :default
61
+ registry[key] = config
62
+ self.class.define_method(key) { config }
63
+ self.class.define_method("#{key}_copy") { Marshal.load Marshal.dump(config) }
64
+ self.class.define_singleton_method(key) { instance.public_send key }
65
+ [key, config]
66
+ end
67
+ end
68
+
69
+ def [](key)
70
+ registry[key.to_sym]
71
+ end
72
+
73
+ private
74
+
75
+ attr_reader :registry
76
+
77
+ def initialize
78
+ super
79
+ @registry = {}
80
+ register :default, DEFAULT_FILE_PATH
81
+ end
82
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UniversalID
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/universal_id.rb CHANGED
@@ -1,13 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
4
- require "zlib"
5
- require "globalid"
6
- require "active_model"
7
- require "active_support/all"
3
+ require "date"
4
+ require "forwardable"
5
+ require "ostruct"
6
+ require "uri"
7
+
8
8
  require_relative "universal_id/version"
9
- require_relative "universal_id/errors"
10
- require_relative "universal_id/config"
11
- require_relative "universal_id/portable"
12
- require_relative "universal_id/portable_hash"
13
- require_relative "universal_id/active_model_serializer"
9
+ require_relative "universal_id/settings"
10
+ require_relative "universal_id/encoder"
11
+ require_relative "uri/uid"
12
+ require_relative "universal_id/prepacker"
13
+ require_relative "universal_id/prepack_options"
14
+ require_relative "universal_id/message_pack_factory"
15
+
16
+ UniversalID::Settings.instance # initialize settings
17
+
18
+ module UniversalID
19
+ class << self
20
+ attr_writer :logger
21
+
22
+ def logger
23
+ @logger ||= defined?(Rails) ? Rails.logger : Logger.new(File::NULL)
24
+ end
25
+ end
26
+ end
data/lib/uri/uid.rb ADDED
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless defined?(::URI::UID) || ::URI.scheme_list.include?("UID")
4
+
5
+ module URI
6
+ class UID < ::URI::Generic
7
+ extend Forwardable
8
+
9
+ SCHEME = "uid"
10
+ HOST = "universal-id"
11
+
12
+ class << self
13
+ def parse(value)
14
+ components = ::URI.split(value.to_s)
15
+ new(*components)
16
+ end
17
+
18
+ def build_string(payload)
19
+ "#{SCHEME}://#{HOST}/#{payload}"
20
+ end
21
+
22
+ def build(object, options = {})
23
+ path = "/#{UniversalID::Encoder.encode(object, options)}"
24
+ parse "#{SCHEME}://#{HOST}#{path}"
25
+ end
26
+
27
+ # Creates a new URI::UID with the given URI components.
28
+ # SEE: https://ruby-doc.org/3.2.2/stdlibs/uri/URI/Generic.html#method-c-new
29
+ #
30
+ # @param scheme [String] the scheme component.
31
+ # @param userinfo [String] the userinfo component.
32
+ # @param host [String] the host component.
33
+ # @param port [Integer] the port component.
34
+ # @param registry [String] the registry component.
35
+ # @param path [String] the path component.
36
+ # @param opaque [String] the opaque component.
37
+ # @param query [String] the query component.
38
+ # @param fragment [String] the fragment component.
39
+ # @param parser [URI::Parser] the parser to use for the URI, defaults to DEFAULT_PARSER.
40
+ # @param arg_check [Boolean] whether to check arguments, defaults to false.
41
+ # @return [URI::UID] the new URI::UID instance.
42
+ # # @raise [URI::InvalidURIError] if the URI is malformed.
43
+ # @raise [ArgumentError] if the number of arguments is incorrect or an argument is of the wrong type.
44
+ # @raise [TypeError] if an argument is not of the expected type.
45
+ # @raise [URI::InvalidComponentError] if a component of the URI is not valid.
46
+ # @raise [URI::BadURIError] if the URI is in a bad or unexpected state.
47
+ def new(...)
48
+ super.tap do |uri|
49
+ if uri.invalid?
50
+ raise ::URI::InvalidComponentError, "Scheme must be `#{SCHEME}`" if uri.scheme != SCHEME
51
+ raise ::URI::InvalidComponentError, "Host must be `#{HOST}`" if uri.host != HOST
52
+ raise ::URI::InvalidComponentError, "Unable to parse `payload` from the path component!" if uri.payload.strip.empty?
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ def payload(truncate: false)
59
+ (truncate && path.length > 80) ? "#{path[1..77]}..." : path[1..]
60
+ end
61
+
62
+ def valid?
63
+ case self
64
+ in scheme: SCHEME, host: HOST, path: p if p.size >= 8 then return true
65
+ else false
66
+ end
67
+ end
68
+
69
+ def invalid?
70
+ !valid?
71
+ end
72
+
73
+ def decode
74
+ UniversalID::Encoder.decode(payload) if valid?
75
+ end
76
+
77
+ def deconstruct_keys(_keys)
78
+ {scheme: scheme, host: host, path: path}
79
+ end
80
+
81
+ def inspect
82
+ "#<URI::UID scheme=#{scheme}, host=#{host}, payload=#{payload truncate: true}>"
83
+ end
84
+ end
85
+
86
+ # Register the URI scheme
87
+ if ::URI.respond_to? :register_scheme
88
+ ::URI.register_scheme "UID", UID unless ::URI.scheme_list.include?("UID")
89
+ else
90
+ # shenanigans to support Ruby 3.0.X
91
+ ::URI::UID = UID unless defined?(::URI::UID)
92
+ ::URI.scheme_list["UID"] = UID
93
+ end
94
+ end
95
+ end