rails_type_id 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +53 -3
- data/lib/rails_type_id/concern.rb +6 -74
- data/lib/rails_type_id/helpers.rb +71 -0
- data/lib/rails_type_id/require.rb +1 -0
- data/lib/rails_type_id/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/rails_type_id_compiler.rb +0 -5
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c9dca219a90d4889fbfe7e3efcfc0654d6b25dfb6b92c3ce26a7c9e0e66db51
|
4
|
+
data.tar.gz: bff2e4c5d830371922b696c14675739f985bfe37f1add64a9f8fa28552940ad2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a376381db8db1c2a571f4f43248e6da57867890410dee87af7e5bb72ea8caa84b5484a72f3fc51c7e8acd64fe1991d948e3b35c5cab51328e4277a2cf2d82389
|
7
|
+
data.tar.gz: 2f5ab23d07f0c1e1914bf391673519e29f684f6da2894802e039452ac5c7286965407e52a4609d2d4ee0d5e1b5fc58695afe2fe7bad710511c2d2293df19e309
|
data/README.md
CHANGED
@@ -12,8 +12,13 @@ bundle add rails_type_id
|
|
12
12
|
|
13
13
|
## Usage
|
14
14
|
|
15
|
+
### Database requirements
|
16
|
+
|
17
|
+
- ActiveRecord models should have an `id` field that is either a string-like type (`TEXT`, `VARCHAR`) .
|
18
|
+
See [Migration](#migrating-existing-ids) if you have an existing `id` field.
|
19
|
+
|
15
20
|
### Declaring on models
|
16
|
-
Add `RailsTypeId::Concern` to the model. This model's `id` field should have
|
21
|
+
Add `RailsTypeId::Concern` to the model. This model's `id` field should have a string-like database column type.
|
17
22
|
|
18
23
|
```ruby
|
19
24
|
# app/models/my_model.rb
|
@@ -33,9 +38,54 @@ Model instances will have a `type_id` field of type `TypeID`.
|
|
33
38
|
|
34
39
|
```ruby
|
35
40
|
my_model = MyModel.create!(..)
|
36
|
-
my_model.id #=> "
|
41
|
+
my_model.id #=> "mm_01k1kzwngff50tfvc4e9hsrype"
|
37
42
|
my_model.type_id #=> #<TypeID mm_01k1kzwngff50tfvc4e9hsrype>
|
38
|
-
|
43
|
+
```
|
44
|
+
|
45
|
+
### Testing
|
46
|
+
|
47
|
+
`lib/rails_type_id/test_helpers.rb` contains some helper methods for writing wholesome tests.
|
48
|
+
|
49
|
+
### Migrating existing IDs
|
50
|
+
|
51
|
+
For models with an existing Rails `id` field (usually an auto-incrementing integer), you'll need to
|
52
|
+
migrate these to either a string-like column type. Below is an example migration for SQLite
|
53
|
+
using a `text` type for a Users model that has an associated Session.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class MigrateUserToUUID < ActiveRecord::Migration[8.0]
|
57
|
+
def change
|
58
|
+
add_column :users, :uuid, :text, null: true
|
59
|
+
add_column :sessions, :user_uuid, :text, null: true
|
60
|
+
|
61
|
+
Users.find_each do |u|
|
62
|
+
u.update(uuid: RailsTypeId::Concern::Helpers.generate_type_id("user"))
|
63
|
+
end
|
64
|
+
Session.find_each do |s|
|
65
|
+
s.update(user_uuid: s.user.uuid)
|
66
|
+
end
|
67
|
+
change_column_null :users, :uuid, false
|
68
|
+
change_column_null :sessions, :user_uuid, false
|
69
|
+
|
70
|
+
remove_foreign_key :sessions, :users
|
71
|
+
|
72
|
+
rename_column :users, :id, :integer_id
|
73
|
+
rename_column :users, :uuid, :id
|
74
|
+
|
75
|
+
rename_column :sessions, :user_id, :integer_user_id
|
76
|
+
rename_column :sessions, :user_uuid, :user_id
|
77
|
+
change_column_null :session, :integer_user_id, true
|
78
|
+
|
79
|
+
execute "ALTER TABLE users DROP CONSTRAINT users_pkey;"
|
80
|
+
execute "ALTER_TABLE users ADD PRIMARY KEY (id);"
|
81
|
+
|
82
|
+
execute "ALTER TABLE ONLY users ALTER COLUMN integer_id DROP DEFAULT"
|
83
|
+
change_column_null :users, :integer_id, true
|
84
|
+
execute "DROP SEQUENCE IF EXISTS users_id_seq"
|
85
|
+
|
86
|
+
add_foreign_key :sessions, :users
|
87
|
+
end
|
88
|
+
end
|
39
89
|
```
|
40
90
|
|
41
91
|
## Development
|
@@ -13,8 +13,6 @@ module RailsTypeId
|
|
13
13
|
extend ::ActiveSupport::Concern
|
14
14
|
include ::ActiveModel::Validations::Callbacks
|
15
15
|
|
16
|
-
class InvalidTypeIdPrefix < StandardError; end
|
17
|
-
|
18
16
|
# Checks validity of the ActiveRecord model instances
|
19
17
|
class Validator < ActiveModel::Validator
|
20
18
|
def validate(record)
|
@@ -42,87 +40,21 @@ module RailsTypeId
|
|
42
40
|
included do
|
43
41
|
attribute :id # Postgres UUID field
|
44
42
|
|
45
|
-
before_create :
|
43
|
+
before_create :generate_type_id
|
46
44
|
validates_with Validator
|
47
45
|
|
48
46
|
# Returns the TypeID for the model
|
49
47
|
# @return [TypeID]
|
50
48
|
define_method :type_id do
|
51
49
|
Helpers.validate_type_id_prefix!(self.class.type_id_prefix)
|
52
|
-
TypeID.
|
50
|
+
TypeID.from_string(id)
|
53
51
|
end
|
54
52
|
|
55
|
-
# If `id` is unset, generates a new UUID v7 and sets
|
53
|
+
# If `id` is unset, generates a new UUID v7 TypeID and sets the `id` field
|
56
54
|
# @return [void]
|
57
|
-
define_method :
|
58
|
-
|
59
|
-
|
60
|
-
self.id ||= SecureRandom.uuid_v7
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
define_method :to_param do
|
65
|
-
type_id.to_s
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Internal helper methods
|
70
|
-
class Helpers
|
71
|
-
extend T::Sig
|
72
|
-
|
73
|
-
class << self
|
74
|
-
def validate_type_id_prefix(prefix)
|
75
|
-
return "type_id_prefix cannot be nil" if prefix.nil?
|
76
|
-
|
77
|
-
return nil if prefix.match(/\A[a-z]{1,10}\z/)
|
78
|
-
|
79
|
-
"type_id_prefix must be lowercase alphabetic (a-z) with length >= 1, <= 10"
|
80
|
-
end
|
81
|
-
|
82
|
-
def validate_type_id_prefix!(prefix)
|
83
|
-
result = validate_type_id_prefix(prefix)
|
84
|
-
raise InvalidTypeIdPrefix, result if result.present?
|
85
|
-
end
|
86
|
-
|
87
|
-
def lookup_type_id(type_id)
|
88
|
-
klasses = lookup_model(type_id)
|
89
|
-
return if klasses.nil?
|
90
|
-
|
91
|
-
id = type_id # TODO: parse out the uuid part
|
92
|
-
klasses.each do |klass|
|
93
|
-
result = klass.find_by(id: id)
|
94
|
-
return result unless result.nil?
|
95
|
-
end
|
96
|
-
|
97
|
-
nil
|
98
|
-
end
|
99
|
-
|
100
|
-
def lookup_model(type_id)
|
101
|
-
prefix = get_prefix(type_id)
|
102
|
-
return if prefix.nil?
|
103
|
-
|
104
|
-
prefix_map[prefix]
|
105
|
-
end
|
106
|
-
|
107
|
-
def get_prefix(id)
|
108
|
-
prefix, = parse_id(id)
|
109
|
-
prefix
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def parse_id(id)
|
115
|
-
id.split("_")
|
116
|
-
end
|
117
|
-
|
118
|
-
def prefix_map
|
119
|
-
Rails.application.eager_load!
|
120
|
-
# This has to be group_by and not index_by because we can have multiple
|
121
|
-
# models that use the same prefix (like Settings)
|
122
|
-
@prefix_map ||= ActiveRecord::Base.descendants
|
123
|
-
.select { |klass| klass.respond_to?(:type_id_prefix) }
|
124
|
-
.group_by(&:type_id_prefix)
|
125
|
-
end
|
55
|
+
define_method :generate_type_id do
|
56
|
+
Helpers.validate_type_id_prefix!(self.class.type_id_prefix)
|
57
|
+
self.id ||= Helpers.generate_type_id(self.class.type_id_prefix).to_s
|
126
58
|
end
|
127
59
|
end
|
128
60
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
|
6
|
+
module RailsTypeId
|
7
|
+
class InvalidTypeIdPrefix < StandardError; end
|
8
|
+
|
9
|
+
# Helper methods for interacting with type IDs
|
10
|
+
class Helpers
|
11
|
+
class << self
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(prefix: String).returns(TypeID) }
|
15
|
+
def generate_type_id(prefix)
|
16
|
+
TypeID.from_uuid(prefix, SecureRandom.uuid_v7)
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { params(prefix: T.nilable(String)).returns(T.nilable(String)) }
|
20
|
+
def validate_type_id_prefix(prefix)
|
21
|
+
return "type_id_prefix cannot be nil" if prefix.nil?
|
22
|
+
|
23
|
+
return nil if prefix.match(/\A[a-z]{1,10}\z/)
|
24
|
+
|
25
|
+
"type_id_prefix must be lowercase alphabetic (a-z) with length >= 1, <= 10"
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(prefix: T.nilable(String)).void }
|
29
|
+
def validate_type_id_prefix!(prefix)
|
30
|
+
result = validate_type_id_prefix(prefix)
|
31
|
+
raise InvalidTypeIdPrefix, result if result.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { params(type_id: String).returns(T.nilable(ActiveRecord::Base)) }
|
35
|
+
def lookup_type_id(type_id)
|
36
|
+
klasses = lookup_model(type_id)
|
37
|
+
return if klasses.nil?
|
38
|
+
|
39
|
+
klasses.each do |klass|
|
40
|
+
result = klass.find_by(id: type_id)
|
41
|
+
return result unless result.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(type_id: String).returns(T.nilable(T::Array[T.untyped])) }
|
48
|
+
def lookup_model(type_id)
|
49
|
+
prefix = TypeID.from_string(type_id).prefix
|
50
|
+
return if prefix.nil?
|
51
|
+
|
52
|
+
prefix_map[prefix]
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
sig { returns(T::Hash[String, T::Array[T.untyped]]) }
|
58
|
+
def prefix_map
|
59
|
+
Rails.application.eager_load!
|
60
|
+
# This has to be group_by and not index_by because we can have multiple
|
61
|
+
# models that use the same prefix (like Settings)
|
62
|
+
@prefix_map ||= T.let(
|
63
|
+
ActiveRecord::Base.descendants
|
64
|
+
.select { |klass| klass.respond_to?(:type_id_prefix) }
|
65
|
+
.group_by(&:type_id_prefix),
|
66
|
+
T.nilable(T::Hash[String, T::Array[T.untyped]])
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_type_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Franklin Hu
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- Rakefile
|
67
67
|
- lib/rails_type_id.rb
|
68
68
|
- lib/rails_type_id/concern.rb
|
69
|
+
- lib/rails_type_id/helpers.rb
|
69
70
|
- lib/rails_type_id/require.rb
|
70
71
|
- lib/rails_type_id/test_helper.rb
|
71
72
|
- lib/rails_type_id/version.rb
|