pg_multitenant_schemas 0.1.3 → 0.2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +46 -0
  4. data/README.md +269 -16
  5. data/docs/README.md +77 -0
  6. data/docs/configuration.md +340 -0
  7. data/docs/context.md +292 -0
  8. data/docs/errors.md +498 -0
  9. data/docs/integration_testing.md +454 -0
  10. data/docs/migrator.md +291 -0
  11. data/docs/rails_integration.md +468 -0
  12. data/docs/schema_switcher.md +182 -0
  13. data/docs/tenant_resolver.md +394 -0
  14. data/docs/testing.md +358 -0
  15. data/examples/context_management.rb +198 -0
  16. data/examples/migration_workflow.rb +50 -0
  17. data/examples/rails_integration/controller_examples.rb +368 -0
  18. data/examples/schema_operations.rb +124 -0
  19. data/lib/pg_multitenant_schemas/configuration.rb +4 -4
  20. data/lib/pg_multitenant_schemas/migration_display_reporter.rb +30 -0
  21. data/lib/pg_multitenant_schemas/migration_executor.rb +81 -0
  22. data/lib/pg_multitenant_schemas/migration_schema_operations.rb +54 -0
  23. data/lib/pg_multitenant_schemas/migration_status_reporter.rb +65 -0
  24. data/lib/pg_multitenant_schemas/migrator.rb +89 -0
  25. data/lib/pg_multitenant_schemas/schema_switcher.rb +40 -66
  26. data/lib/pg_multitenant_schemas/tasks/advanced_tasks.rake +21 -0
  27. data/lib/pg_multitenant_schemas/tasks/basic_tasks.rake +20 -0
  28. data/lib/pg_multitenant_schemas/tasks/pg_multitenant_schemas.rake +53 -143
  29. data/lib/pg_multitenant_schemas/tasks/tenant_tasks.rake +65 -0
  30. data/lib/pg_multitenant_schemas/tenant_task_helpers.rb +102 -0
  31. data/lib/pg_multitenant_schemas/version.rb +1 -1
  32. data/lib/pg_multitenant_schemas.rb +10 -5
  33. data/pg_multitenant_schemas.gemspec +10 -9
  34. data/rails_integration/app/controllers/application_controller.rb +6 -0
  35. data/rails_integration/app/models/tenant.rb +6 -0
  36. metadata +39 -17
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgMultitenantSchemas
4
+ # Migration status reporting functionality
5
+ module MigrationStatusReporter
6
+ # Check migration status across all tenants
7
+ def migration_status(verbose: true)
8
+ schemas = tenant_schemas
9
+ results = collect_migration_status_for_schemas(schemas)
10
+ display_status_report(results, verbose)
11
+ results
12
+ end
13
+
14
+ private
15
+
16
+ def collect_migration_status_for_schemas(schemas)
17
+ schemas.map do |schema|
18
+ collect_status_for_schema(schema)
19
+ end
20
+ end
21
+
22
+ def collect_status_for_schema(schema)
23
+ original_schema = current_schema
24
+
25
+ begin
26
+ switch_to_schema(schema)
27
+ build_schema_status(schema)
28
+ rescue StandardError => e
29
+ { schema: schema, error: e.message, status: :error }
30
+ ensure
31
+ switch_to_schema(original_schema) if original_schema
32
+ end
33
+ end
34
+
35
+ def build_schema_status(schema)
36
+ pending = pending_migrations
37
+ applied = applied_migrations
38
+
39
+ {
40
+ schema: schema,
41
+ pending_count: pending.count,
42
+ applied_count: applied.count,
43
+ status: pending.any? ? :pending : :up_to_date
44
+ }
45
+ end
46
+
47
+ def display_status_report(results, verbose)
48
+ return unless verbose
49
+
50
+ puts "📋 Migration Status Report:"
51
+ results.each { |result| display_schema_status(result) }
52
+ end
53
+
54
+ def display_schema_status(result)
55
+ case result[:status]
56
+ when :up_to_date
57
+ puts " ✅ #{result[:schema]}: Up to date (#{result[:applied_count]} applied)"
58
+ when :pending
59
+ puts " ⏳ #{result[:schema]}: #{result[:pending_count]} pending, #{result[:applied_count]} applied"
60
+ when :error
61
+ puts " ❌ #{result[:schema]}: Error - #{result[:error]}"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "migration_status_reporter"
4
+ require_relative "migration_schema_operations"
5
+ require_relative "migration_executor"
6
+ require_relative "migration_display_reporter"
7
+
8
+ module PgMultitenantSchemas
9
+ # Enhanced migration management for multi-tenant schemas
10
+ # Provides automated migration operations across tenant schemas
11
+ class Migrator
12
+ extend MigrationStatusReporter
13
+ extend MigrationSchemaOperations
14
+ extend MigrationExecutor
15
+ extend MigrationDisplayReporter
16
+
17
+ class << self
18
+ # Run migrations on all tenant schemas
19
+ def migrate_all(verbose: true, ignore_errors: false)
20
+ schemas = tenant_schemas
21
+ results = process_schemas_migration(schemas, verbose, ignore_errors)
22
+
23
+ display_migration_summary(results, verbose)
24
+ results
25
+ end
26
+
27
+ # Run migrations on a specific tenant schema
28
+ def migrate_tenant(schema_name, verbose: true, raise_on_error: true)
29
+ return handle_missing_schema(schema_name, verbose, raise_on_error) unless schema_exists?(schema_name)
30
+
31
+ execute_tenant_migration(schema_name, verbose, raise_on_error)
32
+ end
33
+
34
+ # Setup tenant with schema creation and migrations
35
+ def setup_tenant(schema_name, verbose: true)
36
+ puts "🏗️ Setting up tenant: #{schema_name}" if verbose
37
+
38
+ begin
39
+ create_tenant_schema_if_needed(schema_name, verbose)
40
+ result = migrate_tenant(schema_name, verbose: verbose)
41
+ puts " 🎉 Tenant setup completed!" if verbose
42
+ result
43
+ rescue StandardError => e
44
+ puts " ❌ Setup failed: #{e.message}" if verbose
45
+ raise
46
+ end
47
+ end
48
+
49
+ # Setup all tenants from Tenant model
50
+ def setup_all_tenants(verbose: true)
51
+ validate_tenant_model_exists
52
+ tenants = Tenant.all
53
+ puts "🏗️ Setting up #{tenants.count} tenants..." if verbose
54
+
55
+ results = process_setup_for_tenants(tenants, verbose)
56
+ display_setup_summary(results, verbose)
57
+ results
58
+ end
59
+
60
+ # Create a new tenant with schema and migrations
61
+ def create_tenant_with_schema(attributes, verbose: true)
62
+ validate_tenant_model_exists
63
+ tenant = Tenant.create!(attributes)
64
+ schema_name = extract_schema_name(tenant)
65
+
66
+ puts "🆕 Creating new tenant: #{schema_name}" if verbose
67
+ setup_tenant(schema_name, verbose: verbose)
68
+ tenant
69
+ end
70
+
71
+ # Rollback migrations for a specific tenant
72
+ def rollback_tenant(schema_name, steps: 1, verbose: true)
73
+ puts "⏪ Rolling back #{steps} steps for #{schema_name}" if verbose
74
+ original_schema = current_schema
75
+
76
+ begin
77
+ switch_to_schema(schema_name)
78
+ ActiveRecord::Base.connection.migration_context.rollback(migration_paths, steps)
79
+ puts " ✅ Rollback completed" if verbose
80
+ rescue StandardError => e
81
+ puts " ❌ Rollback failed: #{e.message}" if verbose
82
+ raise
83
+ ensure
84
+ switch_to_schema(original_schema) if original_schema
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -22,118 +22,92 @@ module PgMultitenantSchemas
22
22
  raise ConnectionError, "Failed to get database connection: #{e.message}"
