typed_uuid 2.3 → 4.0.rc2

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: 42ecd88d5a86ad89fe9a8c0cd8ec275bfded2d66ce7a7bd240f43184d0fe65ab
4
- data.tar.gz: 2b32e8528b4d5dc750c54b8818ccb0fcbb99ea4d5276ece385d8af8f2f70c259
3
+ metadata.gz: 581721d0683d1e3bf4bfa2c4bc29d4a7f5079854c85d98d8672e2f9bec6934e9
4
+ data.tar.gz: cb660ea624b3be55d02084ca8175035ade7774604a7428cb4555fa00e95460e6
5
5
  SHA512:
6
- metadata.gz: 2bacb4da4092347b9a483711e7e4530979bf2b135d03bc77f03599851463c21041dbef6b566c0ba6d151c1d2c516a4ca98caa150592888f2a3c85ec941dd060d
7
- data.tar.gz: 7e4dd50dbd81e87934f04daf4bf94ed89f7bac9f024e23886bc3c401d626137de664aa8a8e087d02cb57f1193309159a5c6a97ac5b9258ab326c787669f427dc
6
+ metadata.gz: 25835d74e2cf54a76f4be490f95fa98e5b0cbde8ddec6bafdd990a17d9a900fa7033f15f63a4be23609ae1164d686132a0472bec2e168d93f19e6204af1ab315
7
+ data.tar.gz: 8dcd77488ddd9f45bba84db7faec4b3e1f37c4258df3dc6273bee7eef1b620799cd725d56338759dc1d488aece3c32102a7364232e03183fa123cef481aa0543
data/README.md CHANGED
@@ -7,24 +7,36 @@ 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 4: A random UUID where the first 112 bits are random. The following
39
+ 16 bits are the UUID type.
28
40
 
29
41
  ## Install
30
42
 
@@ -33,24 +45,28 @@ Add this to your Gemfile:
33
45
  `gem 'typed_uuid'`
34
46
 
35
47
  Once bundled you can add an initializer to Rails to register your types as shown
36
- below. This maps the __table_names__ of the models to an integer between 0 and 255.
48
+ below. This maps the __Model Classes__ to an integer between 0 and 65,535.
37
49
 
38
50
  ```ruby
39
51
  # config/initializers/uuid_types.rb
40
52
 
41
53
  ActiveRecord::Base.register_uuid_types({
42
- listings: 0,
43
- buildings: 65_535
54
+ Listing: 0,
55
+ Address: {enum: 5},
56
+ Building: {enum: 512, version: 1},
57
+ 'Building::SkyScrpaer' => 8_191
44
58
  })
45
59
 
46
60
  # Or:
47
61
 
48
62
  ActiveRecord::Base.register_uuid_types({
49
- 0 => :listings,
50
- 65_535 => :buildings
63
+ 0 => :Listing,
64
+ 512 => :Building,
65
+ 8_191 => 'Building::SkyScrpaer'
51
66
  })
52
67
  ```
53
68
 
69
+
54
70
  ## Usage
55
71
 
56
72
  In your migrations simply replace `id: :uuid` with `id: :typed_uuid` when creating
@@ -64,4 +80,38 @@ class CreateProperties < ActiveRecord::Migration[5.2]
64
80
  end
65
81
  end
66
82
  end
67
- ```
83
+ ```
84
+
85
+ To add a typed UUID to an existing table:
86
+
87
+ ```ruby
88
+ class UpdateProperties < ActiveRecord::Migration[6.1]
89
+ def change
90
+ klass_enum = ::ActiveRecord::Base.uuid_type_from_table_name(:properties)
91
+
92
+ # Add the column
93
+ add_column :properties, :typed_uuid, :uuid, default: -> { "typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}')" }
94
+
95
+ # Update existing properties with a new typed UUID
96
+ execute "UPDATE properties SET id = typed_uuid('\\x#{klass_enum.to_s(16).rjust(4, '0')}');"
97
+
98
+ # Add null constraint since we'll swap these out for the primary key
99
+ change_column_null :properties, :typed_uuid, false
100
+
101
+ # TODO: Here you will want to update any reference to the old primary key
102
+ # with the new typed_uuid that will be the new primary key.
103
+
104
+ # Replace the old primary key with the typed_uuid
105
+ execute "ALTER TABLE properties DROP CONSTRAINT properties_pkey;"
106
+ rename_column :properties, :typed_uuid, :id
107
+ execute "ALTER TABLE properties ADD PRIMARY KEY (id);"
108
+ end
109
+ ```
110
+
111
+ ## STI Models
112
+ When using STI Model Rails will generate the UUID to be inserted. This UUID will
113
+ be calculated of the STI Model class and not the base class.
114
+
115
+ In the migration you can still used `id: :typed_uuid`, this will use the base
116
+ class to calculated the default type for the UUID. You could also set the
117
+ `id` to `:uuid` and the `default` to `false` so when no ID is given it will error.
@@ -1,18 +1,32 @@
1
1
  class AddTypedUuidFunction < ActiveRecord::Migration[6.0]
2
2
  def up
3
- enable_extension 'pgcrypto'
3
+ if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
4
+ enable_extension 'pgcrypto'
4
5
 
5
- execute <<-SQL
6
- CREATE OR REPLACE FUNCTION typed_uuid(t bytea) RETURNS uuid AS $$
6
+ execute <<-SQL
7
+ CREATE OR REPLACE FUNCTION typed_uuid(enum int, version int default 4)
8
+ RETURNS uuid AS $$
7
9
  DECLARE
8
- bytes bytea := gen_random_bytes(16);
9
- uuid bytea;
10
+ bytes bytea;
11
+ type bytea;
10
12
  BEGIN
11
- bytes := set_byte(bytes, 6, (get_byte(bytes, 4) # get_byte(bytes, 8)) # get_byte(t, 0));
12
- bytes := set_byte(bytes, 7, (get_byte(bytes, 5) # get_byte(bytes, 9)) # get_byte(t, 1));
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
+
13
26
  RETURN encode( bytes, 'hex') :: uuid;
14
27
  END;
15
- $$ LANGUAGE plpgsql;
16
- SQL
28
+ $$ LANGUAGE plpgsql;
29
+ SQL
30
+ end
17
31
  end
18
32
  end
data/lib/typed_uuid.rb CHANGED
@@ -3,16 +3,50 @@ module TypedUUID
3
3
  autoload :PsqlColumnMethods, 'typed_uuid/psql_column_methods'
4
4
  autoload :PsqlSchemaDumper, 'typed_uuid/psql_schema_dumper'
5
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
6
+ def self.uuid(enum, version = 4, **options)
7
+ if enum < 0 || enum > 8_191
8
+ raise ArgumentError, "UUID type must be between 0 and 8,191"
9
+ end
10
+
11
+ if version == 1
12
+ timestamp_uuid(enum, **options)
13
+ elsif version == 4
14
+ random_uuid(enum, **options)
15
+ end
16
+ end
17
+
18
+ def self.random_uuid(enum)
19
+ uuid = SecureRandom.random_bytes(16).unpack("nnnnnnnn")
20
+
21
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 4)
22
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
23
+ end
24
+
25
+ def self.timestamp_uuid(enum, timestamp: nil, sequence: nil)
26
+ timestamp ||= Time.now
27
+
28
+ uuid = [timestamp.to_i * 1_000_000 + timestamp.usec].pack('Q>')[1..-1]
29
+ uuid << (sequence&.pack('Q>') || SecureRandom.random_bytes(10))
30
+
31
+ uuid = uuid.unpack("nnnnnnnn")
32
+ uuid[7] = (uuid[2] ^ uuid[6]) ^ ((enum << 3) | 1)
33
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
10
34
  end
11
35
 
12
36
  def self.enum(uuid)
13
37
  uuid = uuid.gsub('-', '')
14
- (uuid[8..11].to_i(16) ^ uuid[16..19].to_i(16)) ^ uuid[12..15].to_i(16)
38
+ ((uuid[8..11].to_i(16) ^ uuid[24..27].to_i(16)) ^ uuid[28..31].to_i(16)) >> 3
39
+ end
40
+
41
+ def self.version(uuid)
42
+ uuid = uuid.gsub('-', '')
43
+ ((uuid[8..11].to_i(16) ^ uuid[24..27].to_i(16)) ^ uuid[28..31].to_i(16)) & 0b0000000000000111
44
+ end
45
+
46
+ def self.timestamp(uuid)
47
+ uuid = uuid.gsub('-', '')
48
+ Time.at(*uuid[0..13].to_i(16).divmod(1_000_000), :usec)
15
49
  end
16
50
  end
17
51
 
18
- require 'typed_uuid/railtie' if defined? Rails
52
+ require 'typed_uuid/railtie' if defined? Rails
@@ -1,79 +1,132 @@
1
+ require 'active_support/concern'
2
+
1
3
  module TypedUUID::ActiveRecord
4
+ extend ActiveSupport::Concern
2
5
 
3
6
  UUID_TYPE_CONFLICT_MESSAGE = \
4
- "You tried to define an UUID type %{int} for \"%{table}\", but " \
7
+ "You tried to define an UUID type %{int} for \"%{class_name}\", but " \
5
8
  " %{int} is already defined as the type for %{other}"
6
-
7
- def self.extended(base) # :nodoc:
8
- base.class_attribute(:defined_uuid_types, instance_writer: false, default: {})
9
- base.class_attribute(:uuid_type_cache, instance_writer: false, default: {})
9
+
10
+ included do
11
+ class_attribute(:defined_uuid_types, instance_writer: false, default: {})
12
+ class_attribute(:class_to_uuid_type_cache, instance_writer: false, default: Hash.new { |hash, klass|
13
+ if type = defined_uuid_types.find { |k, v| v[:class] == klass.name }
14
+ hash[klass] = type[1]
15
+ end
16
+ })
17
+ class_attribute(:uuid_enum_to_class_cache, instance_writer: false, default: {})
10
18
  end
11
19
 
12
- def register_uuid_type(table, int)
13
- if int < 0 || int > 65_535
14
- raise ArgumentError, "UUID type must be between 0 and 65,535"
15
- elsif defined_uuid_types.has_key?(int)
16
- raise ArgumentError, UUID_TYPE_CONFLICT_MESSAGE % {
17
- int: int,
18
- table: table,
19
- other: defined_uuid_types[int]
20
- }
21
- else
22
- defined_uuid_types[int] = table.to_s
20
+ def _create_record
21
+ klass = self.class
22
+ if !klass.descends_from_active_record? && klass.typed?
23
+ pk = klass.primary_key
24
+ write_attribute(pk, klass.typed_uuid) if pk && read_attribute(pk).nil?
23
25
  end
26
+
27
+ super
24
28
  end
25
29
 
26
- def register_uuid_types(mapping)
27
- mapping.each do |k, v|
28
- if k.is_a?(Integer)
29
- register_uuid_type(v, k)
30
+ class_methods do
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
30
35
  else
31
- register_uuid_type(k, v)
36
+ enum = enum_or_options
37
+ version = 4
38
+ end
39
+
40
+ if enum < 0 || enum > 8_191
41
+ raise ArgumentError, "UUID type must be between 0 and 65,535"
42
+ elsif defined_uuid_types.has_key?(enum)
43
+ raise ArgumentError, UUID_TYPE_CONFLICT_MESSAGE % {
44
+ int: enum,
45
+ class_name: class_name,
46
+ other: defined_uuid_types[enum]
47
+ }
48
+ else
49
+ defined_uuid_types[enum] = { class: class_name.to_s, version: version, enum: enum }
32
50
  end
33
51
  end
34
- end
35
-
36
- def typed_uuid
37
- TypedUUID.uuid(uuid_type_from_class(self))
38
- end
39
52
 
40
- def uuid_type_from_table_name(table)
41
- type = defined_uuid_types.key(table.to_s)
42
- if type.nil?
43
- raise ArgumentError, "UUID Type for \"#{table}\" not defined"
53
+ def register_uuid_types(mapping)
54
+ mapping.each do |k, v|
55
+ if k.is_a?(Integer)
56
+ register_uuid_type(v, k)
57
+ else
58
+ register_uuid_type(k, v)
59
+ end
60
+ end
44
61
  end
45
-
46
- type
47
- end
48
62
 
49
- def uuid_type_from_class(klass)
50
- type = defined_uuid_types.key(klass.table_name)
51
- if type.nil?
52
- raise ArgumentError, "UUID Type for \"#{table}\" not defined"
63
+ def typed?
64
+ !!class_to_uuid_type_cache[self.base_class]
53
65
  end
54
66
 
55
- type
56
- end
67
+ def typed_uuid(**options)
68
+ TypedUUID.uuid(uuid_enum_from_class(self), uuid_version_from_class(self), **options)
69
+ end
70
+
71
+ def uuid_enum_from_table_name(table)
72
+ uuid_enum_from_class(class_from_table_name(table))
73
+ end
74
+
75
+ def uuid_version_from_table_name(table)
76
+ uuid_version_from_class(class_from_table_name(table))
77
+ end
57
78
 
58
- def class_from_uuid_type(type)
59
- if klass = uuid_type_cache[type]
60
- return klass
61
- else
62
- Rails.application.eager_load! if !Rails.application.config.eager_load
79
+ def uuid_enum_from_class(klass)
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
87
+
88
+ def uuid_version_from_class(klass)
89
+ type = class_to_uuid_type_cache[klass]
90
+ if type.nil?
91
+ raise ArgumentError, "UUID Type for \"#{klass.name}\" not defined"
92
+ end
63
93
 
64
- ::ActiveRecord::Base.descendants.select do |klass|
94
+ type[:version]
95
+ end
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]
100
+ else
101
+ Rails.application.eager_load! if !Rails.application.config.eager_load
102
+
103
+ ::ActiveRecord::Base.descendants.each do |klass|
104
+ next if klass.table_name.nil?
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
109
+ end
110
+
111
+ uuid_enum_to_class_cache[enum]
112
+ end
113
+ end
114
+
115
+ def class_from_table_name(table)
116
+ table = table.to_s
117
+ Rails.application.eager_load! if !Rails.application.config.eager_load
118
+
119
+ ::ActiveRecord::Base.descendants.find do |klass|
65
120
  next unless ( klass.superclass == ::ActiveRecord::Base || klass.superclass.abstract_class? )
66
121
  next if klass.table_name.nil?
67
-
68
- uuid_type_cache[defined_uuid_types.key(klass.table_name)] = klass
122
+
123
+ klass.table_name == table
69
124
  end
70
-
71
- uuid_type_cache[type]
72
125
  end
73
- end
74
126
 
75
- def class_from_uuid(uuid)
76
- class_from_uuid_type(TypedUUID.enum(uuid))
127
+ def class_from_uuid(uuid)
128
+ class_from_uuid_enum(TypedUUID.enum(uuid))
129
+ end
77
130
  end
78
131
 
79
132
  end
@@ -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
 
@@ -4,7 +4,7 @@ class TypedUUID::Railtie < Rails::Railtie
4
4
  ActiveRecord::Tasks::DatabaseTasks.migrations_paths << File.expand_path('../../../db/migrate', __FILE__)
5
5
 
6
6
  ActiveSupport.on_load(:active_record) do
7
- ActiveRecord::Base.extend TypedUUID::ActiveRecord
7
+ ActiveRecord::Base.include TypedUUID::ActiveRecord
8
8
  end
9
9
 
10
10
  require 'active_record/connection_adapters/postgresql/schema_definitions'
@@ -1,3 +1,3 @@
1
1
  module TypedUUID
2
- VERSION = '2.3'
2
+ VERSION = '4.0.rc2'
3
3
  end
data/test/test_helper.rb CHANGED
@@ -28,7 +28,9 @@ module ActiveRecord::Tasks::DatabaseTasks
28
28
  end
29
29
  TypedUUID::Railtie.initializers.each(&:run)
30
30
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
31
-
31
+ def MiniTest.filter_backtrace(bt)
32
+ bt
33
+ end
32
34
  class ActiveSupport::TestCase
33
35
 
34
36
  # File 'lib/active_support/testing/declarative.rb'
@@ -52,6 +54,10 @@ class ActiveSupport::TestCase
52
54
  set_callback(:setup, :before) do
53
55
  Rails.stubs(:application).returns(stub(config: stub(eager_load: true)))
54
56
  if !self.class.class_variable_defined?(:@@suite_setup_run)
57
+ ActiveRecord::Base.defined_uuid_types.clear
58
+ ActiveRecord::Base.uuid_enum_to_class_cache.clear
59
+ ActiveRecord::Base.class_to_uuid_type_cache.clear
60
+
55
61
  configuration = {
56
62
  adapter: "postgresql",
57
63
  database: "uuid-types-test",
@@ -76,14 +82,14 @@ class ActiveSupport::TestCase
76
82
  self.class.class_variable_set(:@@suite_setup_run, true)
77
83
  end
78
84
 
79
- # def debug
80
- # ActiveRecord::Base.logger = Logger.new(STDOUT)
81
- # $debugging = true
82
- # yield
83
- # ensure
84
- # ActiveRecord::Base.logger = nil
85
- # $debugging = false
86
- # end
85
+ def debug
86
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
87
+ $debugging = true
88
+ yield
89
+ ensure
90
+ ActiveRecord::Base.logger = nil
91
+ $debugging = false
92
+ end
87
93
 
88
94
  def capture_sql
89
95
  # ActiveRecord::Base.connection.materialize_transactions
@@ -4,8 +4,9 @@ class FilterTest < ActiveSupport::TestCase
4
4
 
5
5
  schema do
6
6
  ActiveRecord::Base.register_uuid_types({
7
- listings: 0,
8
- buildings: 592
7
+ 'FilterTest::Listing' => {enum: 0, version: 1},
8
+ 'FilterTest::Building' => 592,
9
+ 'FilterTest::SkyScraper' => {enum: 1_952}
9
10
  })
10
11
 
11
12
  create_table :listings, id: :typed_uuid do |t|
@@ -14,6 +15,7 @@ class FilterTest < ActiveSupport::TestCase
14
15
 
15
16
  create_table :buildings, id: :typed_uuid do |t|
16
17
  t.string "name", limit: 255
18
+ t.string "type", limit: 255
17
19
  end
18
20
  end
19
21
 
@@ -22,14 +24,26 @@ class FilterTest < ActiveSupport::TestCase
22
24
 
23
25
  class Building < ActiveRecord::Base
24
26
  end
27
+
28
+ class SkyScraper < Building
29
+ end
25
30
 
31
+ class SingleFamilyHome < Building
32
+ end
33
+
34
+ class Property < ActiveRecord::Base
35
+ end
36
+
37
+ class Lot < ActiveRecord::Base
38
+ end
39
+
26
40
  test 'adding primary key as a typed_uuid in a migration' do
27
41
  ActiveRecord::Base.register_uuid_types({
28
- properties: 1
42
+ 1 => 'FilterTest::Property'
29
43
  })
30
44
 
31
45
  exprexted_sql = <<-SQL
32
- 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))
33
47
  SQL
34
48
 
35
49
  assert_sql exprexted_sql do
@@ -41,29 +55,100 @@ class FilterTest < ActiveSupport::TestCase
41
55
  end
42
56
  end
43
57
 
58
+ test 'adding primary key as a typed_uuid in a migration with a version' do
59
+ ActiveRecord::Base.register_uuid_types({
60
+ 'FilterTest::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
+
44
76
  test 'typed_uuid' do
45
77
  assert_equal 512, TypedUUID.enum(TypedUUID.uuid(512))
46
- assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
47
- assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid(Building.typed_uuid)
78
+ assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
79
+ assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid(Building.typed_uuid)
80
+ assert_equal FilterTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid(SkyScraper.typed_uuid)
81
+
82
+ assert_raises ArgumentError do
83
+ ::ActiveRecord::Base.class_from_uuid(SingleFamilyHome.typed_uuid)
84
+ end
48
85
  end
49
86
 
50
- test 'class_from uuid' do
87
+ test 'typed_uuid v1' do
88
+ time = Time.at(1606612254, 370979, :usec)
89
+ Time.stubs(:now).returns(time)
90
+
91
+ uuid = TypedUUID.uuid(592, 1)
92
+
93
+ assert_equal 1, TypedUUID.version(uuid)
94
+ assert_equal 592, TypedUUID.enum(uuid)
95
+
96
+ assert_equal time, TypedUUID.timestamp(uuid)
97
+ assert_equal time.to_i, TypedUUID.timestamp(uuid).to_i
98
+ assert_equal time.nsec, TypedUUID.timestamp(uuid).nsec
99
+
100
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
101
+ end
102
+
103
+ test 'class_from uuid generated bye PostgresQL' do
51
104
  listing = Listing.create
52
105
  building = Building.create
106
+ skyscraper = SkyScraper.create
53
107
 
54
108
  assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid(listing.id)
55
109
  assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid(building.id)
110
+ assert_equal FilterTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid(skyscraper.id)
111
+
112
+ assert_raises ArgumentError do
113
+ SingleFamilyHome.create
114
+ end
56
115
  end
57
116
 
58
- test 'uuid_type from table_name' do
59
- assert_equal 0, ::ActiveRecord::Base.uuid_type_from_table_name(:listings)
60
- assert_equal 0, ::ActiveRecord::Base.uuid_type_from_table_name('listings')
61
- assert_equal 592, ::ActiveRecord::Base.uuid_type_from_table_name(:buildings)
117
+ test 'class_from v1 uuid generated bye PostgresQL' do
118
+ listing = Listing.create
119
+
120
+ assert_equal Listing, ::ActiveRecord::Base.class_from_uuid(listing.id)
121
+ end
122
+
123
+ test 'uuid_enum from table_name' do
124
+ assert_equal 0, ::ActiveRecord::Base.uuid_enum_from_table_name(:listings)
125
+ assert_equal 0, ::ActiveRecord::Base.uuid_enum_from_table_name('listings')
126
+
127
+ assert_equal 592, ::ActiveRecord::Base.uuid_enum_from_table_name(:buildings)
128
+ end
129
+
130
+ test 'uuid_version from table_name' do
131
+ assert_equal 1, ::ActiveRecord::Base.uuid_version_from_table_name(:listings)
132
+ assert_equal 1, ::ActiveRecord::Base.uuid_version_from_table_name('listings')
133
+ assert_equal 4, ::ActiveRecord::Base.uuid_version_from_table_name(:buildings)
134
+ end
135
+
136
+ test 'uuid_enum from class' do
137
+ assert_equal 0, ::ActiveRecord::Base.uuid_enum_from_class(Listing)
138
+ assert_equal 592, ::ActiveRecord::Base.uuid_enum_from_class(Building)
139
+ assert_equal 1_952, ::ActiveRecord::Base.uuid_enum_from_class(SkyScraper)
140
+ end
141
+
142
+ test 'uuid_version from class' do
143
+ assert_equal 1, ::ActiveRecord::Base.uuid_version_from_class(Listing)
144
+ assert_equal 4, ::ActiveRecord::Base.uuid_version_from_class(Building)
145
+ assert_equal 4, ::ActiveRecord::Base.uuid_version_from_class(SkyScraper)
62
146
  end
63
147
 
64
- test 'class from uuid_type' do
65
- assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid_type(0)
66
- assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid_type(592)
148
+ test 'class from uuid_enum' do
149
+ assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid_enum(0)
150
+ assert_equal FilterTest::Building, ::ActiveRecord::Base.class_from_uuid_enum(592)
151
+ assert_equal FilterTest::SkyScraper, ::ActiveRecord::Base.class_from_uuid_enum(1_952)
67
152
  end
68
153
 
69
154
  end
data/typed_uuid.gemspec CHANGED
@@ -24,8 +24,8 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency 'byebug'
25
25
  s.add_development_dependency 'activesupport', '>= 6.0.0'
26
26
  s.add_development_dependency 'rails', '>= 6.0.0'
27
+ s.add_development_dependency 'pg'
27
28
 
28
29
  # Runtime
29
- s.add_runtime_dependency 'pg'
30
30
  s.add_runtime_dependency 'activerecord', '>= 6.0.0'
31
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: '2.3'
4
+ version: 4.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-25 00:00:00.000000000 Z
11
+ date: 2021-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -143,7 +143,7 @@ dependencies:
143
143
  - - ">="
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
- type: :runtime
146
+ type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
@@ -190,7 +190,7 @@ files:
190
190
  homepage: https://github.com/malomalo/typed_uuid
191
191
  licenses: []
192
192
  metadata: {}
193
- post_install_message:
193
+ post_install_message:
194
194
  rdoc_options: []
195
195
  require_paths:
196
196
  - lib
@@ -201,12 +201,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
201
201
  version: '0'
202
202
  required_rubygems_version: !ruby/object:Gem::Requirement
203
203
  requirements:
204
- - - ">="
204
+ - - ">"
205
205
  - !ruby/object:Gem::Version
206
- version: '0'
206
+ version: 1.3.1
207
207
  requirements: []
208
- rubygems_version: 3.0.3
209
- signing_key:
208
+ rubygems_version: 3.2.3
209
+ signing_key:
210
210
  specification_version: 4
211
211
  summary: Typed UUIDs for ActiveRecord
212
212
  test_files: