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
@@ -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