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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b002a4e9136bfe9b886e13b8aaea8962f98b6ccf94e64dc875423c985fab6f8c
4
- data.tar.gz: 7e245dcb3726845ddc8617cb91f2b6e79510baff72348d27124255855d79b3e6
3
+ metadata.gz: 0c9dca219a90d4889fbfe7e3efcfc0654d6b25dfb6b92c3ce26a7c9e0e66db51
4
+ data.tar.gz: bff2e4c5d830371922b696c14675739f985bfe37f1add64a9f8fa28552940ad2
5
5
  SHA512:
6
- metadata.gz: eb7fb1c5a1953d4b6ce56aa3771b18001220bcb383413abf17157d4cd7644cc505ac6c8cefe30902d246cf15b297fffa631db674ef33a07852c9189d3a08ac19
7
- data.tar.gz: d534f95fbbec9934890497da6b2c9b0a4e0d82189b66c8e176a57758d5c9353336147b6136343b1cd8ee18388be655b631d336452e9a0fd5528b8dd9100178df
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 either a String or UUID database column type.
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 #=> "019867fe-560f-7941-a7ed-8472639c7ace"
41
+ my_model.id #=> "mm_01k1kzwngff50tfvc4e9hsrype"
37
42
  my_model.type_id #=> #<TypeID mm_01k1kzwngff50tfvc4e9hsrype>
38
- my_model.type_id.to_s #=> mm_01k1kzwngff50tfvc4e9hsrype
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 :generate_uuid_v7
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.from_uuid(self.class.type_id_prefix, id)
50
+ TypeID.from_string(id)
53
51
  end
54
52
 
55
- # If `id` is unset, generates a new UUID v7 and sets it
53
+ # If `id` is unset, generates a new UUID v7 TypeID and sets the `id` field
56
54
  # @return [void]
57
- define_method :generate_uuid_v7 do
58
- case self.class.attribute_types["id"].type
59
- when :uuid, :string
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
@@ -2,5 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative "version"
5
+ require_relative "helpers"
5
6
  require_relative "concern"
6
7
  require_relative "test_helper"
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RailsTypeId
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.0"
6
6
  end
@@ -34,11 +34,6 @@ module Tapioca
34
34
  "type_id",
35
35
  return_type: "TypeID"
36
36
  )
37
-
38
- methods_mod.create_method(
39
- "to_param",
40
- return_type: "String"
41
- )
42
37
  end
43
38
  klass.create_include(RBI_MODULE_NAME)
44
39
  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.1.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