typed_uuid 3.2 → 4.0.rc1

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: 956d3f40e94a40968f9e66ae955a0eb4ca0a0d5e7c0a307e846a6bc329970108
4
+ data.tar.gz: 49844d2082da751bc4d36dede69bdd927bb93269a8caadcaee20916a66e56c4f
5
5
  SHA512:
6
- metadata.gz: 204697bd0dc2c7dd64b62dc7b7955cb5d83242c5966bc447c84d79d343f3b0825cc8bd1bb450525a3acc3405a61a1740b06f127a9df6f5f5830f9a063a75f18a
7
- data.tar.gz: cba9d2425930e678e4c44bfc163392fb749c249f5e2930b62998f22fae5ad10cdaa9e8df5df013b6155eac8c1c242056e4ff67b65dfb63140f82f545b24d38d4
6
+ metadata.gz: 5e4792f1bf817f6d3edf9b958ce843ddbda5d9fbb1286629a4cd91acdc56581d239e167b1808d580d5139233c1a74cc0aae3f88adc5585542413196735f6c7ea
7
+ data.tar.gz: 143e55145e97eb7dd177d2d2785c03f8267cdad624a40cd52ce628c66194ae155c6d852b564eecdf991399e62c05ae1b48837698f362239aff6e2774a5b9ef1c
data/README.md CHANGED
@@ -7,24 +7,35 @@ 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 9th & 10th bytes in the
14
+ UUID are the enum XORed with the result of XORing bytes 7 & 8 with bytes 11 & 12.
17
15
 
18
- `xxxxxxxx-YYYY-TTTT-ZZZZ-xxxxxxxxxxxx`
16
+ `xxxxxxxx-xxxx-YYYY-TTTT-ZZZZxxxxxxxx`
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 7 & 8 with 11 & 12 and XORing again with bytes 9 & 10 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 64 bits are an unsigned integer
34
+ representing the nanoseconds since epoch. The following 16 bits are
35
+ the UUID type folled by 48 random bits.
36
+
37
+ - Version 4: A random UUID where the first 64 bits are random. The following
38
+ 16 bits are the UUID type folled by another 48 random bits.
28
39
 
29
40
  ## Install
30
41
 
@@ -40,8 +51,9 @@ below. This maps the __Model Classes__ to an integer between 0 and 65,535.
40
51
 
41
52
  ActiveRecord::Base.register_uuid_types({
42
53
  Listing: 0,
43
- Building: 512,
44
- 'Building::SkyScrpaer' => 65_535
54
+ Address: {enum: 5},
55
+ Building: {enum: 512, version: 1},
56
+ 'Building::SkyScrpaer' => 8_191
45
57
  })
46
58
 
47
59
  # Or:
