rails-uuid-pk 0.8.0 → 0.9.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: cda21a747f154d1863e741ecce9b44855009bc202b11524be1a48911c6945ee2
4
- data.tar.gz: f513b130b54f7d2230a13aa53bd60b707174005eff1bb792c0380a9d2b80a885
3
+ metadata.gz: 712bd0217dd06a7b87d855696311146bc08bd1ee9f45b52ce1ece11a251a284c
4
+ data.tar.gz: 8d702575c6177fe8074ea8b88bb650b72d785e83645b02a0a41ace3ea048cc76
5
5
  SHA512:
6
- metadata.gz: 44070a3cf56eca32a1e83abdb83fa084abcb8c6042d2a2e06f3314bcfbf9df4e12a2794a5fab1e7d50d589455f2242dbf7e141618cd2b2b721b76be92ddfdc0f
7
- data.tar.gz: 04ca3df41f44ca36bba89849a5c2a515481417b28b995670ecb93fb7a87a90abe5d9bf74bdb1b3a31c109a28127df2b9478bd63adbfa83fb5d0011e0b334a872
6
+ metadata.gz: 78d45ed25bc2ab1cedc26d2bb64df821ddc1f4e99bf06cc65d3ac595d87a9bad365e458de8ef8c280ee4518cc96d0c5373dc3e569d364f30f2ad0b700cffc029
7
+ data.tar.gz: d1230736212301ebf5b6d9816fa3c1cd05c92c095290153437cffb9db3ef6340d040553736c370a895aa4ab99c472e0f09503fa6f4e1347b98a442274bbbed12
data/CHANGELOG.md CHANGED
@@ -5,6 +5,58 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v1.0.0.html).
7
7
 
8
+ ## [0.9.0] - 2026-01-14
9
+
10
+ ### Added
11
+ - **Logging and Observability Framework**: Added comprehensive logging infrastructure for production debugging and monitoring
12
+ - `RailsUuidPk.logger` and `RailsUuidPk.log` methods with Rails logger integration
13
+ - Debug logging for UUID assignment tracking in models
14
+ - Debug logging for foreign key type detection in migrations
15
+ - Debug logging for database adapter UUID type registration
16
+ - Production-ready logging with configurable log levels
17
+ - **Comprehensive CI/CD Pipeline**: Enterprise-grade continuous integration with security scanning, multi-version testing, performance monitoring, and automated quality assurance
18
+ - Dependency vulnerability scanning with `bundler-audit`
19
+ - Code coverage reporting with `simplecov` and Codecov integration
20
+ - Multi-Ruby testing (3.3, 3.4, 4.0) across PostgreSQL 18, MySQL 9, and SQLite
21
+ - Performance benchmarking with automated `bin/benchmark` script
22
+ - Job dependencies for efficient CI execution and faster failure detection
23
+ - **Enhanced Database Support**: Updated to latest database versions (PostgreSQL 18, MySQL 9) for optimal performance and compatibility
24
+ - **Improved Executable Scripts**: Professional-grade command-line tools with comprehensive error handling, progress feedback, and documentation
25
+ - `bin/coverage`: Enhanced with proper error handling and user feedback
26
+ - `bin/benchmark`: Renamed from `bin/performance` following standard naming conventions
27
+ - **Ruby 4.0 Compatibility**: Fixed all compatibility issues including benchmark warnings and dependency management
28
+
29
+ ### Improved
30
+ - **Code Quality**: Replaced `puts` statements with proper logging infrastructure
31
+ - **Debugging Support**: Enhanced observability for troubleshooting production issues
32
+ - **Log Aggregation**: Compatible with existing Rails logging and monitoring systems (Datadog, CloudWatch, etc.)
33
+
34
+ ### Documentation
35
+ - **Bulk Operations Awareness**: Added comprehensive documentation about bulk operations limitation across README.md, ARCHITECTURE.md, PERFORMANCE.md, and AGENTS.md, clarifying that `Model.import` and `Model.insert_all` bypass callbacks and require explicit UUID assignment
36
+ - **YARD API Documentation**: Added comprehensive YARD documentation to all core library files, achieving 100% documentation coverage with detailed method descriptions, parameter specifications, usage examples, and cross-references for improved developer experience
37
+
38
+ ### Changed
39
+ - **Dependency Management**: Moved CI and development tools to `gemspec` for consistency and proper gem packaging
40
+ - **Script Naming**: Renamed `bin/performance` to `bin/benchmark` following industry standards
41
+ - **CI Workflow**: Comprehensive pipeline with security, testing, coverage, and performance validation
42
+ - **Database Versions**: Updated to PostgreSQL 18 and MySQL 9 for cutting-edge support
43
+
44
+ ### Security
45
+ - **Enhanced Timestamp Privacy Documentation**: Added explicit privacy consideration warning in SECURITY.md about UUIDv7 timestamp exposure, clarifying that UUIDv7 includes a timestamp component that reveals approximate record creation time and advising against use when creation timestamps must be hidden
46
+
47
+ ### Technical Details
48
+ - Added `bundler-audit`, `simplecov`, `benchmark-ips`, and `benchmark` as development dependencies
49
+ - Implemented Ruby 4.0 compatibility fixes for SecureRandom and benchmark libraries
50
+ - Enhanced CI matrix with 9 test combinations across multiple Ruby and database versions
51
+ - Added comprehensive error handling and logging to executable scripts
52
+ - Implemented job dependencies in GitHub Actions for optimal CI performance
53
+ - Added logging framework in `lib/rails_uuid_pk.rb` with Rails logger fallback
54
+ - Implemented debug logging in `concern.rb` for UUIDv7 assignment tracking
55
+ - Added debug logging in `migration_helpers.rb` for foreign key type detection
56
+ - Enhanced adapter extensions with proper logging instead of console output
57
+ - All logging uses structured format with `[RailsUuidPk]` prefix for easy filtering
58
+ - Added `yard` as development dependency for automated documentation generation
59
+
8
60
  ## [0.8.0] - 2026-01-12