23
23
  end
24
24
 
25
- # Switches the search_path - supports both APIs for backward compatibility
26
- def switch_schema(conn_or_schema, schema = nil)
27
- if conn_or_schema.is_a?(String)
28
- # New API: switch_schema('schema_name')
29
- schema = conn_or_schema
30
- conn = connection
31
- else
32
- # Old API: switch_schema(conn, 'schema_name')
33
- conn = conn_or_schema
34
- end
35
-
36
- raise ArgumentError, "Schema name cannot be empty" if schema.nil? || schema.strip.empty?
25
+ # Switches the search_path to the specified schema
26
+ def switch_schema(schema_name)
27
+ raise ArgumentError, "Schema name cannot be empty" if schema_name.nil? || schema_name.strip.empty?
37
28
 
38
- # Use simple quoting for PostgreSQL identifiers
39
- quoted_schema = "\"#{schema.gsub('"', '""')}\""
29
+ conn = connection
30
+ quoted_schema = "\"#{schema_name.gsub('"', '""')}\""
40
31
  execute_sql(conn, "SET search_path TO #{quoted_schema};")
41
32
  end
42
33
 
43
- # Reset to default schema - supports both APIs
44
- def reset_schema(conn = nil)
45
- conn ||= connection
34
+ # Reset to default schema
35
+ def reset_schema
36
+ conn = connection
46
37
  execute_sql(conn, "SET search_path TO public;")