@@ -49,7 +61,7 @@ ActiveRecord::Base.register_uuid_types({
49
61
  ActiveRecord::Base.register_uuid_types({
50
62
  0 => :Listing,
51
63
  512 => :Building,
52
- 65_535 => 'Building::SkyScrpaer'
64
+ 8_191 => 'Building::SkyScrpaer'
53
65
  })
54
66
  ```
55
67
 
@@ -4,15 +4,28 @@ 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
+ to_hex((extract(epoch from clock_timestamp())*1000000000)::bigint),
16
+ encode(gen_random_bytes(8), 'hex')
17
+ ), 'hex');
18
+ ELSE
19
+ bytes := gen_random_bytes(16);
20
+ version := 4;
21
+ END IF;
22
+
23
+ type := decode( lpad(to_hex(((enum << 3) | version)), 4, '0'), 'hex');
24
+ bytes := set_byte(bytes, 8, (get_byte(bytes, 6) # get_byte(bytes, 10)) # get_byte(type, 0));
25
+ bytes := set_byte(bytes, 9, (get_byte(bytes, 7) # get_byte(bytes, 11)) # get_byte(type, 1));
26
+
27
+ RETURN encode( bytes, 'hex') :: uuid;
28
+ END;
16
29
  $$ LANGUAGE plpgsql;
17
30
  SQL
18
31
  end
@@ -3,15 +3,48 @@ 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)
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)
13
+ elsif version == 4
14
+ random_uuid(enum)
15
+ end
16
+ end
17
+
18
+ def self.random_uuid(enum)
19
+ uuid = SecureRandom.random_bytes(16).unpack("nnnnnnnn")
20
+
21
+ uuid[4] = (uuid[3] ^ uuid[5]) ^ ((enum << 3) | 4)
22
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
23
+ end
24
+
25
+ def self.timestamp_uuid(enum)
26
+ time = Time.now
27
+ uuid = [time.to_i * 1_000_000_000 + time.nsec].pack('Q>')
28
+ uuid << SecureRandom.random_bytes(8)
29
+
30
+ uuid = uuid.unpack("nnnnnnnn")
31
+ uuid[4] = (uuid[3] ^ uuid[5]) ^ ((enum << 3) | 1)
32
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % uuid
10
33
  end
11
34
 
12
35
  def self.enum(uuid)
13
36
  uuid = uuid.gsub('-', '')
14
- (uuid[8..11].to_i(16) ^ uuid[16..19].to_i(16)) ^ uuid[12..15].to_i(16)
37
+ ((uuid[12..15].to_i(16) ^ uuid[20..23].to_i(16)) ^ uuid[16..19].to_i(16)) >> 3
38
+ end
39
+
40
+ def self.version(uuid)
41
+ uuid = uuid.gsub('-', '')
42
+ ((uuid[12..15].to_i(16) ^ uuid[20..23].to_i(16)) ^ uuid[16..19].to_i(16)) & 0b0000000000000111
43
+ end
44
+
45
+ def self.timestamp(uuid)
46
+ uuid = uuid.gsub('-', '')
47
+ Time.at(*uuid[0..15].to_i(16).divmod(1_000_000_000), :nsec)
15
48
  end
16
49
  end
17
50
 
@@ -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
 
@@ -55,36 +65,50 @@ module TypedUUID::ActiveRecord
55
65
  end
56
66
 
57
67
  def typed_uuid
58
- TypedUUID.uuid(uuid_type_from_class(self))
68
+ TypedUUID.uuid(uuid_enum_from_class(self), uuid_version_from_class(self))
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
@@ -1,3 +1,3 @@
1
1
  module TypedUUID
2
- VERSION = '3.2'
2
+ VERSION = '4.0.rc1'
3
3
  end
@@ -28,9 +28,7 @@ module ActiveRecord::Tasks::DatabaseTasks
28
28
  end
29
29
  TypedUUID::Railtie.initializers.each(&:run)
30
30
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
31
- def MiniTest.filter_backtrace(bt)
32
- bt
33
- end
31
+
34
32
  class ActiveSupport::TestCase
35
33
 
36
34
  # File 'lib/active_support/testing/declarative.rb'
@@ -55,7 +53,7 @@ class ActiveSupport::TestCase
55
53
  Rails.stubs(:application).returns(stub(config: stub(eager_load: true)))
56
54
  if !self.class.class_variable_defined?(:@@suite_setup_run)
57
55
  ActiveRecord::Base.defined_uuid_types.clear
58
- ActiveRecord::Base.uuid_type_to_class_cache.clear
56
+ ActiveRecord::Base.uuid_enum_to_class_cache.clear
59
57
  ActiveRecord::Base.class_to_uuid_type_cache.clear
60
58
 
61
59
  configuration = {
@@ -4,9 +4,9 @@ class FilterTest < ActiveSupport::TestCase
4
4
 
5
5
  schema do
6
6
  ActiveRecord::Base.register_uuid_types({
7
- 'FilterTest::Listing' => 0,
7
+ 'FilterTest::Listing' => {enum: 0, version: 1},
8
8
  'FilterTest::Building' => 592,
9
- 'FilterTest::SkyScraper' => 1_952
9
+ 'FilterTest::SkyScraper' => {enum: 1_952}
10
10
  })
11
11
 
12
12
  create_table :listings, id: :typed_uuid do |t|
@@ -34,13 +34,16 @@ class FilterTest < ActiveSupport::TestCase
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
42
  1 => 'FilterTest::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,6 +55,24 @@ 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
+ '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
+
55
76
  test 'typed_uuid' do
56
77
  assert_equal 512, TypedUUID.enum(TypedUUID.uuid(512))
57
78
  assert_equal FilterTest::Listing, ::ActiveRecord::Base.class_from_uuid(Listing.typed_uuid)
@@ -63,7 +84,23 @@ class FilterTest < ActiveSupport::TestCase
63
84
  end
64
85
  end
65
86
 
66
- test 'class_from uuid' do
87
+ test 'typed_uuid v1' do
88
+ time = Time.at(1606612254, 370979123, :nsec)
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
67
104
  listing = Listing.create
68
105
  building = Building.create
69
106
  skyscraper = SkyScraper.create
@@ -77,23 +114,41 @@ class FilterTest < ActiveSupport::TestCase
77
114
  end
78
115
  end
79
116
 
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)
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)
84
121
  end
85
122
 
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)
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)
91
146
  end
92
147
 
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)
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)
97
152
  end
98
153
 
99
154
  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.0.rc1
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: 2020-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -201,9 +201,9 @@ 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
208
  rubygems_version: 3.1.4
209
209
  signing_key: