pg_multitenant_schemas 0.1.3 → 0.2.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.actrc +17 -0
  3. data/.env.local.example +21 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +86 -0
  6. data/LOCAL_TESTING_SUMMARY.md +141 -0
  7. data/README.md +269 -16
  8. data/TESTING_LOCALLY.md +208 -0
  9. data/docs/README.md +81 -0
  10. data/docs/configuration.md +340 -0
  11. data/docs/context.md +292 -0
  12. data/docs/errors.md +498 -0
  13. data/docs/github_actions_permissions_fix.md +136 -0
  14. data/docs/github_actions_setup.md +181 -0
  15. data/docs/integration_testing.md +454 -0
  16. data/docs/local_workflow_testing.md +314 -0
  17. data/docs/migrator.md +291 -0
  18. data/docs/rails_integration.md +468 -0
  19. data/docs/schema_switcher.md +182 -0
  20. data/docs/tenant_resolver.md +394 -0
  21. data/docs/testing.md +358 -0
  22. data/examples/context_management.rb +198 -0
  23. data/examples/migration_workflow.rb +50 -0
  24. data/examples/rails_integration/controller_examples.rb +368 -0
  25. data/examples/schema_operations.rb +124 -0
  26. data/lib/pg_multitenant_schemas/configuration.rb +4 -4
  27. data/lib/pg_multitenant_schemas/migration_display_reporter.rb +30 -0
  28. data/lib/pg_multitenant_schemas/migration_executor.rb +81 -0
  29. data/lib/pg_multitenant_schemas/migration_schema_operations.rb +54 -0
  30. data/lib/pg_multitenant_schemas/migration_status_reporter.rb +65 -0
  31. data/lib/pg_multitenant_schemas/migrator.rb +89 -0
  32. data/lib/pg_multitenant_schemas/schema_switcher.rb +40 -66
  33. data/lib/pg_multitenant_schemas/tasks/advanced_tasks.rake +21 -0
  34. data/lib/pg_multitenant_schemas/tasks/basic_tasks.rake +20 -0
  35. data/lib/pg_multitenant_schemas/tasks/pg_multitenant_schemas.rake +53 -143
  36. data/lib/pg_multitenant_schemas/tasks/tenant_tasks.rake +65 -0
  37. data/lib/pg_multitenant_schemas/tenant_task_helpers.rb +102 -0
  38. data/lib/pg_multitenant_schemas/version.rb +1 -1
  39. data/lib/pg_multitenant_schemas.rb +10 -5
  40. data/pg_multitenant_schemas.gemspec +10 -9
  41. data/pre-push-check.sh +95 -0
  42. data/rails_integration/app/controllers/application_controller.rb +6 -0
  43. data/rails_integration/app/models/tenant.rb +6 -0
  44. data/test-github-setup.sh +85 -0
  45. data/validate-github-commands.sh +47 -0
  46. metadata +49 -17