47
38
  end
48
39
 
49
- # Create a new schema - supports both APIs
50
- def create_schema(conn_or_schema, schema_name = nil)
51
- if conn_or_schema.is_a?(String)
52
- # New API: create_schema('schema_name')
53
- schema_name = conn_or_schema
54
- conn = connection
55
- else
56
- # Old API: create_schema(conn, 'schema_name')
57
- conn = conn_or_schema
58
- end
59
-
40
+ # Create a new schema
41
+ def create_schema(schema_name)
60
42
  raise ArgumentError, "Schema name cannot be empty" if schema_name.nil? || schema_name.strip.empty?
61
43
 
44
+ conn = connection
62
45
  quoted_schema = "\"#{schema_name.gsub('"', '""')}\""
63
46
  execute_sql(conn, "CREATE SCHEMA IF NOT EXISTS #{quoted_schema};")
64
47
  end
65
48
 
66
- # Drop a schema - supports both APIs
67
- def drop_schema(conn_or_schema, schema_name_or_options = nil, cascade: true)
68
- if conn_or_schema.is_a?(String)
69
- # New API: drop_schema('schema_name', cascade: true)
70
- schema_name = conn_or_schema
71
- options = schema_name_or_options || {}
72
- cascade = options.fetch(:cascade, cascade)
73
- conn = connection
74
- else
75
- # Old API: drop_schema(conn, 'schema_name', cascade: true)
76
- conn = conn_or_schema
77
- schema_name = schema_name_or_options
78
- end
79
-
49
+ # Drop a schema
50
+ def drop_schema(schema_name, cascade: true)
80
51
  raise ArgumentError, "Schema name cannot be empty" if schema_name.nil? || schema_name.strip.empty?
81
52
 
53
+ conn = connection
82
54
  cascade_option = cascade ? "CASCADE" : "RESTRICT"
83
55
  quoted_schema = "\"#{schema_name.gsub('"', '""')}\""
84
56
  execute_sql(conn, "DROP SCHEMA IF EXISTS #{quoted_schema} #{cascade_option};")
85
57
  end
86
58
 
87
- # Check if schema exists - supports both APIs
88
- def schema_exists?(conn_or_schema, schema_name = nil)
89
- if conn_or_schema.is_a?(String)
90
- # New API: schema_exists?('schema_name')
91
- schema_name = conn_or_schema
92
- conn = connection
93
- else
94
- # Old API: schema_exists?(conn, 'schema_name')
95
- conn = conn_or_schema
96
- end
97
-
59
+ # Check if schema exists
60
+ def schema_exists?(schema_name)
98
61
  return false if schema_name.nil? || schema_name.strip.empty?
99
62
 
63
+ conn = connection
100
64
  result = execute_sql(conn, <<~SQL)
101
65
  SELECT EXISTS(
102
66
  SELECT 1 FROM information_schema.schemata#{" "}
103
67
  WHERE schema_name = '#{schema_name}'
104
68
  ) AS schema_exists
105
69
  SQL
106
- get_result_value(result, 0, 0) == "t"
70
+
71
+ value = get_result_value(result, 0, 0)
72
+ # Handle both boolean values and string representations
73
+ [true, "t", "true"].include?(value)
107
74
  end
108
75
 
109
- # Get current schema - supports both APIs
110
- def current_schema(conn = nil)
111
- conn ||= connection
76
+ # Get current schema
77
+ def current_schema
78
+ conn = connection
112
79
  result = execute_sql(conn, "SELECT current_schema()")
113
80
  get_result_value(result, 0, 0)
114
81
  end
115
82
 
116
83
  private
117
84
 
118
- # Execute SQL with Rails 8 compatibility
85
+ # Execute SQL - handles both Rails connections and raw PG connections
119
86
  def execute_sql(conn, sql)
120
87
  if conn.respond_to?(:execute)
121
- # Rails connection - use execute
88
+ # Rails/ActiveRecord connection
122
89
  conn.execute(sql)
123
- else
124
- # Raw PG connection - use exec
90
+ elsif conn.respond_to?(:exec)
91
+ # Raw PG::Connection
125
92
  conn.exec(sql)
93
+ else
94
+ raise ArgumentError, "Connection must respond to either :execute or :exec"
126
95
  end
127
96
  end
128
97
 
129
- # Get value from result with compatibility for different result types
98
+ # Get value from result - handles both Rails and PG results
130
99
  def get_result_value(result, row, col)
131
- if result.respond_to?(:getvalue)
132
- # PG::Result
100
+ if result.respond_to?(:rows)
101
+ # Rails ActiveRecord::Result
102
+ result.rows[row][col]
103
+ elsif result.respond_to?(:getvalue)
104
+ # Raw PG::Result
133
105
  result.getvalue(row, col)
106
+ elsif result.respond_to?(:[])
107
+ # Alternative PG::Result access
108
+ result[row][col]
134
109
  else
135
- # ActiveRecord::Result
136
- result.rows[row][col]
110
+ raise ArgumentError, "Result must respond to either :rows, :getvalue, or :[]"
137
111
  end
138
112
  end
139
113
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Advanced tenant management tasks
4
+ namespace :tenants do
5
+ desc "Run migrations for a specific tenant"
6
+ task :migrate_tenant, [:schema_name] => :environment do |_task, args|
7
+ require_relative "../tenant_task_helpers"
8
+ TenantTaskHelpers.migrate_specific_tenant(args[:schema_name])
9
+ end
10
+
11
+ desc "Setup new tenant with schema and migrations"
12
+ task :create, [:schema_name] => :environment do |_task, args|
13
+ require_relative "../tenant_task_helpers"
14
+ TenantTaskHelpers.create_tenant_with_schema(args[:schema_name])
15
+ end
16
+
17
+ desc "Setup schemas and run migrations for all existing tenants"
18
+ task setup: :environment do
19
+ PgMultitenantSchemas::Migrator.setup_all_tenants
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Basic tenant management tasks
4
+ namespace :tenants do
5
+ desc "List all tenant schemas"
6
+ task list: :environment do
7
+ require_relative "../tenant_task_helpers"
8
+ TenantTaskHelpers.list_tenant_schemas
9
+ end
10
+
11
+ desc "Show migration status for all tenants"
12
+ task status: :environment do
13
+ PgMultitenantSchemas::Migrator.migration_status
14
+ end
15
+
16
+ desc "Run migrations for all tenant schemas"
17
+ task migrate: :environment do
18
+ PgMultitenantSchemas::Migrator.migrate_all
19
+ end
20
+ end
@@ -1,155 +1,65 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- namespace :pg_multitenant_schemas do
4
- desc "List all tenant schemas"
5
- task :list_schemas => :environment do
6
- puts "Available tenant schemas:"
7
-
8
- connection = ActiveRecord::Base.connection
9
- schemas = connection.execute(
10
- "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast_temp_1', 'pg_temp_1', 'public') AND schema_name NOT LIKE 'pg_toast%'"
11
- ).map { |row| row['schema_name'] }
12
-
13
- if schemas.any?
14
- schemas.each { |schema| puts " - #{schema}" }
15
- else
16
- puts " No tenant schemas found"
17
- end
18
- end
3
+ # Load individual task files
4
+ Dir[File.join(File.dirname(__FILE__), "*.rake")].each { |file| load file unless file == __FILE__ }
19
5
 
20
- desc "Create schema for a tenant"
21
- task :create_schema, [:schema_name] => :environment do |task, args|
22
- schema_name = args[:schema_name]
23
-
24
- if schema_name.blank?
25
- puts "Usage: rails pg_multitenant_schemas:create_schema[schema_name]"
26
- exit 1
27
- end
28
-
29
- begin
30
- connection = ActiveRecord::Base.connection
31
- PgMultitenantSchemas::SchemaSwitcher.create_schema(connection, schema_name)
32
- puts "Created schema: #{schema_name}"
33
- rescue => e
34
- puts "Error creating schema #{schema_name}: #{e.message}"
35
- exit 1
36
- end
37
- end
6
+ require_relative "../tenant_task_helpers"
38
7
 
39
- desc "Drop schema for a tenant"
40
- task :drop_schema, [:schema_name] => :environment do |task, args|
41
- schema_name = args[:schema_name]
42
-
43
- if schema_name.blank?
44
- puts "Usage: rails pg_multitenant_schemas:drop_schema[schema_name]"
45
- exit 1
46
- end
47
-
48
- if schema_name == 'public'
49
- puts "Cannot drop public schema"
50
- exit 1
51
- end
52
-
53
- begin
54
- connection = ActiveRecord::Base.connection
55
- PgMultitenantSchemas::SchemaSwitcher.drop_schema(connection, schema_name)
56
- puts "Dropped schema: #{schema_name}"
57
- rescue => e
58
- puts "Error dropping schema #{schema_name}: #{e.message}"
59
- exit 1
60
- end
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])
61
12
  end
