pg_multitenant_schemas 0.2.2 โ 0.2.3
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/CHANGELOG.md +1 -3
- data/README.md +4 -4
- data/docs/testing_rails_tasks.md +128 -0
- data/lib/pg_multitenant_schemas/migration_schema_operations.rb +99 -5
- data/lib/pg_multitenant_schemas/migrator.rb +66 -9
- data/lib/pg_multitenant_schemas/rails/railtie.rb +4 -1
- data/lib/pg_multitenant_schemas/schema_switcher.rb +34 -0
- data/lib/pg_multitenant_schemas/tasks/tenant_tasks.rake +0 -3
- data/lib/pg_multitenant_schemas/version.rb +1 -1
- data/pg_multitenant_schemas.gemspec +5 -5
- metadata +10 -10
- data/lib/pg_multitenant_schemas/tasks/pg_multitenant_schemas.rake +0 -65
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f362436f339eb06d63f3c54a928054b0dd454eb0c6165f219d57ad1658476b50
         | 
| 4 | 
            +
              data.tar.gz: 150c105745e09f419168145f57a70c856241fea231cf20d87d01d8cce1da60a6
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 228d2da53051f03e48dd828eec5817987b4029c850026a58493cec6a7877a126cf5b632722bcc5607c686b704e87555bfad583d364895a314182317f6ce0f9c8
         | 
| 7 | 
            +
              data.tar.gz: c891689fabcd8b3ee54752f9dc3a7bd54c37eabf9a081a4696f23ec0bb01cf0439a98899c8a51ba90661ca92929719f3fd1c3e63d63b54b8b9a56393f418fc4e
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -5,9 +5,7 @@ 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/v2.0.0.html).
         | 
| 7 7 |  | 
| 8 | 
            -
            ## [ | 
| 9 | 
            -
             | 
| 10 | 
            -
            ## [0.2.2] - 2025-09-07
         | 
| 8 | 
            +
            ## [0.2.3] - 2025-01-17
         | 
| 11 9 |  | 
| 12 10 | 
             
            ### ๐ง **Developer Experience & Testing**
         | 
| 13 11 | 
             
            - **NEW: Local Workflow Testing**: Complete solution for testing GitHub Actions locally before push
         | 
    
        data/README.md
    CHANGED
    
    | @@ -3,14 +3,14 @@ | |