9
61
 
10
62
  ### Changed
data/README.md CHANGED
@@ -7,6 +7,7 @@ Automatically use UUID v7 for **all primary keys** in Rails applications. Works
7
7
  [![Gem Version](https://img.shields.io/gem/v/rails-uuid-pk.svg?style=flat-square)](https://rubygems.org/gems/rails-uuid-pk)
8
8
  [![Ruby](https://img.shields.io/badge/ruby-≥3.3-red.svg?style=flat-square)](https://www.ruby-lang.org)
9
9
  [![Rails](https://img.shields.io/badge/rails-≥8.1-9650f9.svg?style=flat-square)](https://rubyonrails.org)
10
+ [![CI](https://img.shields.io/github/actions/workflow/status/seouri/rails-uuid-pk/ci.yml?branch=main&style=flat-square)](https://github.com/seouri/rails-uuid-pk/actions)
10
11
 
11
12
  ## Why this gem?
12
13
 
@@ -19,13 +20,14 @@ Automatically use UUID v7 for **all primary keys** in Rails applications. Works
19
20
  - **SQLite**: Uses `VARCHAR(36)` with custom ActiveRecord type handling
20
21
  - Zero database extensions needed
21
22
  - Minimal and maintainable — no monkey-patching hell
23
+ - Production-ready logging for debugging and monitoring
22
24
 
23
25
  ## Installation
24
26
 
25
27
  Add to your `Gemfile`:
26
28
 
27
29
  ```ruby
28
- gem "rails-uuid-pk", "~> 0.8"
30
+ gem "rails-uuid-pk", "~> 0.9"
29
31
  ```
30
32
 
31
33
  Then run:
@@ -108,6 +110,16 @@ end
108
110
 
109
111
  For comprehensive performance analysis, scaling strategies, and optimization guides, see [PERFORMANCE.md](PERFORMANCE.md).
110
112
 
113
+ ## Bulk Operations
114
+
115
+ **Important**: Bulk operations like `Model.import` bypass ActiveRecord callbacks, so UUIDs won't be automatically generated for bulk-inserted records. Use explicit UUID assignment or a custom bulk import method if needed.
116
+
117
+ ```ruby
118
+ # Manual UUID assignment for bulk operations
119
+ users = [{ name: "Alice", id: SecureRandom.uuid_v7 }, { name: "Bob", id: SecureRandom.uuid_v7 }]
120
+ User.insert_all(users) # Bypasses callbacks, requires explicit IDs
121
+ ```
122
+
111
123
  ## Why not use native PostgreSQL `uuidv7()`?
112
124
 
113
125
  While PostgreSQL 18+ has excellent native `uuidv7()` support, the **fallback approach** was chosen for maximum compatibility:
@@ -164,10 +176,25 @@ The project includes a comprehensive test suite that runs against SQLite, Postgr
164
176
  # Run all tests (SQLite + PostgreSQL + MySQL)
165
177
  ./bin/test
166
178
 
179
+ # Run tests with coverage reporting
180
+ ./bin/coverage
181
+
182
+ # Run performance benchmarks
183
+ ./bin/benchmark
184
+
167
185
  # Run tests for specific database
168
186
  DB=sqlite ./bin/test # SQLite only
169
187
  DB=postgres ./bin/test # PostgreSQL only
170
188
  DB=mysql ./bin/test # MySQL only
189
+
190
+ # Run specific test file
191
+ ./bin/test test/uuid/generation_test.rb
192
+
193
+ # Run specific test file with specific database
194
+ DB=sqlite ./bin/test test/uuid/type_test.rb
195
+
196
+ # Run multiple specific test files
197
+ ./bin/test test/uuid/generation_test.rb test/uuid/type_test.rb
171
198
  ```
172
199
 
173
200
  ### Code Quality
@@ -187,7 +214,7 @@ DB=mysql ./bin/test # MySQL only
187
214
  gem build rails_uuid_pk.gemspec
188
215
 
189
216
  # Install locally for testing
190
- gem install rails-uuid-pk-0.1.0.gem
217
+ gem install rails-uuid-pk-0.9.0.gem
191
218
  ```
192
219
 
193
220
  ### Database Setup
@@ -1,4 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
4
+ # Provides automatic UUIDv7 primary key generation for ActiveRecord models.
5
+ #
6
+ # This concern is automatically included in all ActiveRecord::Base models
7
+ # via the Railtie, requiring no manual configuration. It uses a before_create
8
+ # callback to assign UUIDv7 values to the primary key only when the id is nil.
9
+ #
10
+ # @example Automatic UUIDv7 generation
11
+ # class User < ApplicationRecord
12
+ # # Automatically gets UUIDv7 id on create
13
+ # # No additional code needed!
14
+ # end
15
+ #
16
+ # user = User.create
17
+ # user.id # => "018f8c5d-1234-7abc-9def-123456789abc"
18
+ #
19
+ # @example Manual ID assignment (overrides automatic generation)
20
+ # user = User.new(id: "custom-uuid")
21
+ # user.save # Uses the custom UUID, not auto-generated
22
+ #
23
+ # @note UUIDs are only assigned when id is nil to allow manual ID assignment
24
+ # @see RailsUuidPk::Railtie
25
+ # @see https://www.rfc-editor.org/rfc/rfc9562.html RFC 9562 (UUIDv7)
2
26
  module HasUuidv7PrimaryKey
3
27
  extend ActiveSupport::Concern
4
28
 
@@ -8,11 +32,20 @@ module RailsUuidPk
8
32
 
9
33
  private
10
34
 
35
+ # Assigns a UUIDv7 to the primary key if not already set.
36
+ #
37
+ # This method is called automatically before creating a record if the id is nil.
38
+ # It uses Ruby 3.3+'s SecureRandom.uuid_v7 for cryptographically secure UUID generation.
39
+ #
40
+ # @return [void]
41
+ # @note This method is private and should not be called directly
11
42
  def assign_uuidv7_if_needed
12
43
  # Skip if id was already set (manual set, bulk insert with ids, etc)
13
44
  return if id.present?
14
45
 
15
- self.id = SecureRandom.uuid_v7
46
+ uuid = SecureRandom.uuid_v7
47
+ RailsUuidPk.log(:debug, "Assigned UUIDv7 #{uuid} to #{self.class.name}")
48
+ self.id = uuid
16
49
  end
17
50
  end
18
51
  end
@@ -1,6 +1,48 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
4
+ # Migration helpers for automatic foreign key type detection.
5
+ #
6
+ # This module provides smart migration helpers that automatically detect when
7
+ # foreign key columns should use UUID types based on the referenced table's
8
+ # primary key type. It extends ActiveRecord's migration DSL to provide seamless
9
+ # UUID foreign key support.
10
+ #
11
+ # @example Automatic UUID foreign key detection
12
+ # create_table :posts do |t|
13
+ # t.references :user # Automatically uses :uuid type if users.id is UUID
14
+ # t.string :title
15
+ # end
16
+ #
17
+ # @example Polymorphic associations
18
+ # create_table :comments do |t|
19
+ # t.references :commentable, polymorphic: true # Uses :uuid if app uses UUIDs
20
+ # end
21
+ #
22
+ # @example Explicit type override (respects user choice)
23
+ # create_table :posts do |t|
24
+ # t.references :user, type: :integer # Uses :integer even if users.id is UUID
25
+ # end
26
+ #
27
+ # @see RailsUuidPk::Railtie
2
28
  module MigrationHelpers
29
+ # Migration helper methods for references and foreign keys.
30
+ #
31
+ # This module extends ActiveRecord's migration classes to automatically
32
+ # determine appropriate foreign key types based on the referenced table's
33
+ # primary key type.
3
34
  module References
35
+ # Creates a reference column with automatic type detection.
36
+ #
37
+ # Automatically sets the column type to :uuid if the referenced table
38
+ # uses UUID primary keys, unless the type is explicitly specified.
39
+ #
40
+ # @param args [Array<String, Symbol>] Arguments passed to the original references method
41
+ # @param options [Hash] Options hash for the reference column
42
+ # @option options [String, Symbol] :to_table The table being referenced (defaults to pluralized ref_name)
43
+ # @option options [Symbol] :type The column type (:integer, :bigint, :uuid)
44
+ # @option options [Boolean] :polymorphic Whether this is a polymorphic association
45
+ # @return [void]
4
46
  def references(*args, **options)
5
47
  ref_name = args.first
6
48
  ref_table = options.delete(:to_table) || ref_name.to_s.pluralize
@@ -12,34 +54,121 @@ module RailsUuidPk
12
54
  current_type = options[:type]
13
55
  if (current_type.nil? || current_type == :integer || current_type == :bigint) &&
14
56
  (uuid_primary_key?(ref_table) || (options[:polymorphic] && application_uses_uuid_primary_keys?))
57
+ RailsUuidPk.log(:debug, "Setting foreign key #{ref_name} to UUID type (referencing #{ref_table})")
15
58
  options[:type] = :uuid
16
59
  end
17
60
 
18
61
  super(*args, **options)
19
62
  end
20
63
 
64
+ # Adds a reference column to an existing table with automatic type detection.
65
+ #
66
+ # @param table_name [String, Symbol] The name of the table to add the reference to
67
+ # @param ref_name [String, Symbol] The name of the reference column
68
+ # @param options [Hash] Options hash for the reference column
69
+ # @option options [String, Symbol] :to_table The table being referenced (defaults to pluralized ref_name)
70
+ # @option options [Symbol] :type The column type (:integer, :bigint, :uuid)
71
+ # @option options [Boolean] :polymorphic Whether this is a polymorphic association
72
+ # @return [void]
21
73
  def add_reference(table_name, ref_name, **options)
22
74
  ref_table = options.delete(:to_table) || ref_name.to_s.pluralize
23
75
 
24
76
  current_type = options[:type]
25
77
  if (current_type.nil? || current_type == :integer || current_type == :bigint) &&
26
78
  (uuid_primary_key?(ref_table) || (options[:polymorphic] && application_uses_uuid_primary_keys?))
79
+ RailsUuidPk.log(:debug, "Setting foreign key #{ref_name} to UUID type (referencing #{ref_table})")
27
80
  options[:type] = :uuid
28
81
  end
29
82
 
30
83
  super(table_name, ref_name, **options)
31
84
  end
32
85
 
86
+ # Checks if the application is configured to use UUID primary keys globally.
87
+ #
88
+ # @return [Boolean] true if the application uses UUID primary keys by default
89
+ # @note This checks Rails generator configuration for primary_key_type
33
90
  def application_uses_uuid_primary_keys?
34
91
  # Check if the application is configured to use UUID primary keys globally
35
- defined?(Rails) && Rails.application.config.generators.options[:active_record]&.[](:primary_key_type) == :uuid
92
+ return false unless defined?(Rails) && Rails.application&.config&.generators&.options
93
+
94
+ active_record_config = Rails.application.config.generators.options[:active_record]
95
+ active_record_config.is_a?(Hash) && active_record_config[:primary_key_type] == :uuid
96
+ end
97
+
98
+ # Module-level access to application_uses_uuid_primary_keys? for testing.
99
+ #
100
+ # This method is exposed for testing purposes only and should not be used
101
+ # in production code. It creates a temporary instance to access the instance method.
102
+ #
103
+ # @return [Boolean] true if the application uses UUID primary keys by default
104
+ # @api private
105
+ # @note For testing only - do not use in production code
106
+ def self.application_uses_uuid_primary_keys?
107
+ # Create a dummy class to include the module and call the method
108
+ dummy_class = Class.new { include RailsUuidPk::MigrationHelpers::References }
109
+ dummy_class.new.application_uses_uuid_primary_keys?
36
110
  end
37
111
 
112
+ # Module-level access to uuid_primary_key? for testing.
113
+ #
114
+ # This method is exposed for testing purposes only and should not be used
115
+ # in production code. It creates a temporary instance to access the private method.
116
+ #
117
+ # @param table_name [String, Symbol] The name of the table to check
118
+ # @param mock_conn [Object] Optional mock connection for testing
119
+ # @return [Boolean] true if the table's primary key is a UUID type
120
+ # @api private
121
+ # @note For testing only - do not use in production code
122
+ def self.test_uuid_primary_key?(table_name, mock_conn = nil)
123
+ # Create a dummy class to include the module and make the method public for testing
124
+ dummy_class = Class.new do
125
+ include RailsUuidPk::MigrationHelpers::References
126
+ # For testing, provide a connection method that returns the mock_conn
127
+ define_method(:connection) { mock_conn } if mock_conn
128
+ # Make the private method public for testing
129
+ public :uuid_primary_key?
130
+ end
131
+ instance = dummy_class.new
132
+ instance.instance_variable_set(:@conn, mock_conn) if mock_conn
133
+ instance.uuid_primary_key?(table_name)
134
+ end
135
+
136
+ # Module-level access to find_primary_key_column for testing.
137
+ #
138
+ # This method is exposed for testing purposes only and should not be used
139
+ # in production code. It creates a temporary instance to access the private method.
140
+ #
141
+ # @param table_name [String, Symbol] The name of the table
142
+ # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter] The database connection
143
+ # @return [ActiveRecord::ConnectionAdapters::Column, nil] The primary key column or nil
144
+ # @api private
145
+ # @note For testing only - do not use in production code
146
+ def self.test_find_primary_key_column(table_name, conn)
147
+ # Create a dummy class to include the module and make the method public for testing
148
+ dummy_class = Class.new do
149
+ include RailsUuidPk::MigrationHelpers::References
150
+ # For testing, provide a connection method that returns the conn
151
+ define_method(:connection) { conn }
152
+ # Make the private method public for testing
153
+ public :find_primary_key_column
154
+ end
155
+ instance = dummy_class.new
156
+ instance.find_primary_key_column(table_name, conn)
157
+ end
158
+
159
+ # Alias for references method (ActiveRecord compatibility)
38
160
  alias_method :belongs_to, :references
161
+
162
+ # Alias for add_reference method (ActiveRecord compatibility)
39
163
  alias_method :add_belongs_to, :add_reference
40
164
 
41
165
  private
42
166
 
167
+ # Checks if a table uses UUID primary keys.
168
+ #
169
+ # @param table_name [String, Symbol] The name of the table to check
170
+ # @return [Boolean] true if the table's primary key is a UUID type
171
+ # @note Results are cached during migration execution for performance
43
172
  def uuid_primary_key?(table_name)
44
173
  # Cache results for the duration of the migration process to improve performance
45
174
  @uuid_pk_cache ||= {}
@@ -53,6 +182,11 @@ module RailsUuidPk
53
182
  @uuid_pk_cache[table_name] = !!(pk_column && pk_column.sql_type.downcase.match?(/\A(uuid|varchar\(36\))\z/))
54
183
  end
55
184
 
185
+ # Finds the primary key column for a given table.
186
+ #
187
+ # @param table_name [String, Symbol] The name of the table
188
+ # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter] The database connection
189
+ # @return [ActiveRecord::ConnectionAdapters::Column, nil] The primary key column or nil
56
190
  def find_primary_key_column(table_name, conn)
57
191
  pk_name = conn.primary_key(table_name)
58
192
  return nil unless pk_name
@@ -1,21 +1,52 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
4
+ # MySQL adapter extension for UUID type support.
5
+ #
6
+ # This module extends ActiveRecord's MySQL2 adapter to provide native UUID
7
+ # type support. Since MySQL doesn't have a native UUID type, it maps UUIDs
8
+ # to VARCHAR(36) columns and registers the custom UUID type handlers.
9
+ #
10
+ # @example Automatic type mapping
11
+ # # MySQL tables with VARCHAR(36) columns are automatically treated as UUIDs
12
+ # create_table :users do |t|
13
+ # t.column :id, :uuid # Maps to VARCHAR(36) in MySQL
14
+ # end
15
+ #
16
+ # @see RailsUuidPk::Type::Uuid
17
+ # @see https://dev.mysql.com/doc/refman/8.0/en/data-types.html
2
18
  module Mysql2AdapterExtension
19
+ # Defines native database types for MySQL UUID support.
20
+ #
21
+ # @return [Hash] Database type definitions including UUID mapping
3
22
  def native_database_types
4
23
  super.merge(
5
24
  uuid: { name: "varchar", limit: 36 }
6
25
  )
7
26
  end
8
27
 
28
+ # Registers UUID type handlers in the adapter's type map.
29
+ #
30
+ # @param m [ActiveRecord::ConnectionAdapters::AbstractAdapter::TypeMap] The type map to register with
31
+ # @return [void]
9
32
  def register_uuid_types(m = type_map)
33
+ RailsUuidPk.log(:debug, "Registering UUID types on #{m.class}")
10
34
  m.register_type(/varchar\(36\)/i) { RailsUuidPk::Type::Uuid.new }
11
35
  m.register_type("uuid") { RailsUuidPk::Type::Uuid.new }
12
36
  end
13
37
 
38
+ # Initializes the type map with UUID type registrations.
39
+ #
40
+ # @param m [ActiveRecord::ConnectionAdapters::AbstractAdapter::TypeMap] The type map to initialize
41
+ # @return [void]
14
42
  def initialize_type_map(m = type_map)
15
43
  super
16
44
  register_uuid_types(m)
17
45
  end
18
46
 
47
+ # Configures the database connection with UUID type support.
48
+ #
49
+ # @return [void]
19
50
  def configure_connection
20
51
  super
21
52
  register_uuid_types
@@ -1,11 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
4
+ # Rails integration for automatic UUIDv7 primary key generation.
5
+ #
6
+ # This Railtie automatically configures Rails applications to use UUID primary keys
7
+ # by including the HasUuidv7PrimaryKey concern in all ActiveRecord models and setting
8
+ # up proper database type mappings and migration helpers.
9
+ #
10
+ # The railtie performs the following integrations:
11
+ # - Configures Rails generators to use UUID primary keys
12
+ # - Registers UUID types for SQLite and MySQL adapters
13
+ # - Includes UUIDv7 generation concern in all models
14
+ # - Adds smart migration helpers for foreign key type detection
15
+ # - Sets appropriate schema format for UUID compatibility
16
+ #
17
+ # @example Automatic configuration
18
+ # # No configuration needed - everything works automatically
19
+ # class User < ApplicationRecord
20
+ # # Primary key is automatically UUIDv7
21
+ # end
22
+ #
23
+ # @see RailsUuidPk::HasUuidv7PrimaryKey
24
+ # @see RailsUuidPk::MigrationHelpers
2
25
  class Railtie < ::Rails::Railtie
26
+ # Configures Rails generators to use UUID primary keys by default.
27
+ #
28
+ # This initializer sets the default primary_key_type to :uuid for all
29
+ # newly generated models and migrations.
3
30
  initializer "rails-uuid-pk.generators" do |app|
4
31
  app.config.generators do |g|
5
32
  g.orm :active_record, primary_key_type: :uuid
6
33
  end
7
34
  end
8
35
 
36
+ # Configures type mappings for SQLite and MySQL adapters.
37
+ #
38
+ # Registers the custom UUID type for adapters that don't have native UUID support.
9
39
  initializer "rails-uuid-pk.configure_type_map", after: "active_record.initialize_database" do
10
40
  ActiveSupport.on_load(:active_record) do
11
41
  adapter_name = ActiveRecord::Base.connection.adapter_name
@@ -15,6 +45,10 @@ module RailsUuidPk
15
45
  end
16
46
  end
17
47
 
48
+ # Extends database adapters with UUID type support.
49
+ #
50
+ # Prepends adapter-specific extensions to add native UUID type definitions
51
+ # and type registration methods.
18
52
  initializer "rails-uuid-pk.native_types" do
19
53
  ActiveSupport.on_load(:active_record_sqlite3adapter) do
20
54
  prepend RailsUuidPk::Sqlite3AdapterExtension
@@ -25,6 +59,10 @@ module RailsUuidPk
25
59
  end
26
60
  end
27
61
 
62
+ # Ensures UUID types are registered on all database connections.
63
+ #
64
+ # This runs after Rails initialization to register UUID types on any
65
+ # existing or future database connections.
28
66
  config.after_initialize do
29
67
  ActiveSupport.on_load(:active_record) do
30
68
  if ActiveRecord::Base.connected?
@@ -46,26 +84,43 @@ module RailsUuidPk
46
84
  end
47
85
  end
48
86
 
87
+ # Sets the schema format to Ruby for UUID compatibility.
88
+ #
89
+ # Ruby schema format is required for proper UUID type handling across
90
+ # different database adapters and Rails versions.
49
91
  initializer "rails-uuid-pk.schema_format" do |app|
50
92
  app.config.active_record.schema_format ||= :ruby
51
93
  end
52
94
 
95
+ # Includes the UUIDv7 generation concern in all ActiveRecord models.
96
+ #
97
+ # This automatically adds UUIDv7 primary key generation to all models
98
+ # without requiring any changes to existing code.
53
99
  initializer "rails-uuid-pk.include_concern" do
54
100
  ActiveSupport.on_load(:active_record) do
55
101
  ActiveRecord::Base.include RailsUuidPk::HasUuidv7PrimaryKey
56
102
  end
57
103
  end
58
104
 
105
+ # Adds smart migration helpers for automatic foreign key type detection.
106
+ #
107
+ # Prepends the References module to ActiveRecord migration classes to
108
+ # automatically detect when foreign keys should use UUID types.
59
109
  initializer "rails-uuid-pk.migration_helpers" do
60
110
  ActiveSupport.on_load(:active_record) do
61
111
  require "rails_uuid_pk/migration_helpers"
62
112
 
113
+ ActiveRecord::Migration.prepend(RailsUuidPk::MigrationHelpers::References)
63
114
  ActiveRecord::ConnectionAdapters::TableDefinition.prepend(RailsUuidPk::MigrationHelpers::References)
64
115
  ActiveRecord::ConnectionAdapters::Table.prepend(RailsUuidPk::MigrationHelpers::References)
65
116
  ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(RailsUuidPk::MigrationHelpers::References)
66
117
  end
67
118
  end
68
119
 
120
+ # Registers the custom UUID type with ActiveRecord's type registry.
121
+ #
122
+ # @param adapter [Symbol] The database adapter (:sqlite, :mysql)
123
+ # @return [void]
69
124
  def self.register_uuid_type(adapter)
70
125
  ActiveRecord::Type.register(:uuid, RailsUuidPk::Type::Uuid, adapter: adapter)
71
126
  end
@@ -1,24 +1,56 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
4
+ # SQLite adapter extension for UUID type support.
5
+ #
6
+ # This module extends ActiveRecord's SQLite3 adapter to provide native UUID
7
+ # type support. Since SQLite doesn't have a native UUID type, it maps UUIDs
8
+ # to VARCHAR(36) columns and registers the custom UUID type handlers.
9
+ #
10
+ # @example Automatic type mapping
11
+ # # SQLite tables with VARCHAR(36) columns are automatically treated as UUIDs
12
+ # create_table :users do |t|
13
+ # t.column :id, :uuid # Maps to VARCHAR(36) in SQLite
14
+ # end
15
+ #
16
+ # @see RailsUuidPk::Type::Uuid
17
+ # @see https://www.sqlite.org/datatype3.html
2
18
  module Sqlite3AdapterExtension
19
+ # Defines native database types for SQLite UUID support.
20
+ #
21
+ # @return [Hash] Database type definitions including UUID mapping
3
22
  def native_database_types
4
23
  super.merge(
5
24
  uuid: { name: "varchar", limit: 36 }
6
25
  )
7
26
  end
8
27
 
28
+ # Registers UUID type handlers in the adapter's type map.
29
+ #
30
+ # @param m [ActiveRecord::ConnectionAdapters::AbstractAdapter::TypeMap] The type map to register with
31
+ # @return [void]
9
32
  def register_uuid_types(m = type_map)
10
- puts "[RailsUuidPk] Registering UUID types on #{m.class}"
33
+ RailsUuidPk.log(:debug, "Registering UUID types on #{m.class}")
11
34
  m.register_type(/varchar\(36\)/i) { RailsUuidPk::Type::Uuid.new }
12
35
  m.register_type("uuid") { RailsUuidPk::Type::Uuid.new }
13
36
  end
14
37
 
38
+ # Initializes the type map with UUID type registrations.
39
+ #
40
+ # @param m [ActiveRecord::ConnectionAdapters::AbstractAdapter::TypeMap] The type map to initialize
41
+ # @return [void]
15
42
  def initialize_type_map(m = type_map)
16
43
  super
17
44
  register_uuid_types(m)
18
45
  end
19
46
 
47
+ # Configures the database connection with UUID type support.
48
+ #
49
+ # @return [void]
20
50
  def configure_connection
21
- super
51
+ # Only call super if not inside a transaction, as PRAGMA statements
52
+ # cannot be executed inside transactions in SQLite
53
+ super unless open_transactions > 0
22
54
  register_uuid_types
23
55
  end
24
56
  end
@@ -1,6 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
4
+ # Custom ActiveRecord types for UUID handling.
5
+ #
6
+ # This module provides custom type classes that handle UUID serialization,
7
+ # deserialization, and schema dumping with proper Rails version compatibility.
2
8
  module Type
9
+ # Custom UUID type for ActiveRecord.
10
+ #
11
+ # This type extends ActiveRecord::Type::String to provide UUID-specific
12
+ # handling with Rails version-aware schema dumping. It ensures proper
13
+ # UUID validation and formatting while maintaining backward compatibility.
14
+ #
15
+ # @example Automatic type handling
16
+ # class User < ApplicationRecord
17
+ # # id column automatically uses this type
18
+ # end
19
+ #
20
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/Type/String.html
3
21
  class Uuid < ActiveRecord::Type::String
22
+ # Returns the appropriate schema type symbol for the current Rails version.
23
+ #
24
+ # @return [Symbol] :uuid for Rails 8.1+, :string for earlier versions
25
+ # @note Rails 8.1+ supports native :uuid type in schema dumping
4
26
  def type
5
27
  # Rails 8.1+ supports UUID types in schema dumping
6
28
  # Earlier versions need :string to avoid "Unknown type 'uuid'" errors
@@ -11,11 +33,22 @@ module RailsUuidPk
11
33
  end
12
34
  end
13
35
 
36
+ # Deserializes a value from the database.
37
+ #
38
+ # @param value [Object] The raw value from the database
39
+ # @return [String, nil] The deserialized UUID string or nil
14
40
  def deserialize(value)
15
41
  return if value.nil?
16
42
  cast(value)
17
43
  end
18
44
 
45
+ # Casts a value to a UUID string.
46
+ #
47
+ # Accepts valid UUID strings and converts other values to strings.
48
+ # Invalid UUIDs are allowed for backward compatibility.
49
+ #
50
+ # @param value [Object] The value to cast
51
+ # @return [String, nil] The cast UUID string or nil
19
52
  def cast(value)
20
53
  return if value.nil?
21
54
  return value if value.is_a?(String) && valid?(value)
@@ -29,20 +62,58 @@ module RailsUuidPk
29
62
  value.to_s
30
63
  end
31
64
 
65
+ # Serializes a value for database storage.
66
+ #
67
+ # @param value [Object] The value to serialize
68
+ # @return [String, nil] The serialized value or nil
32
69
  def serialize(value)
33
70
  cast(value)
34
71
  end
35
72
 
73
+ # Casts a value for database storage (ActiveRecord compatibility).
74
+ #
75
+ # @param value [Object] The value to cast for database storage
76
+ # @return [String, nil] The cast value or nil
77
+ def type_cast_for_database(value)
78
+ serialize(value)
79
+ end
80
+
81
+ # Casts a value from the database (ActiveRecord compatibility).
82
+ #
83
+ # @param value [Object] The value from the database
84
+ # @return [String, nil] The cast value or nil
85
+ def type_cast_from_database(value)
86
+ deserialize(value)
87
+ end
88
+
89
+ # Checks if two values are different for change detection.
90
+ #
91
+ # Compares the original values to determine if they represent different data,
92
+ # which is used by ActiveRecord for dirty tracking.
93
+ #
94
+ # @param raw_old_value [Object] The old raw value from the database
95
+ # @param new_value [Object] The new value being compared
96
+ # @return [Boolean] true if the values are different
36
97
  def changed_in_place?(raw_old_value, new_value)
37
- cast(raw_old_value) != cast(new_value)
98
+ # Compare original values for change detection
99
+ raw_old_value != new_value
38
100
  end
39
101
 
40
102
  private
41
103
 
104
+ # Validates if a string is a properly formatted UUID.
105
+ #
106
+ # @param value [String] The string to validate
107
+ # @return [Boolean] true if the string matches UUID format
42
108
  def valid?(value)
109
+ return false if value.nil?
43
110
  value.match?(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
44
111
  end
45
112
 
113
+ # Checks if the current Rails version supports UUID types in schema dumping.
114
+ #
115
+ # @return [Boolean] true if Rails 8.1 or later
116
+ # @note Rails 8.1+ allows :uuid in schema files, earlier versions require :string
46
117
  def rails_supports_uuid_in_schema?
47
118
  # Rails 8.1+ supports UUID types in schema dumping
48
119
  # Earlier versions (8.0.x) need :string to avoid "Unknown type 'uuid'" errors
@@ -1,3 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsUuidPk
2
- VERSION = "0.8.0"
4
+ # The version of the rails-uuid-pk gem.
5
+ #
6
+ # Follows semantic versioning (MAJOR.MINOR.PATCH).
7
+ #
8
+ # @return [String] The current version
9
+ # @example
10
+ # RailsUuidPk::VERSION # => "0.9.0"
11
+ VERSION = "0.9.0"
3
12
  end
data/lib/rails_uuid_pk.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails_uuid_pk/version"
2
4
  require "rails_uuid_pk/concern"
3
5
  require "rails_uuid_pk/type"
@@ -5,5 +7,69 @@ require "rails_uuid_pk/sqlite3_adapter_extension"
5
7
  require "rails_uuid_pk/mysql2_adapter_extension"
6
8
  require "rails_uuid_pk/railtie"
7
9
 
10
+ # Rails UUID Primary Key
11
+ #
12
+ # A Rails gem that automatically uses UUIDv7 for all primary keys in Rails applications.
13
+ # This gem provides seamless integration with Rails generators, automatic UUIDv7 generation,
14
+ # and support for PostgreSQL, MySQL, and SQLite databases.
15
+ #
16
+ # @example Installation
17
+ # # Add to Gemfile
18
+ # gem 'rails-uuid-pk'
19
+ #
20
+ # # All models automatically get UUIDv7 primary keys
21
+ # class User < ApplicationRecord
22
+ # # id will be automatically assigned a UUIDv7 on create
23
+ # end
24
+ #
25
+ # @example Migration with foreign keys
26
+ # # Foreign key types are automatically detected
27
+ # create_table :posts do |t|
28
+ # t.references :user, null: false # Automatically uses :uuid type
29
+ # t.string :title
30
+ # end
31
+ #
32
+ # @see RailsUuidPk::HasUuidv7PrimaryKey
33
+ # @see RailsUuidPk::Railtie
34
+ # @see https://github.com/seouri/rails-uuid-pk
8
35
  module RailsUuidPk
36
+ # The prefix used for all log messages.
37
+ #
38
+ # @return [String] The log message prefix
39
+ LOG_PREFIX = "[RailsUuidPk]"
40
+
41
+ # Returns the logger instance for this gem.
42
+ #
43
+ # Uses Rails.logger if available and not nil, otherwise creates a new Logger instance.
44
+ #
45
+ # @return [Logger] The logger instance
46
+ # @example
47
+ # RailsUuidPk.logger.info("Custom message")
48
+ def self.logger
49
+ return @logger if @logger
50
+
51
+ rails_logger = defined?(Rails.logger) && Rails.logger
52
+ @logger = rails_logger || Logger.new($stdout)
53
+ @logger = Logger.new($stdout) unless @logger.is_a?(Logger)
54
+ @logger
55
+ end
56
+
57
+ # Sets the logger instance for this gem.
58
+ #
59
+ # @param logger [Logger] The logger instance to use
60
+ # @example
61
+ # RailsUuidPk.logger = Rails.logger
62
+ def self.logger=(logger)
63
+ @logger = logger
64
+ end
65
+
66
+ # Logs a message at the specified level.
67
+ #
68
+ # @param level [Symbol] The log level (:debug, :info, :warn, :error, :fatal)
69
+ # @param message [String] The message to log
70
+ # @example
71
+ # RailsUuidPk.log(:info, "UUID assigned successfully")
72
+ def self.log(level, message)
73
+ logger.send(level, "#{LOG_PREFIX} #{message}")
74
+ end
9
75
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-uuid-pk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joon Lee
@@ -65,6 +65,76 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: 2.9.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: yard
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.9'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.9'
82
+ - !ruby/object:Gem::Dependency
83
+ name: bundler-audit
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.9'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.9'
96
+ - !ruby/object:Gem::Dependency
97
+ name: simplecov
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.22'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.22'
110
+ - !ruby/object:Gem::Dependency
111
+ name: benchmark-ips
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.14'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.14'
124
+ - !ruby/object:Gem::Dependency
125
+ name: benchmark
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '0.5'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '0.5'
68
138
  description: Automatically use UUID v7 for all primary keys in Rails applications.
69
139
  Works with PostgreSQL, MySQL, and SQLite, zero configuration required.
70
140
  email: