typed_uuid 3.2 → 4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9b97eecee8303b2863faf3982c4080d347c9dabc4b0fca45c2c5dc7c74b6172
4
- data.tar.gz: 72d70bc0bc247987141c6ee4710a5ef34257693179b1d526dcc6fbf427e414f5
3
+ metadata.gz: 9dd0a928d842b001570b023cace562666b6f391623fd27ca1c6b88cebd58ed3a
4
+ data.tar.gz: '09ea1aee57794d976b79c8bd221e1894519994603f8112bd4ffb55966b3082c6'
5
5
  SHA512:
6
- metadata.gz: 204697bd0dc2c7dd64b62dc7b7955cb5d83242c5966bc447c84d79d343f3b0825cc8bd1bb450525a3acc3405a61a1740b06f127a9df6f5f5830f9a063a75f18a
7
- data.tar.gz: cba9d2425930e678e4c44bfc163392fb749c249f5e2930b62998f22fae5ad10cdaa9e8df5df013b6155eac8c1c242056e4ff67b65dfb63140f82f545b24d38d4
6
+ metadata.gz: 5123b6d3291005d87ae8ed4fd1fefe9b55f10d83cde474367d9e136ff625a80a7bae221584a92c297f3a32e2b33e5a0c81bb3124b7acacb4bd82a28df94e4e82
7
+ data.tar.gz: 7e96e3a5376fdcd87ba3a0c89e0e1511707000d280aa17c52d618d6f7c150eb3ff33bf23102c6dfdbfe04b6b91dccf94f984d94d66e38b3830c89c23516ce805
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ name: TypedUUID Test
12
+ runs-on: ubuntu-20.04
13
+
14
+ steps:
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 3.0
18
+
19
+ - uses: actions/checkout@v2
20
+
21
+ - run: |
22
+ sudo systemctl start postgresql.service
23
+ sudo -u postgres createuser runner --superuser
24
+ sudo -u postgres createdb uuid-types-test
25
+
26
+ - run: bundle
27
+
28
+ - run: bundle exec rake test
data/README.md CHANGED
@@ -7,24 +7,44 @@ a hex representation of 4 bits.
7
7
 
8
8
  `xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx`
9
9
 
10
- Where:
11
-
12
10
  - M is 4 bits and is the Version
13
11
  - N is 3 bits and is the Variant of the Version followed a bit
14
12
 
15
- We modify this and use the following structure where the 7th & 8th bytes in the
16
- UUID are enum XORed with the result of XORing bytes 5 & 6 with bytes 9 & 10.
13
+ We modify this and use the following structure where the 15th & 16th bytes in the
14
+ UUID are the enum XORed with the result of XORing bytes 5 & 6 with bytes 13 & 14.
17
15
 
18
- `xxxxxxxx-YYYY-TTTT-ZZZZ-xxxxxxxxxxxx`
16
+ `xxxxxxxx-YYYY-xxxx-xxxx-xxxxZZZZTTTT`
19
17
 
20
18
  Where:
21
19
 
22
- - TTTT is the Type ENUM 0bNNNN_NNNN_NNNN_NNNN (0 - 65,535) XORed with (YYYY xor ZZZZ)
20
+ - TTTT is the Type ENUM & Version 0bEEEE_EEEE_EEEE_EVVV; XORed with (YYYY xor ZZZZ)
21
+ - The Es are the bits of the 13 bit ENUM supporting 8,192 enums/types (0 - 8,191)
22
+ - The Vs are the bits in the 3 bit version supporting 8 versions (0 - 7)
23
23
  - YYYY bytes XORed with ZZZZ and the Type ENUM to produce the identifying bytes
24
24
  - ZZZZ bytes XORed with YYYY and the Type ENUM to produce the identifying bytes
25
25
 
26
- XORing bytes 5 & 6 with 9 & 10 and XORing again with bytes 5 & 6 of the Typed UUID
27
- will give us back the ENUM of the Type using soley the UUID.
26
+ XORing bytes 5 & 6 with 13 & 14 and XORing again with bytes 15 & 16 of the
27
+ Typed UUID will give us back the ENUM and Version of the Type using soley the UUID.
28
+
29
+ ## Versions
30
+
31
+ As with regular UUID Typed UUIDs come in multiple version. The current versions are:
32
+
33
+ - Version 1: A timebased UUID where the first 56 bits are an unsigned integer
34
+ representing the microseconds since epoch. Followed by 48 random
35
+ bits or a sequence counter. Then 8 random bits followed by 16 bits
36
+ which are the UUID type.
37
+
38
+ - Version 3: A name-based UUID where the first 112 bits are based off the MD5
39
+ digest of the namespace and name. The following 16 bits are the
40
+ UUID type.
41
+
42
+ - Version 4: A random UUID where the first 112 bits are random. The following
43
+ 16 bits are the UUID type.
44
+
45
+ - Version 5: A name-based UUID where the first 112 bits are based off the SHA1
46
+ digest of the namespace and name. The following 16 bits are the
47
+ UUID type.
28
48
 