| 3 3 | 
             
            [](https://badge.fury.io/rb/pg_multitenant_schemas)
         | 
| 4 4 | 
             
            [](https://github.com/yourusername/pg_multitenant_schemas/actions/workflows/main.yml)
         | 
| 5 5 |  | 
| 6 | 
            -
            A modern Ruby gem that provides PostgreSQL schema-based multitenancy with automatic tenant resolution and schema switching.  | 
| 6 | 
            +
            A modern Ruby gem that provides PostgreSQL schema-based multitenancy with automatic tenant resolution and schema switching. Compatible with Rails 7+ and Ruby 3.0+, with optimizations for Rails 8, focusing on security, performance, and developer experience.
         | 
| 7 7 |  | 
| 8 8 | 
             
            ## โจ Features
         | 
| 9 9 |  | 
| 10 10 | 
             
            - ๐ข **Schema-based multitenancy** - Complete tenant isolation using PostgreSQL schemas
         | 
| 11 11 | 
             
            - ๐ **Automatic schema switching** - Seamlessly switch between tenant schemas  
         | 
| 12 12 | 
             
            - ๐ **Subdomain resolution** - Extract tenant from request subdomains
         | 
| 13 | 
            -
            -  | 
| 13 | 
            +
            - ๐ **Rails 7+ compatible** - Works with Rails 7 and optimized for Rails 8
         | 
| 14 14 | 
             
            - ๏ฟฝ๏ธ **Security-first design** - Database-level tenant isolation
         | 
| 15 15 | 
             
            - ๐งต **Thread-safe** - Safe for concurrent operations
         | 
| 16 16 | 
             
            - ๐ **Comprehensive logging** - Track schema operations
         | 
| @@ -20,8 +20,8 @@ A modern Ruby gem that provides PostgreSQL schema-based multitenancy with automa | |
| 20 20 |  | 
| 21 21 | 
             
            ## Requirements
         | 
| 22 22 |  | 
| 23 | 
            -
            - Ruby 3. | 
| 24 | 
            -
            - Rails  | 
| 23 | 
            +
            - Ruby 3.0+
         | 
| 24 | 
            +
            - Rails 7.0+
         | 
| 25 25 | 
             
            - PostgreSQL 12+
         | 
| 26 26 | 
             
            - **pg gem**: 1.5 or higher
         | 
| 27 27 |  | 
| @@ -0,0 +1,128 @@ | |
| 1 | 
            +
            # ๐งช Testing Rails Tasks in PG Multitenant Schemas
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ## ๐จ Important: Testing Context
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            The `rails tenants:list` command only works **within a Rails application** that includes this gem, not in the gem directory itself.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## ๐ง Fixed Issues
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            โ
 **Removed circular dependency** in rake task loading
         | 
| 10 | 
            +
            โ
 **Eliminated duplicate task files** (`pg_multitenant_schemas.rake` was identical to `tenant_tasks.rake`)
         | 
| 11 | 
            +
            โ
 **Updated railtie** to load all task files properly
         | 
| 12 | 
            +
            โ
 **Added missing `list_schemas` method** to `SchemaSwitcher` class
         | 
| 13 | 
            +
            โ
 **Fixed Rails 8 migration_context compatibility** - Updated migration handling for Rails 8
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            ## ๐ How to Test the Rails Tasks
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ### **Option 1: Create a Test Rails App**
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            ```bash
         | 
| 20 | 
            +
            # Create a new Rails app for testing
         | 
| 21 | 
            +
            rails new test_multitenancy --database=postgresql
         | 
| 22 | 
            +
            cd test_multitenancy
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            # Add the gem to Gemfile
         | 
| 25 | 
            +
            echo 'gem "pg_multitenant_schemas", path: "/Users/rubenpaz/personal/pg_multitenant_schemas"' >> Gemfile
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            # Install the gem
         | 
| 28 | 
            +
            bundle install
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            # Now you can test the tasks
         | 
| 31 | 
            +
            rails tenants:list
         | 
| 32 | 
            +
            rails tenants:status
         | 
| 33 | 
            +
            rails tenants:migrate
         | 
| 34 | 
            +
            ```
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            ### **Option 2: Use an Existing Rails App**
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            ```bash
         | 
| 39 | 
            +
            # Navigate to your existing Rails app
         | 
| 40 | 
            +
            cd /Users/rubenpaz/personal/lbyte-security  # or wherever your Rails app is
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            # Add gem to Gemfile if not already added
         | 
| 43 | 
            +
            # gem "pg_multitenant_schemas", path: "/Users/rubenpaz/personal/pg_multitenant_schemas"
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            # Install and test
         | 
| 46 | 
            +
            bundle install
         | 
| 47 | 
            +
            rails tenants:list
         | 
| 48 | 
            +
            ```
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ### **Option 3: Test in the Example Directory**
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ```bash
         | 
| 53 | 
            +
            # If there's a Rails example in the gem
         | 
| 54 | 
            +
            cd /Users/rubenpaz/personal/pg_multitenant_schemas/examples
         | 
| 55 | 
            +
            # Follow instructions in that directory
         | 
| 56 | 
            +
            ```
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            ## ๐ Available Tasks
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            After including the gem in a Rails app, you'll have these tasks:
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            ### **Basic Tasks**
         | 
| 63 | 
            +
            - `rails tenants:list` - List all tenant schemas
         | 
| 64 | 
            +
            - `rails tenants:status` - Show migration status for all tenants  
         | 
| 65 | 
            +
            - `rails tenants:migrate` - Run migrations for all tenant schemas
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            ### **Advanced Tasks**  
         | 
| 68 | 
            +
            - `rails tenants:migrate_tenant[schema_name]` - Run migrations for specific tenant
         | 
| 69 | 
            +
            - `rails tenants:create[schema_name]` - Setup new tenant with schema and migrations
         | 
| 70 | 
            +
            - `rails tenants:setup` - Setup schemas and run migrations for all existing tenants
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            ### **Management Tasks**
         | 
| 73 | 
            +
            - `rails tenants:new[attributes]` - Create new tenant with attributes (JSON format)
         | 
| 74 | 
            +
            - `rails tenants:drop[schema_name]` - Drop tenant schema (DANGEROUS)
         | 
| 75 | 
            +
            - `rails tenants:rollback[schema_name,steps]` - Rollback migrations for a tenant
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            ### **Convenience Aliases**
         | 
| 78 | 
            +
            - `rails tenants:db:create[schema_name]` - Alias for `tenants:create`
         | 
| 79 | 
            +
            - `rails tenants:db:migrate` - Alias for `tenants:migrate`
         | 
| 80 | 
            +
            - `rails tenants:db:status` - Alias for `tenants:status`
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            ### **Legacy Tasks (Deprecated)**
         | 
| 83 | 
            +
            - `rails pg_multitenant_schemas:list_schemas` - Use `tenants:list` instead
         | 
| 84 | 
            +
            - `rails pg_multitenant_schemas:migrate_all` - Use `tenants:migrate` instead
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            ## ๐ ๏ธ Troubleshooting
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            ### "Command not found" or "stack level too deep"
         | 
| 89 | 
            +
            - โ
 **Fixed**: Circular dependency in task loading removed
         | 
| 90 | 
            +
            - Make sure you're in a Rails application directory
         | 
| 91 | 
            +
            - Run `bundle install` after adding the gem
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            ### "NoMethodError: undefined method 'list_schemas'"  
         | 
| 94 | 
            +
            - โ
 **Fixed**: Added missing `list_schemas` method to `SchemaSwitcher`
         | 
| 95 | 
            +
            - Update to the latest version of the gem
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            ### "NoMethodError: undefined method 'migration_context'"
         | 
| 98 | 
            +
            - โ
 **Fixed**: Updated migration handling for Rails 8 compatibility
         | 
| 99 | 
            +
            - The gem now properly handles migration context access across Rails versions
         | 
| 100 | 
            +
            - Update to the latest version of the gem
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            ### "No such task"
         | 
| 103 | 
            +
            - Ensure the gem is properly added to your Rails app's Gemfile
         | 
| 104 | 
            +
            - Run `bundle install`
         | 
| 105 | 
            +
            - Check that the gem is loading: `rails runner "puts PgMultitenantSchemas::VERSION"`
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            ### "Environment not loaded"
         | 
| 108 | 
            +
            - The tasks require `:environment`, so they need a proper Rails environment
         | 
| 109 | 
            +
            - Make sure your Rails app's database is configured and accessible
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            ## ๐งช Quick Test
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            To verify everything works, create a minimal test:
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            ```bash
         | 
| 116 | 
            +
            # In your Rails app directory
         | 
| 117 | 
            +
            rails runner "puts 'Gem loaded: ' + PgMultitenantSchemas::VERSION"
         | 
| 118 | 
            +
            rails tenants:list
         | 
| 119 | 
            +
            ```
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            ## ๐ Task File Structure
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            The gem now has a clean task structure:
         | 
| 124 | 
            +
            - `basic_tasks.rake` - List, status, migrate tasks
         | 
| 125 | 
            +
            - `advanced_tasks.rake` - Advanced tenant management  
         | 
| 126 | 
            +
            - `tenant_tasks.rake` - Management tasks + aliases + legacy compatibility
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            All are loaded automatically via the Rails railtie when you include the gem in your Rails application.
         | 
| @@ -34,21 +34,115 @@ module PgMultitenantSchemas | |
| 34 34 | 
             
                end
         | 
| 35 35 |  | 
| 36 36 | 
             
                def run_migrations
         | 
| 37 | 
            -
                   | 
| 37 | 
            +
                  # Use the migration context to run migrations
         | 
| 38 | 
            +
                  migration_context_obj = migration_context
         | 
| 39 | 
            +
                  return unless migration_context_obj
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  migration_context_obj.migrate
         | 
| 38 42 | 
             
                end
         | 
| 39 43 |  | 
| 40 44 | 
             
                def pending_migrations
         | 
| 41 | 
            -
                   | 
| 42 | 
            -
             | 
| 45 | 
            +
                  migration_context_obj = migration_context
         | 
| 46 | 
            +
                  return [] unless migration_context_obj
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  all_migrations = migration_context_obj.migrations
         | 
| 49 | 
            +
                  applied_version_list = applied_versions
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  all_migrations.reject do |migration|
         | 
| 52 | 
            +
                    applied_version_list.include?(migration.version)
         | 
| 43 53 | 
             
                  end
         | 
| 44 54 | 
             
                end
         | 
| 45 55 |  | 
| 46 56 | 
             
                def applied_migrations
         | 
| 47 | 
            -
                   | 
| 57 | 
            +
                  applied_versions
         | 
| 48 58 | 
             
                end
         | 
| 49 59 |  | 
| 50 60 | 
             
                def migration_paths
         | 
| 51 | 
            -
                   | 
| 61 | 
            +
                  migration_context_obj = migration_context
         | 
| 62 | 
            +
                  if migration_context_obj.respond_to?(:migrations_paths)
         | 
| 63 | 
            +
                    migration_context_obj.migrations_paths
         | 
| 64 | 
            +
                  elsif defined?(::Rails) && ::Rails.application
         | 
| 65 | 
            +
                    # Fallback to default Rails migration paths
         | 
| 66 | 
            +
                    ::Rails.application.paths["db/migrate"].expanded
         | 
| 67 | 
            +
                  else
         | 
| 68 | 
            +
                    ["db/migrate"]
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def migration_context
         | 
| 73 | 
            +
                  # Return nil if ActiveRecord is not available (for tests)
         | 
| 74 | 
            +
                  return nil unless defined?(ActiveRecord::Base)
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  # Rails 8 compatibility: Try multiple approaches
         | 
| 77 | 
            +
                  find_migration_context
         | 
| 78 | 
            +
                rescue StandardError => e
         | 
| 79 | 
            +
                  # Use explicit Rails logger to avoid namespace conflicts
         | 
| 80 | 
            +
                  ::Rails.logger&.warn("Failed to get migration context: #{e.message}") if defined?(::Rails)
         | 
| 81 | 
            +
                  nil
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def find_migration_context
         | 
| 85 | 
            +
                  if ActiveRecord::Base.respond_to?(:migration_context)
         | 
| 86 | 
            +
                    # Rails 8+: Try base migration context first
         | 
| 87 | 
            +
                    ActiveRecord::Base.migration_context
         | 
| 88 | 
            +
                  elsif ActiveRecord::Base.connection.respond_to?(:migration_context)
         | 
| 89 | 
            +
                    # Rails 7: Use connection migration context
         | 
| 90 | 
            +
                    ActiveRecord::Base.connection.migration_context
         | 
| 91 | 
            +
                  elsif defined?(ActiveRecord::MigrationContext)
         | 
| 92 | 
            +
                    # Fallback: Create a new migration context with default paths
         | 
| 93 | 
            +
                    create_fallback_migration_context
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def create_fallback_migration_context
         | 
| 98 | 
            +
                  paths = if defined?(::Rails) && ::Rails.application
         | 
| 99 | 
            +
                            ::Rails.application.paths["db/migrate"].expanded
         | 
| 100 | 
            +
                          else
         | 
| 101 | 
            +
                            ["db/migrate"]
         | 
| 102 | 
            +
                          end
         | 
| 103 | 
            +
                  ActiveRecord::MigrationContext.new(paths)
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                # Get applied migration versions with Rails 8 compatibility
         | 
| 107 | 
            +
                def applied_versions
         | 
| 108 | 
            +
                  # Return empty array if ActiveRecord is not available (for tests)
         | 
| 109 | 
            +
                  return [] unless defined?(ActiveRecord::Base)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  applied_versions_from_context || applied_versions_from_database
         | 
| 112 | 
            +
                rescue StandardError => e
         | 
| 113 | 
            +
                  # If anything fails, return empty array
         | 
| 114 | 
            +
                  ::Rails.logger&.warn("Failed to get applied versions: #{e.message}") if defined?(::Rails)
         | 
| 115 | 
            +
                  []
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                def applied_versions_from_context
         | 
| 119 | 
            +
                  # Try using migration context first (for tests and Rails 7)
         | 
| 120 | 
            +
                  migration_context_obj = migration_context
         | 
| 121 | 
            +
                  return migration_context_obj.get_all_versions if migration_context_obj.respond_to?(:get_all_versions)
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  nil
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def applied_versions_from_database
         | 
| 127 | 
            +
                  # Fallback to direct database query (Rails 8)
         | 
| 128 | 
            +
                  connection = ActiveRecord::Base.connection
         | 
| 129 | 
            +
                  table_name = "schema_migrations"
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  ensure_schema_migrations_table_exists(connection, table_name)
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  # Query the table directly for maximum compatibility
         | 
| 134 | 
            +
                  connection.select_values("SELECT version FROM #{table_name} ORDER BY version")
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                def ensure_schema_migrations_table_exists(connection, table_name)
         | 
| 138 | 
            +
                  # Ensure the schema_migrations table exists
         | 
| 139 | 
            +
                  return if connection.table_exists?(table_name)
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  # Create the table if it doesn't exist
         | 
| 142 | 
            +
                  connection.create_table(table_name, id: false) do |t|
         | 
| 143 | 
            +
                    t.string :version, null: false
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
                  connection.add_index(table_name, :version, unique: true, name: "unique_schema_migrations")
         | 
| 52 146 | 
             
                end
         | 
| 53 147 | 
             
              end
         | 
| 54 148 | 
             
            end
         | 
| @@ -73,15 +73,72 @@ module PgMultitenantSchemas | |
| 73 73 | 
             
                    puts "โช Rolling back #{steps} steps for #{schema_name}" if verbose
         | 
| 74 74 | 
             
                    original_schema = current_schema
         | 
| 75 75 |  | 
| 76 | 
            -
                     | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 76 | 
            +
                    perform_rollback(schema_name, steps, verbose)
         | 
| 77 | 
            +
                  ensure
         | 
| 78 | 
            +
                    switch_to_schema(original_schema) if original_schema
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  private
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def perform_rollback(schema_name, steps, verbose)
         | 
| 84 | 
            +
                    switch_to_schema(schema_name)
         | 
| 85 | 
            +
                    migration_context_obj = migration_context
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    unless migration_context_obj
         | 
| 88 | 
            +
                      puts "  โ Cannot rollback: migration context not available" if verbose
         | 
| 89 | 
            +
                      return
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    migration_context_obj.rollback(migration_paths, steps)
         | 
| 93 | 
            +
                    puts "  โ
 Rollback completed" if verbose
         | 
| 94 | 
            +
                  rescue StandardError => e
         | 
| 95 | 
            +
                    puts "  โ Rollback failed: #{e.message}" if verbose
         | 
| 96 | 
            +
                    raise
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  def migration_context
         | 
| 100 | 
            +
                    # Return nil if ActiveRecord is not available (for tests)
         | 
| 101 | 
            +
                    return nil unless defined?(ActiveRecord::Base)
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                    # Rails 8 compatibility: Try multiple approaches
         | 
| 104 | 
            +
                    find_migration_context
         | 
| 105 | 
            +
                  rescue StandardError => e
         | 
| 106 | 
            +
                    # Use explicit Rails logger to avoid namespace conflicts
         | 
| 107 | 
            +
                    ::Rails.logger&.warn("Failed to get migration context: #{e.message}") if defined?(::Rails)
         | 
| 108 | 
            +
                    nil
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def find_migration_context
         | 
| 112 | 
            +
                    if ActiveRecord::Base.respond_to?(:migration_context)
         | 
| 113 | 
            +
                      # Rails 8+: Try base migration context first
         | 
| 114 | 
            +
                      ActiveRecord::Base.migration_context
         | 
| 115 | 
            +
                    elsif ActiveRecord::Base.connection.respond_to?(:migration_context)
         | 
| 116 | 
            +
                      # Rails 7: Use connection migration context
         | 
| 117 | 
            +
                      ActiveRecord::Base.connection.migration_context
         | 
| 118 | 
            +
                    elsif defined?(ActiveRecord::MigrationContext)
         | 
| 119 | 
            +
                      # Fallback: Create a new migration context with default paths
         | 
| 120 | 
            +
                      create_fallback_migration_context
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  def create_fallback_migration_context
         | 
| 125 | 
            +
                    paths = if defined?(::Rails) && ::Rails.application
         | 
| 126 | 
            +
                              ::Rails.application.paths["db/migrate"].expanded
         | 
| 127 | 
            +
                            else
         | 
| 128 | 
            +
                              ["db/migrate"]
         | 
| 129 | 
            +
                            end
         | 
| 130 | 
            +
                    ActiveRecord::MigrationContext.new(paths)
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  def migration_paths
         | 
| 134 | 
            +
                    migration_context_obj = migration_context
         | 
| 135 | 
            +
                    if migration_context_obj.respond_to?(:migrations_paths)
         | 
| 136 | 
            +
                      migration_context_obj.migrations_paths
         | 
| 137 | 
            +
                    elsif defined?(::Rails) && ::Rails.application
         | 
| 138 | 
            +
                      # Fallback to default Rails migration paths
         | 
| 139 | 
            +
                      ::Rails.application.paths["db/migrate"].expanded
         | 
| 140 | 
            +
                    else
         | 
| 141 | 
            +
                      ["db/migrate"]
         | 
| 85 142 | 
             
                    end
         | 
| 86 143 | 
             
                  end
         | 
| 87 144 | 
             
                end
         | 
| @@ -14,7 +14,10 @@ module PgMultitenantSchemas | |
| 14 14 |  | 
| 15 15 | 
             
                  # Add rake tasks
         | 
| 16 16 | 
             
                  rake_tasks do
         | 
| 17 | 
            -
                     | 
| 17 | 
            +
                    # Load all task files
         | 
| 18 | 
            +
                    Dir[File.expand_path("../tasks/*.rake", __dir__)].each do |task_file|
         | 
| 19 | 
            +
                      load task_file
         | 
| 20 | 
            +
                    end
         | 
| 18 21 | 
             
                  end
         | 
| 19 22 |  | 
| 20 23 | 
             
                  # Add generators
         | 
| @@ -80,8 +80,42 @@ module PgMultitenantSchemas | |
| 80 80 | 
             
                    get_result_value(result, 0, 0)
         | 
| 81 81 | 
             
                  end
         | 
| 82 82 |  | 
| 83 | 
            +
                  # List all schemas in the database
         | 
| 84 | 
            +
                  def list_schemas
         | 
| 85 | 
            +
                    conn = connection
         | 
| 86 | 
            +
                    result = execute_sql(conn, <<~SQL)
         | 
| 87 | 
            +
                      SELECT schema_name FROM information_schema.schemata#{" "}
         | 
| 88 | 
            +
                      ORDER BY schema_name
         | 
| 89 | 
            +
                    SQL
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    extract_schemas_from_result(result)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 83 94 | 
             
                  private
         | 
| 84 95 |  | 
| 96 | 
            +
                  def extract_schemas_from_result(result)
         | 
| 97 | 
            +
                    schemas = []
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    if result.respond_to?(:rows)
         | 
| 100 | 
            +
                      # Rails ActiveRecord::Result
         | 
| 101 | 
            +
                      result.rows.each { |row| schemas << row[0] }
         | 
| 102 | 
            +
                    elsif result.respond_to?(:each)
         | 
| 103 | 
            +
                      # Raw PG::Result
         | 
| 104 | 
            +
                      result.each { |row| schemas << row["schema_name"] }
         | 
| 105 | 
            +
                    else
         | 
| 106 | 
            +
                      # Fallback for other result types
         | 
| 107 | 
            +
                      extract_schemas_fallback(result, schemas)
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                    schemas
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  def extract_schemas_fallback(result, schemas)
         | 
| 114 | 
            +
                    (0...result.ntuples).each do |i|
         | 
| 115 | 
            +
                      schemas << get_result_value(result, i, 0)
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 85 119 | 
             
                  # Execute SQL - handles both Rails connections and raw PG connections
         | 
| 86 120 | 
             
                  def execute_sql(conn, sql)
         | 
| 87 121 | 
             
                    if conn.respond_to?(:execute)
         | 
| @@ -8,9 +8,9 @@ Gem::Specification.new do |spec| | |
| 8 8 | 
             
              spec.authors = ["Ruben Paz"]
         | 
| 9 9 | 
             
              spec.email = ["rubenpazchuspe@outlook.com"]
         | 
| 10 10 |  | 
| 11 | 
            -
              spec.summary = "Modern PostgreSQL schema-based multitenancy for Rails  | 
| 11 | 
            +
              spec.summary = "Modern PostgreSQL schema-based multitenancy for Rails 7+ applications"
         | 
| 12 12 | 
             
              spec.description = "A modern Ruby gem that provides PostgreSQL schema-based multitenancy with automatic tenant " \
         | 
| 13 | 
            -
                                 "resolution and schema switching.  | 
| 13 | 
            +
                                 "resolution and schema switching. Compatible with Rails 7+ and Ruby 3.0+, focusing on security, " \
         | 
| 14 14 | 
             
                                 "performance, and developer experience. Perfect for modern SaaS applications requiring " \
         | 
| 15 15 | 
             
                                 "secure tenant isolation."
         | 
| 16 16 | 
             
              spec.homepage = "https://github.com/rubenpazch/pg_multitenant_schemas"
         | 
| @@ -34,8 +34,8 @@ Gem::Specification.new do |spec| | |
| 34 34 | 
             
              spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
         | 
| 35 35 | 
             
              spec.require_paths = ["lib"]
         | 
| 36 36 |  | 
| 37 | 
            -
              # Runtime dependencies -  | 
| 38 | 
            -
              spec.add_dependency "activerecord", ">=  | 
| 39 | 
            -
              spec.add_dependency "activesupport", ">=  | 
| 37 | 
            +
              # Runtime dependencies - Rails 7+ support with Rails 8 optimizations
         | 
| 38 | 
            +
              spec.add_dependency "activerecord", ">= 7.0", "< 9.0"
         | 
| 39 | 
            +
              spec.add_dependency "activesupport", ">= 7.0", "< 9.0"
         | 
| 40 40 | 
             
              spec.add_dependency "pg", "~> 1.5"
         | 
| 41 41 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: pg_multitenant_schemas
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.2. | 
| 4 | 
            +
              version: 0.2.3
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ruben Paz
         | 
| @@ -15,7 +15,7 @@ dependencies: | |
| 15 15 | 
             
                requirements:
         | 
| 16 16 | 
             
                - - ">="
         | 
| 17 17 | 
             
                  - !ruby/object:Gem::Version
         | 
| 18 | 
            -
                    version: ' | 
| 18 | 
            +
                    version: '7.0'
         | 
| 19 19 | 
             
                - - "<"
         | 
| 20 20 | 
             
                  - !ruby/object:Gem::Version
         | 
| 21 21 | 
             
                    version: '9.0'
         | 
| @@ -25,7 +25,7 @@ dependencies: | |
| 25 25 | 
             
                requirements:
         | 
| 26 26 | 
             
                - - ">="
         | 
| 27 27 | 
             
                  - !ruby/object:Gem::Version
         | 
| 28 | 
            -
                    version: ' | 
| 28 | 
            +
                    version: '7.0'
         | 
| 29 29 | 
             
                - - "<"
         | 
| 30 30 | 
             
                  - !ruby/object:Gem::Version
         | 
| 31 31 | 
             
                    version: '9.0'
         | 
| @@ -35,7 +35,7 @@ dependencies: | |
| 35 35 | 
             
                requirements:
         | 
| 36 36 | 
             
                - - ">="
         | 
| 37 37 | 
             
                  - !ruby/object:Gem::Version
         | 
| 38 | 
            -
                    version: ' | 
| 38 | 
            +
                    version: '7.0'
         | 
| 39 39 | 
             
                - - "<"
         | 
| 40 40 | 
             
                  - !ruby/object:Gem::Version
         | 
| 41 41 | 
             
                    version: '9.0'
         | 
| @@ -45,7 +45,7 @@ dependencies: | |
| 45 45 | 
             
                requirements:
         | 
| 46 46 | 
             
                - - ">="
         | 
| 47 47 | 
             
                  - !ruby/object:Gem::Version
         | 
| 48 | 
            -
                    version: ' | 
| 48 | 
            +
                    version: '7.0'
         | 
| 49 49 | 
             
                - - "<"
         | 
| 50 50 | 
             
                  - !ruby/object:Gem::Version
         | 
| 51 51 | 
             
                    version: '9.0'
         | 
| @@ -64,9 +64,9 @@ dependencies: | |
| 64 64 | 
             
                  - !ruby/object:Gem::Version
         | 
| 65 65 | 
             
                    version: '1.5'
         | 
| 66 66 | 
             
            description: A modern Ruby gem that provides PostgreSQL schema-based multitenancy
         | 
| 67 | 
            -
              with automatic tenant resolution and schema switching.  | 
| 68 | 
            -
              3. | 
| 69 | 
            -
              SaaS applications requiring secure tenant isolation.
         | 
| 67 | 
            +
              with automatic tenant resolution and schema switching. Compatible with Rails 7+
         | 
| 68 | 
            +
              and Ruby 3.0+, focusing on security, performance, and developer experience. Perfect
         | 
| 69 | 
            +
              for modern SaaS applications requiring secure tenant isolation.
         | 
| 70 70 | 
             
            email:
         | 
| 71 71 | 
             
            - rubenpazchuspe@outlook.com
         | 
| 72 72 | 
             
            executables: []
         | 
| @@ -99,6 +99,7 @@ files: | |
| 99 99 | 
             
            - docs/schema_switcher.md
         | 
| 100 100 | 
             
            - docs/tenant_resolver.md
         | 
| 101 101 | 
             
            - docs/testing.md
         | 
| 102 | 
            +
            - docs/testing_rails_tasks.md
         | 
| 102 103 | 
             
            - examples/context_management.rb
         | 
| 103 104 | 
             
            - examples/migration_workflow.rb
         | 
| 104 105 | 
             
            - examples/rails_integration/controller_examples.rb
         | 
| @@ -118,7 +119,6 @@ files: | |
| 118 119 | 
             
            - lib/pg_multitenant_schemas/schema_switcher.rb
         | 
| 119 120 | 
             
            - lib/pg_multitenant_schemas/tasks/advanced_tasks.rake
         | 
| 120 121 | 
             
            - lib/pg_multitenant_schemas/tasks/basic_tasks.rake
         | 
| 121 | 
            -
            - lib/pg_multitenant_schemas/tasks/pg_multitenant_schemas.rake
         | 
| 122 122 | 
             
            - lib/pg_multitenant_schemas/tasks/tenant_tasks.rake
         | 
| 123 123 | 
             
            - lib/pg_multitenant_schemas/tenant_resolver.rb
         | 
| 124 124 | 
             
            - lib/pg_multitenant_schemas/tenant_task_helpers.rb
         | 
| @@ -154,5 +154,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 154 154 | 
             
            requirements: []
         | 
| 155 155 | 
             
            rubygems_version: 3.6.7
         | 
| 156 156 | 
             
            specification_version: 4
         | 
| 157 | 
            -
            summary: Modern PostgreSQL schema-based multitenancy for Rails  | 
| 157 | 
            +
            summary: Modern PostgreSQL schema-based multitenancy for Rails 7+ applications
         | 
| 158 158 | 
             
            test_files: []
         | 
| @@ -1,65 +0,0 @@ | |
| 1 | 
            -
            # frozen_string_literal: true
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            # Load individual task files
         | 
| 4 | 
            -
            Dir[File.join(File.dirname(__FILE__), "*.rake")].each { |file| load file unless file == __FILE__ }
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            require_relative "../tenant_task_helpers"
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            namespace :tenants do
         | 
| 9 | 
            -
              desc "Create new tenant with attributes (JSON format)"
         | 
| 10 | 
            -
              task :new, [:attributes] => :environment do |_task, args|
         | 
| 11 | 
            -
                TenantTaskHelpers.create_tenant_with_attributes(args[:attributes])
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              desc "Drop tenant schema (DANGEROUS)"
         | 
| 15 | 
            -
              task :drop, [:schema_name] => :environment do |_task, args|
         | 
| 16 | 
            -
                TenantTaskHelpers.drop_tenant_schema(args[:schema_name])
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
              desc "Rollback migrations for a tenant"
         | 
| 20 | 
            -
              task :rollback, %i[schema_name steps] => :environment do |_task, args|
         | 
| 21 | 
            -
                steps = (args[:steps] || 1).to_i
         | 
| 22 | 
            -
                TenantTaskHelpers.rollback_tenant_migrations(args[:schema_name], steps)
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
            end
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            # Namespace aliases for convenience
         | 
| 27 | 
            -
            namespace :tenants do
         | 
| 28 | 
            -
              namespace :db do
         | 
| 29 | 
            -
                desc "Create tenant schema (alias for tenants:create)"
         | 
| 30 | 
            -
                task :create, [:schema_name] => "tenants:create"
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                desc "Run tenant migrations (alias for tenants:migrate)"
         | 
| 33 | 
            -
                task migrate: "tenants:migrate"
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                desc "Check tenant migration status (alias for tenants:status)"
         | 
| 36 | 
            -
                task status: "tenants:status"
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                desc "Setup all tenants (alias for tenants:setup)"
         | 
| 39 | 
            -
                task setup: "tenants:setup"
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                desc "Rollback tenant migrations (alias for tenants:rollback)"
         | 
| 42 | 
            -
                task :rollback, %i[schema_name steps] => "tenants:rollback"
         | 
| 43 | 
            -
              end
         | 
| 44 | 
            -
            end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
            # Legacy namespace for backward compatibility
         | 
| 47 | 
            -
            namespace :pg_multitenant_schemas do
         | 
| 48 | 
            -
              desc "DEPRECATED: Use 'tenants:list' instead"
         | 
| 49 | 
            -
              task list_schemas: "tenants:list"
         | 
| 50 | 
            -
             | 
| 51 | 
            -
              desc "DEPRECATED: Use 'tenants:migrate' instead"
         | 
| 52 | 
            -
              task migrate_all: "tenants:migrate"
         | 
| 53 | 
            -
             | 
| 54 | 
            -
              desc "DEPRECATED: Use 'tenants:setup' instead"
         | 
| 55 | 
            -
              task setup: "tenants:setup"
         | 
| 56 | 
            -
             | 
| 57 | 
            -
              desc "DEPRECATED: Use 'tenants:migrate_tenant[schema_name]' instead"
         | 
| 58 | 
            -
              task :migrate_tenant, [:schema_name] => "tenants:migrate_tenant"
         | 
| 59 | 
            -
             | 
| 60 | 
            -
              desc "DEPRECATED: Use 'tenants:create[schema_name]' instead"
         | 
| 61 | 
            -
              task :create_schema, [:schema_name] => "tenants:create"
         | 
| 62 | 
            -
             | 
| 63 | 
            -
              desc "DEPRECATED: Use 'tenants:drop[schema_name]' instead"
         | 
| 64 | 
            -
              task :drop_schema, [:schema_name] => "tenants:drop"
         | 
| 65 | 
            -
            end
         |