62
13
 
63
- desc "Run migrations for all tenant schemas"
64
- task :migrate_all => :environment do
65
- puts "Running migrations for all tenant schemas..."
66
-
67
- connection = ActiveRecord::Base.connection
68
- schemas = connection.execute(
69
- "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema', 'pg_catalog', 'pg_toast_temp_1', 'pg_temp_1', 'public') AND schema_name NOT LIKE 'pg_toast%'"
70
- ).map { |row| row['schema_name'] }
71
-
72
- original_schema = PgMultitenantSchemas.current_schema rescue 'public'
73
-
74
- schemas.each do |schema|
75
- puts "Migrating schema: #{schema}"
76
- begin
77
- PgMultitenantSchemas.with_tenant(schema) do
78
- # Rails 8 compatible migration execution
79
- if Rails.version >= "8.0"
80
- # Use Rails 8 migration API
81
- ActiveRecord::Tasks::DatabaseTasks.migrate
82
- else
83
- # Fallback for older Rails versions
84
- ActiveRecord::Migrator.migrate(Rails.application.paths["db/migrate"])
85
- end
86
- end
87
- puts " ✓ Completed migration for #{schema}"
88
- rescue => e
89
- puts " ✗ Error migrating #{schema}: #{e.message}"
90
- end
91
- end
92
-
93
- # Restore original schema
94
- PgMultitenantSchemas.current_schema = original_schema if defined?(PgMultitenantSchemas)
95
-
96
- puts "Completed migrations for #{schemas.count} schemas"
14
+ desc "Drop tenant schema (DANGEROUS)"
15
+ task :drop, [:schema_name] => :environment do |_task, args|
16
+ TenantTaskHelpers.drop_tenant_schema(args[:schema_name])
97
17
  end
98
18
 
99
- desc "Setup multitenancy - create tenant schemas for existing tenants"
100
- task :setup => :environment do
101
- puts "Setting up multitenancy schemas..."
102
-
103
- if defined?(Tenant)
104
- tenants = Tenant.all
105
-
106
- tenants.each do |tenant|
107
- begin
108
- puts "Creating schema for tenant: #{tenant.subdomain}"
109
- PgMultitenantSchemas::SchemaSwitcher.create_schema(
110
- ActiveRecord::Base.connection,
111
- tenant.subdomain
112
- )
113
- puts " ✓ Schema created for #{tenant.subdomain}"
114
- rescue => e
115
- if e.message.include?("already exists")
116
- puts " - Schema #{tenant.subdomain} already exists"
117
- else
118
- puts " ✗ Could not create schema for #{tenant.subdomain}: #{e.message}"
119
- end
120
- end
121
- end
122
-
123
- puts "Setup completed for #{tenants.count} tenants"
124
- else
125
- puts "Tenant model not found. Make sure your Tenant model is defined."
126
- end
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)
127
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"
128
40
 
129
- desc "Run migrations for a specific tenant schema"
130
- task :migrate_tenant, [:schema_name] => :environment do |task, args|
131
- schema_name = args[:schema_name]
132
-
133
- if schema_name.blank?
134
- puts "Usage: rails pg_multitenant_schemas:migrate_tenant[schema_name]"
135
- exit 1
136
- end
137
-
138
- puts "Running migrations for tenant: #{schema_name}"
139
-
140
- begin
141
- PgMultitenantSchemas.with_tenant(schema_name) do
142
- # Rails 8 compatible migration execution
143
- if Rails.version >= "8.0"
144
- ActiveRecord::Tasks::DatabaseTasks.migrate
145
- else
146
- ActiveRecord::Migrator.migrate(Rails.application.paths["db/migrate"])
147
- end
148
- end
149
- puts "✓ Completed migration for #{schema_name}"
150
- rescue => e
151
- puts "✗ Error migrating #{schema_name}: #{e.message}"
152
- exit 1
153
- end
41
+ desc "Rollback tenant migrations (alias for tenants:rollback)"
42
+ task :rollback, %i[schema_name steps] => "tenants:rollback"
154
43
  end
155
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
@@ -0,0 +1,65 @@
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