data/docs/context.md ADDED
@@ -0,0 +1,292 @@
1
+ # Context - Thread-Safe Tenant Management
2
+
3
+ **File**: `lib/pg_multitenant_schemas/context.rb`
4
+
5
+ ## 📋 Overview
6
+
7
+ The `Context` class provides thread-safe tenant context management, maintaining current tenant and schema state across request lifecycle. It's the high-level interface for tenant switching and context management.
8
+
9
+ ## 🎯 Purpose
10
+
11
+ - **Thread Safety**: Maintains separate tenant context per thread
12
+ - **Context Management**: Tracks current tenant and schema state
13
+ - **Automatic Restoration**: Ensures context is properly restored after operations
14
+ - **High-Level API**: Provides convenient methods for tenant switching
15
+
16
+ ## 🔧 Key Methods
17
+
18
+ ### Current State Management
19
+
20
+ ```ruby
21
+ # Get/Set current tenant
22
+ Context.current_tenant
23
+ Context.current_tenant = tenant_object
24
+
25
+ # Get/Set current schema
26
+ Context.current_schema
27
+ Context.current_schema = 'schema_name'
28
+
29
+ # Reset to default state
30
+ Context.reset!
31
+ ```
32
+
33
+ ### Tenant Switching
34
+
35
+ ```ruby
36
+ # Switch to specific tenant
37
+ Context.switch_to_tenant(tenant_object)
38
+
39
+ # Switch to specific schema
40
+ Context.switch_to_schema('schema_name')
41
+
42
+ # Execute block in tenant context
43
+ Context.with_tenant(tenant_or_schema) do
44
+ # Code executes in tenant context
45
+ User.all # Queries tenant's users
46
+ end
47
+ # Automatically restores previous context
48
+ ```
49
+
50
+ ### Schema Management
51
+
52
+ ```ruby
53
+ # Create tenant schema
54
+ Context.create_tenant_schema(tenant_or_schema)
55
+
56
+ # Drop tenant schema
57
+ Context.drop_tenant_schema(tenant_or_schema, cascade: true)
58
+ ```
59
+
60
+ ## 🏗️ Implementation Details
61
+
62
+ ### Thread-Local Storage
63
+
64
+ Context uses Ruby's `Thread.current` to store tenant state:
65
+
66
+ ```ruby
67
+ def current_tenant
68
+ Thread.current[:pg_multitenant_current_tenant]
69
+ end
70
+
71
+ def current_schema
72
+ Thread.current[:pg_multitenant_current_schema] ||
73
+ PgMultitenantSchemas.configuration.default_schema
74
+ end
75
+ ```
76
+
77
+ This ensures:
78
+ - **Isolation**: Each thread has independent tenant context
79
+ - **Concurrency**: Multiple requests can have different tenant contexts
80
+ - **Safety**: No cross-request tenant bleeding
81
+
82
+ ### Context Restoration
83
+
84
+ The `with_tenant` method provides automatic context restoration:
85
+
86
+ ```ruby
87
+ def with_tenant(tenant_or_schema)
88
+ # Store current state
89
+ previous_tenant = current_tenant
90
+ previous_schema = current_schema
91
+
92
+ begin
93
+ # Switch to new context
94
+ switch_to_schema(schema_name)
95
+ self.current_tenant = tenant
96
+ yield if block_given?
97
+ ensure
98
+ # Always restore previous context
99
+ restore_previous_context(previous_tenant, previous_schema)
100
+ end
101
+ end
102
+ ```
103
+
104
+ ### Flexible Input Handling
105
+
106
+ Context methods accept multiple input types:
107
+
108
+ - **Tenant Objects**: `tenant.subdomain` is used as schema name
109
+ - **String/Symbol**: Used directly as schema name
110
+ - **Nil**: Switches to default schema
111
+
112
+ ## 🔄 Usage Patterns
113
+
114
+ ### Basic Tenant Switching
115
+
116
+ ```ruby
117
+ # Using tenant object
118
+ tenant = Tenant.find_by(subdomain: 'acme')
119
+ PgMultitenantSchemas::Context.switch_to_tenant(tenant)
120
+
121
+ # Using schema name directly
122
+ PgMultitenantSchemas::Context.switch_to_schema('acme_corp')
123
+
124
+ # Check current state
125
+ puts PgMultitenantSchemas::Context.current_schema # => "acme_corp"
126
+ ```
127
+
128
+ ### Block-Based Context
129
+
130
+ ```ruby
131
+ # Execute code in tenant context
132
+ PgMultitenantSchemas::Context.with_tenant('acme_corp') do
133
+ # All database operations happen in acme_corp schema
134
+ users = User.all
135
+ orders = Order.where(status: 'pending')
136
+
137
+ # Create new records in tenant schema
138
+ User.create!(email: 'user@acme.com')
139
+ end
140
+
141
+ # Context automatically restored to previous state
142
+ puts PgMultitenantSchemas::Context.current_schema # => "public"
143
+ ```
144
+
145
+ ### Nested Contexts
146
+
147
+ ```ruby
148
+ # Nested tenant contexts work correctly
149
+ PgMultitenantSchemas::Context.with_tenant('tenant_a') do
150
+ puts "In tenant A: #{Context.current_schema}"
151
+
152
+ PgMultitenantSchemas::Context.with_tenant('tenant_b') do
153
+ puts "In tenant B: #{Context.current_schema}"
154
+ end
155
+
156
+ puts "Back in tenant A: #{Context.current_schema}"
157
+ end
158
+ puts "Back in original context: #{Context.current_schema}"
159
+ ```
160
+
161
+ ### Error Handling
162
+
163
+ ```ruby
164
+ # Context is restored even if errors occur
165
+ PgMultitenantSchemas::Context.with_tenant('acme_corp') do
166
+ raise "Something went wrong!"
167
+ rescue => e
168
+ puts "Error: #{e.message}"
169
+ end
170
+
171
+ # Context is still properly restored
172
+ puts PgMultitenantSchemas::Context.current_schema # => "public"
173
+ ```
174
+
175
+ ## 🔗 Integration Points
176
+
177
+ ### Controller Integration
178
+
179
+ ```ruby
180
+ class ApplicationController < ActionController::Base
181
+ around_action :set_tenant_context
182
+
183
+ private
184
+
185
+ def set_tenant_context
186
+ tenant = resolve_tenant_from_request
187
+ PgMultitenantSchemas::Context.with_tenant(tenant) do
188
+ yield
189
+ end
190
+ end
191
+ end
192
+ ```
193
+
194
+ ### Background Jobs
195
+
196
+ ```ruby
197
+ class TenantJob < ApplicationJob
198
+ def perform(tenant_id, *args)
199
+ tenant = Tenant.find(tenant_id)
200
+ PgMultitenantSchemas::Context.with_tenant(tenant) do
201
+ # Job executes in tenant context
202
+ process_tenant_data
203
+ end
204
+ end
205
+ end
206
+ ```
207
+
208
+ ### Model Callbacks
209
+
210
+ ```ruby
211
+ class Order < ApplicationRecord
212
+ after_create :send_notification
213
+
214
+ private
215
+
216
+ def send_notification
217
+ # This runs in the same tenant context as the creation
218
+ current_tenant = PgMultitenantSchemas::Context.current_tenant
219
+ NotificationMailer.order_created(self, current_tenant).deliver_later
220
+ end
221
+ end
222
+ ```
223
+
224
+ ## ⚙️ Configuration
225
+
226
+ Context behavior is influenced by global configuration:
227
+
228
+ ```ruby
229
+ PgMultitenantSchemas.configure do |config|
230
+ config.default_schema = 'public' # Default context
231
+ config.connection_class = 'ApplicationRecord'
232
+ end
233
+ ```
234
+
235
+ ## 🚨 Important Considerations
236
+
237
+ ### Thread Safety
238
+
239
+ - **Safe**: Each thread maintains independent context
240
+ - **Isolation**: No cross-thread context interference
241
+ - **Connection Pools**: Works correctly with Rails connection pooling
242
+
243
+ ### Memory Management
244
+
245
+ - Context data is cleaned up when threads end
246
+ - No memory leaks from tenant context storage
247
+ - Minimal memory overhead per thread
248
+
249
+ ### Performance
250
+
251
+ - Context switching is very fast
252
+ - No database queries required for context management
253
+ - Minimal CPU and memory overhead
254
+
255
+ ## 🔍 Debugging
256
+
257
+ ### Check Current Context
258
+
259
+ ```ruby
260
+ # Current tenant and schema
261
+ puts "Tenant: #{PgMultitenantSchemas::Context.current_tenant&.subdomain}"
262
+ puts "Schema: #{PgMultitenantSchemas::Context.current_schema}"
263
+ ```
264
+
265
+ ### Debug Context Stack
266
+
267
+ ```ruby
268
+ # Add logging to track context changes
269
+ module PgMultitenantSchemas
270
+ class Context
271
+ class << self
272
+ alias_method :original_switch_to_schema, :switch_to_schema
273
+
274
+ def switch_to_schema(schema_name)
275
+ Rails.logger.debug "Switching to schema: #{schema_name}"
276
+ original_switch_to_schema(schema_name)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ ```
282
+
283
+ ## 🔗 Related Components
284
+
285
+ - **[SchemaSwitcher](schema_switcher.md)**: Low-level schema operations used by Context
286
+ - **[TenantResolver](tenant_resolver.md)**: Identifies tenants for context switching
287
+ - **[Rails Integration](rails_integration.md)**: Framework integration using Context
288
+ - **[Configuration](configuration.md)**: Context configuration options
289
+
290
+ ## 📝 Examples
291
+
292
+ See [examples/context_management.rb](../examples/) for complete usage examples.