29
49
  ## Install
30
50
 
@@ -40,8 +60,9 @@ below. This maps the __Model Classes__ to an integer between 0 and 65,535.
40
60
 
41
61
  ActiveRecord::Base.register_uuid_types({
42
62
  Listing: 0,
43
- Building: 512,
44
- 'Building::SkyScrpaer' => 65_535
63
+ Address: {enum: 5},
64
+ Building: {enum: 512, version: 1},
65
+ 'Building::SkyScrpaer' => 8_191
45
66
  })
46
67
 
47
68
  # Or:
@@ -49,7 +70,7 @@ ActiveRecord::Base.register_uuid_types({
49
70
  ActiveRecord::Base.register_uuid_types({
50
71
  0 => :Listing,
51
72
  512 => :Building,
52
- 65_535 => 'Building::SkyScrpaer'
73
+ 8_191 => 'Building::SkyScrpaer'
53
74
  })
54
75
  ```
55
76
 
@@ -61,18 +82,50 @@ a table.
61
82
 
62
83
  ```ruby
63
84
  class CreateProperties < ActiveRecord::Migration[5.2]
64
- def change
65
- create_table :properties, id: :typed_uuid do |t|
85
+ def change
86
+ create_table :properties, id: :typed_uuid do |t|
66
87
  t.string "name", limit: 255
67
88
  end
68
89
  end
69
90
  end
70
91
  ```
71
92
 
93
+ To add a typed UUID to an existing table:
94
+
95
+ ```ruby
96
+ class UpdateProperties < ActiveRecord::Migration[6.1]
97
+ def change
98
+ klass_enum = ::ActiveRecord::Base.uuid_type_from_table_name(:properties)
99
+
100
+ # Add the column
101
+ add_column :properties, :typed_uuid, :uuid, default: -> { "typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}')" }
102
+
103
+ # Update existing properties with a new typed UUID
104
+ execute "UPDATE properties SET id = typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}');"
105
+
106
+ # Add null constraint since we'll swap these out for the primary key
107
+ change_column_null :properties, :typed_uuid, false
108
+
109
+ # TODO: Here you will want to update any reference to the old primary key
110
+ # with the new typed_uuid that will be the new primary key.
111
+
112
+ # Replace the old primary key with the typed_uuid
113
+ execute "ALTER TABLE properties DROP CONSTRAINT properties_pkey;"
114
+ rename_column :properties, :typed_uuid, :id
115
+ execute "ALTER TABLE properties ADD PRIMARY KEY (id);"
116
+ end
117
+ ```
118
+
119
+ When you need to lookup they class of a TypedUUID:
120
+
121
+ ```ruby
122
+ ActiveRecord::Base.class_from_uuid(my_uuid) #=> MyClass
123
+ ```
124
+
72
125
  ## STI Models
73
126
  When using STI Model Rails will generate the UUID to be inserted. This UUID will
74
127
  be calculated of the STI Model class and not the base class.
75
128
 
76
129
  In the migration you can still used `id: :typed_uuid`, this will use the base
77
130
  class to calculated the default type for the UUID. You could also set the
78
- `id` to `:uuid` and the `default` to `false` so when no ID is given it will error.
131
+ `id` to `:uuid` and the `default` to `false` so when no ID is given it will error.
@@ -4,15 +4,27 @@ class AddTypedUuidFunction < ActiveRecord::Migration[6.0]
4
4
  enable_extension 'pgcrypto'
5
5
 
6
6
  execute <<-SQL
7
- CREATE OR REPLACE FUNCTION typed_uuid(t bytea) RETURNS uuid AS $$
8
- DECLARE
9
- bytes bytea := gen_random_bytes(16);
10
- uuid bytea;
11
- BEGIN
12
- bytes := set_byte(bytes, 6, (get_byte(bytes, 4) # get_byte(bytes, 8)) # get_byte(t, 0));
13
- bytes := set_byte(bytes, 7, (get_byte(bytes, 5) # get_byte(bytes, 9)) # get_byte(t, 1));
14
- RETURN encode( bytes, 'hex') :: uuid;
15
- END;
7
+ CREATE OR REPLACE FUNCTION typed_uuid(enum int, version int default 4)
8
+ RETURNS uuid AS $$
9
+ DECLARE
10
+ bytes bytea;
11
+ type bytea;
12
+ BEGIN
13
+ IF version = 1 THEN
14
+ bytes := decode(concat(
15
+ lpad(right(to_hex((extract(epoch from clock_timestamp())*1000000)::bigint), 12), 12, '0'),
16
+ encode(gen_random_bytes(10), 'hex')
17
+ ), 'hex');
18
+ ELSE
19
+ bytes := gen_random_bytes(16);
20
+ END IF;
21
+
22
+ type := decode( lpad(to_hex(((enum << 3) | version)), 4, '0'), 'hex');
23
+ bytes := set_byte(bytes, 14, (get_byte(bytes, 4) # get_byte(bytes, 12)) # get_byte(type, 0));
24
+ bytes := set_byte(bytes, 15, (get_byte(bytes, 5) # get_byte(bytes, 13)) # get_byte(type, 1));
25
+
26
+ RETURN encode( bytes, 'hex') :: uuid;
27
+ END;
16
28
  $$ LANGUAGE plpgsql;
17
29
  SQL
18
30
  end
@@ -10,9 +10,11 @@ module TypedUUID::ActiveRecord
10
10
  included do
11
11
  class_attribute(:defined_uuid_types, instance_writer: false, default: {})
12
12
  class_attribute(:class_to_uuid_type_cache, instance_writer: false, default: Hash.new { |hash, klass|
13
- hash[klass] = defined_uuid_types.key(klass.name)
13
+ if type = defined_uuid_types.find { |k, v| v[:class] == klass.name }
14
+ hash[klass] = type[1]
15
+ end
14
16
  })
15
- class_attribute(:uuid_type_to_class_cache, instance_writer: false, default: {})
17
+ class_attribute(:uuid_enum_to_class_cache, instance_writer: false, default: {})
16
18
  end
17
19
 
18
20
  def _create_record
@@ -26,17 +28,25 @@ module TypedUUID::ActiveRecord
26
28
  end
27
29
 
28
30
  class_methods do
29
- def register_uuid_type(class_name, int)
30
- if int < 0 || int > 65_535
31
+ def register_uuid_type(class_name, enum_or_options)
32
+ if enum_or_options.is_a?(Hash)
33
+ enum = enum_or_options[:enum]
34
+ version = enum_or_options[:version] || 4
35
+ else
36
+ enum = enum_or_options
37
+ version = 4
38
+ end
39
+
40
+ if enum < 0 || enum > 8_191
31
41
  raise ArgumentError, "UUID type must be between 0 and 65,535"
32
- elsif defined_uuid_types.has_key?(int)
42
+ elsif defined_uuid_types.has_key?(enum)
33
43
  raise ArgumentError, UUID_TYPE_CONFLICT_MESSAGE % {
34
- int: int,
44
+ int: enum,
35
45
  class_name: class_name,
36
- other: defined_uuid_types[int]
46
+ other: defined_uuid_types[enum]
37
47
  }
38
48
  else
39
- defined_uuid_types[int] = class_name.to_s
49
+ defined_uuid_types[enum] = { class: class_name.to_s, version: version, enum: enum }
40
50
  end
41
51
  end
42
52
 
@@ -54,37 +64,51 @@ module TypedUUID::ActiveRecord
54
64
  !!class_to_uuid_type_cache[self.base_class]
55
65
  end
56
66
 
57
- def typed_uuid
58
- TypedUUID.uuid(uuid_type_from_class(self))
67
+ def typed_uuid(**options)
68
+ TypedUUID.uuid(uuid_enum_from_class(self), uuid_version_from_class(self), **options)
59
69
  end
60
-
61
- def uuid_type_from_table_name(table)
62
- uuid_type_from_class(class_from_table_name(table))
70
+
71
+ def uuid_enum_from_table_name(table)
72
+ uuid_enum_from_class(class_from_table_name(table))
63
73
  end
64
74
 
65
- def uuid_type_from_class(klass)
75
+ def uuid_version_from_table_name(table)
76
+ uuid_version_from_class(class_from_table_name(table))
77
+ end
78
+
79
+ def uuid_enum_from_class(klass)
66
80
  type = class_to_uuid_type_cache[klass]
81
+ if type.nil?
82
+ raise ArgumentError, "UUID Type for \"#{klass.name}\" not defined"
83
+ end
84
+
85
+ type[:enum]
86
+ end
67
87
 
88
+ def uuid_version_from_class(klass)
89
+ type = class_to_uuid_type_cache[klass]
68
90
  if type.nil?
69
91
  raise ArgumentError, "UUID Type for \"#{klass.name}\" not defined"
70
92
  end
71
93
 
72
- type
94
+ type[:version]
73
95
  end
74
-
75
- def class_from_uuid_type(type)
76
- klass = if uuid_type_to_class_cache.has_key?(type)
77
- uuid_type_to_class_cache[type]
96
+
97
+ def class_from_uuid_enum(enum)
98
+ if uuid_enum_to_class_cache.has_key?(enum)
99
+ uuid_enum_to_class_cache[enum]
78
100
  else
79
101
  Rails.application.eager_load! if !Rails.application.config.eager_load
80
102
 
81
103
  ::ActiveRecord::Base.descendants.each do |klass|
82
104
  next if klass.table_name.nil?
83
-
84
- uuid_type_to_class_cache[defined_uuid_types.key(klass.name)] = klass
105
+
106
+ if key = defined_uuid_types.find { |enum, info| info[:class] == klass.name }
107
+ uuid_enum_to_class_cache[key[0]] = klass
108
+ end
85
109
  end
86
110
 
87
- uuid_type_to_class_cache[type]
111
+ uuid_enum_to_class_cache[enum]
88
112
  end
89
113
  end
90
114
 
@@ -101,7 +125,7 @@ module TypedUUID::ActiveRecord
101
125
  end
102
126
 
103
127
  def class_from_uuid(uuid)
104
- class_from_uuid_type(TypedUUID.enum(uuid))
128
+ class_from_uuid_enum(TypedUUID.enum(uuid))
105
129
  end
106
130
  end
107
131
 
@@ -2,9 +2,10 @@ module TypedUUID::PsqlColumnMethods
2
2
 
3
3
  def primary_key(name, type = :primary_key, **options)
4
4
  if type == :typed_uuid
5
- klass_enum = ::ActiveRecord::Base.uuid_type_from_table_name(self.name)
5
+ klass_type_enum = ::ActiveRecord::Base.uuid_enum_from_table_name(self.name)
6
+ klass_type_version = ::ActiveRecord::Base.uuid_version_from_table_name(self.name)
6
7
  options[:id] = :uuid
7
- options[:default] ||= -> { "typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}')" }
8
+ options[:default] ||= -> { "typed_uuid(#{klass_type_enum}, #{klass_type_version})" }
8
9
  super(name, :uuid, **options)
9
10
  else
10
11
  super
@@ -6,15 +6,27 @@ module TypedUUID::PsqlSchemaDumper
6
6
  ## These are functions that must be enabled in order to support typed_uuids
7
7
  ## in this database
8
8
  execute <<-SQL
9
- CREATE OR REPLACE FUNCTION typed_uuid(t bytea) RETURNS uuid AS $$
10
- DECLARE
11
- bytes bytea := gen_random_bytes(16);
12
- uuid bytea;
13
- BEGIN
14
- bytes := set_byte(bytes, 6, (get_byte(bytes, 4) # get_byte(bytes, 8)) # get_byte(t, 0));
15
- bytes := set_byte(bytes, 7, (get_byte(bytes, 5) # get_byte(bytes, 9)) # get_byte(t, 1));
16
- RETURN encode( bytes, 'hex') :: uuid;
17
- END;
9
+ CREATE OR REPLACE FUNCTION typed_uuid(enum int, version int default 4)
10
+ RETURNS uuid AS $$
11
+ DECLARE
12
+ bytes bytea;
13
+ type bytea;
14
+ BEGIN
15
+ IF version = 1 THEN
16
+ bytes := decode(concat(
17
+ lpad(right(to_hex((extract(epoch from clock_timestamp())*1000000)::bigint), 12), 12, '0'),
18
+ encode(gen_random_bytes(10), 'hex')
19
+ ), 'hex');
20
+ ELSE
21
+ bytes := gen_random_bytes(16);
22
+ END IF;
23
+
24
+ type := decode( lpad(to_hex(((enum << 3) | version)), 4, '0'), 'hex');
25
+ bytes := set_byte(bytes, 14, (get_byte(bytes, 4) # get_byte(bytes, 12)) # get_byte(type, 0));
26
+ bytes := set_byte(bytes, 15, (get_byte(bytes, 5) # get_byte(bytes, 13)) # get_byte(type, 1));
27
+
28
+ RETURN encode( bytes, 'hex') :: uuid;
29
+ END;
18
30
  $$ LANGUAGE plpgsql;
19
31
  SQL
20
32
 
@@ -2,16 +2,17 @@ class TypedUUID::Railtie < Rails::Railtie
2
2
 
3
3
  initializer :typed_uuid do |app|
4
4
  ActiveRecord::Tasks::DatabaseTasks.migrations_paths << File.expand_path('../../../db/migrate', __FILE__)
5
-
5
+
6
6
  ActiveSupport.on_load(:active_record) do
7
7
  ActiveRecord::Base.include TypedUUID::ActiveRecord
8
8
  end
9
-
9
+
10
10
  require 'active_record/connection_adapters/postgresql/schema_definitions'
11
11
  ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.include(TypedUUID::PsqlColumnMethods)
12
-
12
+
13
+ require 'active_record/connection_adapters/abstract/schema_dumper'
13
14
  require 'active_record/connection_adapters/postgresql/schema_dumper'
14
15
  ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend(TypedUUID::PsqlSchemaDumper)
15
16
  end
16
17
 
17
- end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module TypedUUID
2
- VERSION = '3.2'
2
+ VERSION = '4.1'
3
3
  end
data/lib/typed_uuid.rb CHANGED
@@ -1,17 +1,92 @@
1
+ require 'digest/sha1'
2
+
1
3
  module TypedUUID
2
4
  autoload :ActiveRecord, 'typed_uuid/active_record'
3
5
  autoload :PsqlColumnMethods, 'typed_uuid/psql_column_methods'
4
6
  autoload :PsqlSchemaDumper, 'typed_uuid/psql_schema_dumper'
5
-
6
- def self.uuid(enum)
7
- uuid = SecureRandom.random_bytes(16).unpack("NnnnnN")
8
- uuid[2] = (uuid[1] ^ uuid[3]) ^ enum
9
- "%08x-%04x-%04x-%04x-%04x%08x" % uuid
10
- end
11
-
12
- def self.enum(uuid)
13
- uuid = uuid.gsub('-', '')
14
- (uuid[8..11].to_i(16) ^ uuid[16..19].to_i(16)) ^ uuid[12..15].to_i(16)
7
+
8
+ class << self
9
+
10
+ def uuid(enum, version = 4, **options)
11
+ if enum < 0 || enum > 8_191
12
+ raise ArgumentError, "UUID type must be between 0 and 8,191"
13
+ end
14
+
15
+ case version
16
+ when 1
17
+ timestamp_uuid(enum, **options)
18
+ when 3
19
+ namebased_uuid(enum, digester: Digest::MD5, **options)
20
+ when 4
21
+ random_uuid(enum, **options)
22
+ when 5
23
+ namebased_uuid(enum, digester: Digest::SHA1, **options)
24
+ end
25
+ end
26
+
27
+ def random_uuid(enum)
28
+ uuid = SecureRandom.random_bytes(16).unpack("nnnnnnnn")
29
+
30
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 4)
31
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
32
+ end
33
+
34
+ def timestamp_uuid(enum, timestamp: nil, sequence: nil)
35
+ timestamp ||= Time.now
36
+
37
+ uuid = [timestamp.to_i * 1_000_000 + timestamp.usec].pack('Q>')[1..-1]
38
+ uuid << if sequence.nil?
39
+ SecureRandom.random_bytes(8)
40
+ elsif sequence.is_a?(Integer)
41
+ sequence = [sequence].pack("Q>")
42
+ if sequence.bytesize == 8 && sequence[0..1] == "\x00\x00"
43
+ sequence[2..]
44
+ else
45
+ raise ArgumentError, 'Sequence is more than 6 bytes'
46
+ end
47
+ elsif sequence.is_a?(String)
48
+ raise ArgumentError, 'Sequence is more than 6 bytes' if sequence.bytesize > 6
49
+ sequence.b
50
+ else
51
+ raise ArgumentError, 'Unable to convert sequence to binary'
52
+ end
53
+ uuid << SecureRandom.random_bytes(4)
54
+
55
+ uuid = uuid.unpack("nnnnnnnn")
56
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 1)
57
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
58
+ end
59
+
60
+ def namebased_uuid(enum, digester:, name:, namespace: "")
61
+ uuid = digester.digest(name + namespace).unpack("nnnnnnnn")
62
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 5)
63
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
64
+ end
65
+
66
+ def enum(uuid)
67
+ uuid = uuid.gsub('-', '')
68
+ ((uuid[8..11].to_i(16) ^ uuid[24..27].to_i(16)) ^ uuid[28..31].to_i(16)) >> 3
69
+ end
70
+
71
+ def version(uuid)
72
+ uuid = uuid.gsub('-', '')
73
+ ((uuid[8..11].to_i(16) ^ uuid[24..27].to_i(16)) ^ uuid[28..31].to_i(16)) & 0b0000000000000111
74
+ end
75
+
76
+ def timestamp(uuid)
77
+ uuid = uuid.gsub('-', '')
78
+ Time.at(*uuid[0..13].to_i(16).divmod(1_000_000), :usec)
79
+ end
80
+
81
+ def sequence_b(uuid)
82
+ uuid = uuid.gsub('-', '')
83
+ uuid[14..25].scan(/.{4}/).map{|i| i.to_i(16) }.pack('n*').b
84
+ end
85
+
86
+ def sequence(uuid)
87
+ ("\x00\x00" + sequence_b(uuid)).unpack1('Q>')
88
+ end
89
+
15
90
  end
16
91
  end
17
92
 
data/test/test_helper.rb CHANGED
@@ -32,7 +32,7 @@ def MiniTest.filter_backtrace(bt)
32
32
  bt
33
33
  end
34
34
  class ActiveSupport::TestCase
35
-
35
+
36
36
  # File 'lib/active_support/testing/declarative.rb'
37
37
  def self.test(name, &block)
38
38
  test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym
@@ -46,31 +46,32 @@ class ActiveSupport::TestCase
46
46
  end
47
47
  end
48
48
  end
49
-
49
+
50
50
  def self.schema(&block)
51
51
  self.class_variable_set(:@@schema, block)
52
52
  end
53
-
53
+
54
54
  set_callback(:setup, :before) do
55
55
  Rails.stubs(:application).returns(stub(config: stub(eager_load: true)))
56
56
  if !self.class.class_variable_defined?(:@@suite_setup_run)
57
57
  ActiveRecord::Base.defined_uuid_types.clear
58
- ActiveRecord::Base.uuid_type_to_class_cache.clear
58
+ ActiveRecord::Base.uuid_enum_to_class_cache.clear
59
59
  ActiveRecord::Base.class_to_uuid_type_cache.clear
60
-
60
+
61
61
  configuration = {
62
62
  adapter: "postgresql",
63
63
  database: "uuid-types-test",
64
64
  encoding: "utf8"
65
65
  }.stringify_keys
66
-
67
- db_tasks = ActiveRecord::Tasks::PostgreSQLDatabaseTasks.new(configuration)
68
- db_tasks.purge
69
-
66
+
70
67
  ActiveRecord::Base.establish_connection(configuration)
68
+
69
+ db_tasks = ActiveRecord::Tasks::PostgreSQLDatabaseTasks.new(ActiveRecord::Base.connection_db_config)
70
+ db_tasks.purge
71
+
71
72
  ActiveRecord::Migration.suppress_messages do
72
73
  AddTypedUuidFunction.migrate :up
73
-
74
+
74
75
  if self.class.class_variable_defined?(:@@schema)
75
76
  ActiveRecord::Schema.define(&self.class.class_variable_get(:@@schema))
76
77
  ActiveRecord::Migration.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'").each_row do |row|
@@ -134,7 +135,7 @@ class ActiveSupport::TestCase
134
135
  end
135
136
  end
136
137
  end
137
-
138
+
138
139
  class SQLLogger
139
140
  class << self
140
141
  attr_accessor :ignored_sql, :log, :log_all
@@ -197,5 +198,5 @@ class ActiveSupport::TestCase
197
198
  alias :assert_not_predicate :refute_predicate
198
199
  alias :assert_not_respond_to :refute_respond_to
199
200
  alias :assert_not_same :refute_same
200
-
201
- end
201
+
202
+ end
@@ -1,46 +1,49 @@
1
1
  require 'test_helper'
2
2
 
3
- class FilterTest < ActiveSupport::TestCase
3
+ class UUIDTest < ActiveSupport::TestCase
4
4
 
5
5
  schema do
6
6
  ActiveRecord::Base.register_uuid_types({
7
- 'FilterTest::Listing' => 0,
8
- 'FilterTest::Building' => 592,
9
- 'FilterTest::SkyScraper' => 1_952
7
+ 'UUIDTest::Listing' => {enum: 0, version: 1},
8
+ 'UUIDTest::Building' => 592,
9
+ 'UUIDTest::SkyScraper' => {enum: 1_952}
10
10
  })
11
-
11
+
12
12
  create_table :listings, id: :typed_uuid do |t|
13
13
  t.string "name", limit: 255
14
14
  end
15
-
15
+
16
16
  create_table :buildings, id: :typed_uuid do |t|
17
17
  t.string "name", limit: 255
18
18
  t.string "type", limit: 255
19
19
  end
20
20
  end
21
-
21
+
22
22
  class Listing < ActiveRecord::Base
23
23
  end
24
-
24
+
25
25
  class Building < ActiveRecord::Base
26
26
  end
27
-
27
+
28
28
  class SkyScraper < Building
29
29
  end
30
30
 
31
31
  class SingleFamilyHome < Building
32
32
  end
33
-
33
+
34
34
  class Property < ActiveRecord::Base
35
35
  end
36
-
36
+
37
+ class Lot < ActiveRecord::Base
38
+ end
39
+
37
40
  test 'adding primary key as a typed_uuid in a migration' do
38
41
  ActiveRecord::Base.register_uuid_types({
39
- 1 => 'FilterTest::Property'
42
+ 1 => 'UUIDTest::Property'
40
43
  })
41
44
 
42
45
  exprexted_sql = <<-SQL
43
- CREATE TABLE "properties" ("id" uuid DEFAULT typed_uuid('\\x0001') NOT NULL PRIMARY KEY, "name" character varying(255))
46
+ CREATE TABLE "properties" ("id" uuid DEFAULT typed_uuid(1, 4) NOT NULL PRIMARY KEY, "name" character varying(255))
44
47
  SQL
45
48
 
46
49
  assert_sql exprexted_sql do
@@ -52,48 +55,139 @@ class FilterTest < ActiveSupport::TestCase
52
55
  end
53
56
  end
54
57
 
58
+ test 'adding primary key as a typed_uuid in a migration with a version' do
59
+ ActiveRecord::Base.register_uuid_types({
60
+ 'UUIDTest::Lot' => {version: 1, enum: 512}
61
+ })
62
+
63
+ exprexted_sql = <<-SQL
64
+ CREATE TABLE "lots" ("id" uuid DEFAULT typed_uuid(512, 1) NOT NULL PRIMARY KEY, "name" character varying(255))
65
+ SQL
66
+
67
+ assert_sql exprexted_sql do
68
+ ActiveRecord::Migration.suppress_messages do
69
+ ActiveRecord::Migration.create_table :lots, id: :typed_uuid do |t|
70
+ t.string "name", limit: 255
71
+ end
72
+ end
73
+ end
74
+ end
75
+
55
76
  test 'typed_uuid' do
56
77
  assert_equal 512, TypedUUID.enum(TypedUUID.uuid(512))
57
- assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
58
- assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid(Building.typed_uuid)
59
- assert_equal FilterTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid(SkyScraper.typed_uuid)
60
-
78
+ assert_equal 512, TypedUUID.enum(TypedUUID.uuid(512, 1))
79
+ assert_equal 512, TypedUUID.enum(TypedUUID.uuid(512, 3, name: "test"))
80
+ assert_equal 512, TypedUUID.enum(TypedUUID.uuid(512, 5, name: "test"))
81
+ assert_equal UUIDTest::Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
82
+ assert_equal UUIDTest::Building, ::ActiveRecord::Base.class_from_uuid(Building.typed_uuid)
83
+ assert_equal UUIDTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid(SkyScraper.typed_uuid)
84
+
61
85
  assert_raises ArgumentError do
62
86
  ::ActiveRecord::Base.class_from_uuid(SingleFamilyHome.typed_uuid)
63
87
  end
64
88
  end
89
+
90
+ test 'typed_uuid v1' do
91
+ time = Time.at(1606612254, 370979, :usec)
92
+ Time.stubs(:now).returns(time)
93
+
94
+ uuid = TypedUUID.uuid(592, 1)
95
+
96
+ assert_equal 1, TypedUUID.version(uuid)
97
+ assert_equal 592, TypedUUID.enum(uuid)
98
+
99
+ assert_equal time, TypedUUID.timestamp(uuid)
100
+ assert_equal time.to_i, TypedUUID.timestamp(uuid).to_i
101
+ assert_equal time.nsec, TypedUUID.timestamp(uuid).nsec
102
+
103
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
104
+ end
65
105
 
66
- test 'class_from uuid' do
106
+ test 'typed_uuid v1 with a timestamp' do
107
+ time = Time.at(1606612254, 370979, :usec)
108
+ uuid = TypedUUID.uuid(592, 1, timestamp: time)
109
+
110
+ assert_equal 1, TypedUUID.version(uuid)
111
+ assert_equal 592, TypedUUID.enum(uuid)
112
+
113
+ assert_equal time, TypedUUID.timestamp(uuid)
114
+ assert_equal time.to_i, TypedUUID.timestamp(uuid).to_i
115
+ assert_equal time.nsec, TypedUUID.timestamp(uuid).nsec
116
+
117
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
118
+ end
119
+
120
+ test 'typed_uuid v1 with sequence as a string' do
121
+ uuid = TypedUUID.uuid(592, 1, sequence: '123456')
122
+
123
+ assert_equal 1, TypedUUID.version(uuid)
124
+ assert_equal 592, TypedUUID.enum(uuid)
125
+ assert_equal 54091677185334, TypedUUID.sequence(uuid)
126
+ assert_equal '123456', TypedUUID.sequence_b(uuid)
127
+
128
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
129
+ end
130
+
131
+ test 'typed_uuid v1 with sequence as a integer' do
132
+ uuid = TypedUUID.uuid(592, 1, sequence: 123456)
133
+
134
+ assert_equal 1, TypedUUID.version(uuid)
135
+ assert_equal 592, TypedUUID.enum(uuid)
136
+ assert_equal 123456, TypedUUID.sequence(uuid)
137
+ assert_equal "\x00\x00\x00\x01\xE2@".b, TypedUUID.sequence_b(uuid)
138
+
139
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
140
+ end
141
+
142
+ test 'class_from uuid generated bye PostgresQL' do
67
143
  listing = Listing.create
68
144
  building = Building.create
69
145
  skyscraper = SkyScraper.create
70
-
71
- assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid(listing.id)
72
- assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid(building.id)
73
- assert_equal FilterTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid(skyscraper.id)
146
+
147
+ assert_equal UUIDTest::Listing, ::ActiveRecord::Base.class_from_uuid(listing.id)
148
+ assert_equal UUIDTest::Building, ::ActiveRecord::Base.class_from_uuid(building.id)
149
+ assert_equal UUIDTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid(skyscraper.id)
74
150
 
75
151
  assert_raises ArgumentError do
76
152
  SingleFamilyHome.create
77
153
  end
78
154
  end
79
-
80
- test 'uuid_type from table_name' do
81
- assert_equal 0, ::ActiveRecord::Base.uuid_type_from_table_name(:listings)
82
- assert_equal 0, ::ActiveRecord::Base.uuid_type_from_table_name('listings')
83
- assert_equal 592, ::ActiveRecord::Base.uuid_type_from_table_name(:buildings)
155
+
156
+ test 'class_from v1 uuid generated bye PostgresQL' do
157
+ listing = Listing.create
158
+
159
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(listing.id)
84
160
  end
85
-
86
- test 'uuid_type from class' do
87
- assert_equal 0, ::ActiveRecord::Base.uuid_type_from_class(Listing)
88
- assert_equal 0, ::ActiveRecord::Base.uuid_type_from_class(Listing)
89
- assert_equal 592, ::ActiveRecord::Base.uuid_type_from_class(Building)
90
- assert_equal 1_952, ::ActiveRecord::Base.uuid_type_from_class(SkyScraper)
161
+
162
+ test 'uuid_enum from table_name' do
163
+ assert_equal 0, ::ActiveRecord::Base.uuid_enum_from_table_name(:listings)
164
+ assert_equal 0, ::ActiveRecord::Base.uuid_enum_from_table_name('listings')
165
+
166
+ assert_equal 592, ::ActiveRecord::Base.uuid_enum_from_table_name(:buildings)
91
167
  end
92
-
93
- test 'class from uuid_type' do
94
- assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid_type(0)
95
- assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid_type(592)
96
- assert_equal FilterTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid_type(1_952)
168
+
169
+ test 'uuid_version from table_name' do
170
+ assert_equal 1, ::ActiveRecord::Base.uuid_version_from_table_name(:listings)
171
+ assert_equal 1, ::ActiveRecord::Base.uuid_version_from_table_name('listings')
172
+ assert_equal 4, ::ActiveRecord::Base.uuid_version_from_table_name(:buildings)
97
173
  end
98
-
99
- end
174
+
175
+ test 'uuid_enum from class' do
176
+ assert_equal 0, ::ActiveRecord::Base.uuid_enum_from_class(Listing)
177
+ assert_equal 592, ::ActiveRecord::Base.uuid_enum_from_class(Building)
178
+ assert_equal 1_952, ::ActiveRecord::Base.uuid_enum_from_class(SkyScraper)
179
+ end
180
+
181
+ test 'uuid_version from class' do
182
+ assert_equal 1, ::ActiveRecord::Base.uuid_version_from_class(Listing)
183
+ assert_equal 4, ::ActiveRecord::Base.uuid_version_from_class(Building)
184
+ assert_equal 4, ::ActiveRecord::Base.uuid_version_from_class(SkyScraper)
185
+ end
186
+
187
+ test 'class from uuid_enum' do
188
+ assert_equal UUIDTest::Listing, ::ActiveRecord::Base.class_from_uuid_enum(0)
189
+ assert_equal UUIDTest::Building, ::ActiveRecord::Base.class_from_uuid_enum(592)
190
+ assert_equal UUIDTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid_enum(1_952)
191
+ end
192
+
193
+ end
data/typed_uuid.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
15
  s.require_paths = ["lib"]
16
16
 
17
- # Developoment
17
+ # Developoment
18
18
  s.add_development_dependency 'rake'
19
19
  s.add_development_dependency 'bundler'
20
20
  s.add_development_dependency 'minitest'
@@ -23,9 +23,9 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency 'mocha'
24
24
  s.add_development_dependency 'byebug'
25
25
  s.add_development_dependency 'activesupport', '>= 6.0.0'
26
- s.add_development_dependency 'rails', '>= 6.0.0'
26
+ s.add_development_dependency 'rails', '>= 6.1.0'
27
27
  s.add_development_dependency 'pg'
28
-
28
+
29
29
  # Runtime
30
- s.add_runtime_dependency 'activerecord', '>= 6.0.0'
31
- end
30
+ s.add_runtime_dependency 'activerecord', '>= 6.1.0'
31
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_uuid
3
3
  version: !ruby/object:Gem::Version
4
- version: '3.2'
4
+ version: '4.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-04 00:00:00.000000000 Z
11
+ date: 2021-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: 6.0.0
131
+ version: 6.1.0
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: 6.0.0
138
+ version: 6.1.0
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: pg
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: 6.0.0
159
+ version: 6.1.0
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
- version: 6.0.0
166
+ version: 6.1.0
167
167
  description: Typed UUIDs 2 bytes are reserved in the UUID for the class enum
168
168
  email:
169
169
  - jonbracy@gmail.com
@@ -171,6 +171,7 @@ executables: []
171
171
  extensions: []
172
172
  extra_rdoc_files: []
173
173
  files:
174
+ - ".github/workflows/ci.yml"
174
175
  - ".gitignore"
175
176
  - ".tm_properties"
176
177
  - Gemfile
@@ -205,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
205
206
  - !ruby/object:Gem::Version
206
207
  version: '0'
207
208
  requirements: []
208
- rubygems_version: 3.1.4
209
+ rubygems_version: 3.2.3
209
210
  signing_key:
210
211
  specification_version: 4
211
212
  summary: Typed UUIDs for ActiveRecord