tenant_partition 0.1.0 → 0.1.1
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 +4 -1
- data/README.md +20 -20
- data/lib/{activepartition → tenant_partition}/base.rb +7 -7
- data/lib/{activepartition → tenant_partition}/concerns/controller.rb +8 -8
- data/lib/{activepartition → tenant_partition}/concerns/partitioned.rb +8 -8
- data/lib/{activepartition → tenant_partition}/configuration.rb +2 -2
- data/lib/{activepartition → tenant_partition}/railtie.rb +5 -5
- data/lib/{activepartition → tenant_partition}/safety_guard.rb +7 -7
- data/lib/{activepartition → tenant_partition}/schema/statements.rb +3 -3
- data/lib/tenant_partition/tasks/maintenance.rake +14 -0
- data/lib/tenant_partition/version.rb +5 -0
- data/lib/{activepartition.rb → tenant_partition.rb} +15 -15
- data/sig/{activepartition.rbs → tenant_partition.rbs} +1 -1
- metadata +13 -13
- data/lib/activepartition/tasks/maintenance.rake +0 -14
- data/lib/activepartition/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4375402293baef4a0b28e50a004e57d25e2eff598f68d02d43056c7d5cdf51dc
|
|
4
|
+
data.tar.gz: c84e2003e7b8d812fec7edc2970fc6244a770d7bbfb81f64f9db0859663e8ae0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 326a4ed3905b92f44a59b4dc22489e7f68b208f2e8e0a1d6195089a965acefbea35ad43763dcb5499c89859358eb0fac7a8dd84f8bdb506418170d09cef9d07b
|
|
7
|
+
data.tar.gz: 2eb2a9e5cdf25f74b26d58599bed33fe45d2c5fad0eaf7630298964ffbb014c1dc2c8335c538ca1f20df2a2a0e763c0d3fa0c7840cb5457c9909524b3b3faf4a
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TenantPartition
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**TenantPartition** es un framework de infraestructura para Ruby on Rails (7.1+) diseñado para simplificar y automatizar la gestión de **Particionamiento por Lista (List Partitioning)** nativo de PostgreSQL.
|
|
4
4
|
|
|
5
5
|
Específicamente construido para arquitecturas **Multi-tenant**, este framework resuelve la complejidad de:
|
|
6
6
|
1. **Composite Primary Keys (CPK):** Configuración automática de claves compuestas (`id` + `partition_key`) requeridas por ActiveRecord para soportar particionamiento.
|
|
@@ -9,7 +9,7 @@ Específicamente construido para arquitecturas **Multi-tenant**, este framework
|
|
|
9
9
|
|
|
10
10
|
## 🚀 Características Principales
|
|
11
11
|
|
|
12
|
-
* **Fachada de Servicio:** API unificada (`
|
|
12
|
+
* **Fachada de Servicio:** API unificada (`TenantPartition.create!`, `destroy!`, `audit`, `cleanup!`) para gestionar el ciclo de vida completo de los tenants.
|
|
13
13
|
* **Introspección Inteligente:** Los modelos de negocio detectan automáticamente su configuración de infraestructura por convención (ej: `User` -> `Partition::User`).
|
|
14
14
|
* **Migration DSL:** Helper `create_partitioned_table` para definir tablas particionadas y sus tablas `DEFAULT` en una sola instrucción.
|
|
15
15
|
* **Seguridad Estricta:** Concern para controladores que valida Headers HTTP (`X-Tenant-ID`) para asegurar el contexto del tenant.
|
|
@@ -37,10 +37,10 @@ bundle install
|
|
|
37
37
|
|
|
38
38
|
## ⚙️ Configuración
|
|
39
39
|
|
|
40
|
-
Crea un inicializador en `config/initializers/
|
|
40
|
+
Crea un inicializador en `config/initializers/tenant_partition.rb`. Es **obligatorio** definir la clave de partición.
|
|
41
41
|
|
|
42
42
|
```ruby
|
|
43
|
-
|
|
43
|
+
TenantPartition.configure do |config|
|
|
44
44
|
# 1. La columna que discrimina los tenants (ej: :isp_id, :account_id, :tenant_id)
|
|
45
45
|
config.partition_key = :isp_id
|
|
46
46
|
|
|
@@ -69,12 +69,12 @@ end
|
|
|
69
69
|
|
|
70
70
|
### 2. Capa de Infraestructura (Modelos Partition)
|
|
71
71
|
|
|
72
|
-
Define modelos que hereden de `
|
|
72
|
+
Define modelos que hereden de `TenantPartition::Base`. Estos modelos son responsables de las operaciones DDL (Create/Drop tables). Por convención, se recomienda usar el namespace `Partition::`.
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
75
|
# app/models/partition/conversation.rb
|
|
76
76
|
module Partition
|
|
77
|
-
class Conversation <
|
|
77
|
+
class Conversation < TenantPartition::Base
|
|
78
78
|
# Hereda la configuración global (:isp_id) automáticamente.
|
|
79
79
|
end
|
|
80
80
|
end
|
|
@@ -90,7 +90,7 @@ Al incluir el concern, la gema busca automáticamente si existe un modelo de inf
|
|
|
90
90
|
```ruby
|
|
91
91
|
# app/models/conversation.rb
|
|
92
92
|
class Conversation < ApplicationRecord
|
|
93
|
-
include
|
|
93
|
+
include TenantPartition::Concerns::Partitioned
|
|
94
94
|
|
|
95
95
|
# ¡Listo! Rails ahora sabe que la Primary Key es [:id, :isp_id]
|
|
96
96
|
# y aplica scopes automáticos.
|
|
@@ -99,7 +99,7 @@ end
|
|
|
99
99
|
|
|
100
100
|
### 4. Orquestación (Ciclo de Vida del Tenant)
|
|
101
101
|
|
|
102
|
-
Ya no necesitas crear particiones tabla por tabla. Usa la **Fachada** `
|
|
102
|
+
Ya no necesitas crear particiones tabla por tabla. Usa la **Fachada** `TenantPartition` para gestionar la infraestructura de un tenant en **todos** los modelos registrados simultáneamente.
|
|
103
103
|
|
|
104
104
|
**Crear un nuevo Tenant (Provisioning):**
|
|
105
105
|
Ideal para usar en tu `RegistrationService` o `AfterCommit` de la creación del tenant.
|
|
@@ -111,7 +111,7 @@ def create_tenant
|
|
|
111
111
|
|
|
112
112
|
# Busca TODOS los modelos particionados y crea las tablas físicas para este ID.
|
|
113
113
|
# Es idempotente: si alguna ya existe, la salta sin error.
|
|
114
|
-
|
|
114
|
+
TenantPartition.create!(isp.id)
|
|
115
115
|
end
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -120,7 +120,7 @@ end
|
|
|
120
120
|
```ruby
|
|
121
121
|
# Esta operación realiza DETACH + DROP de las tablas físicas.
|
|
122
122
|
# ¡Es destructiva e irreversible!
|
|
123
|
-
|
|
123
|
+
TenantPartition.destroy!(old_isp.id)
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
### 5. Seguridad en Controladores
|
|
@@ -129,7 +129,7 @@ Protege tus API endpoints asegurando que siempre reciban el ID del tenant.
|
|
|
129
129
|
|
|
130
130
|
```ruby
|
|
131
131
|
class ApiController < ActionController::API
|
|
132
|
-
include
|
|
132
|
+
include TenantPartition::Concerns::Controller
|
|
133
133
|
|
|
134
134
|
# Valida que el request traiga el header 'X-Tenant-ID'.
|
|
135
135
|
# Devuelve 400 Bad Request si falta.
|
|
@@ -145,7 +145,7 @@ end
|
|
|
145
145
|
|
|
146
146
|
## 🛡 Mantenimiento y Recuperación
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
TenantPartition maneja el escenario de "Race Condition" donde llegan datos *antes* de que la partición exista. Esos datos caen automáticamente en la tabla `_default`.
|
|
149
149
|
|
|
150
150
|
### Auditoría
|
|
151
151
|
|
|
@@ -153,13 +153,13 @@ Verifica si tienes datos "fugados" en las tablas default:
|
|
|
153
153
|
|
|
154
154
|
```ruby
|
|
155
155
|
# Desde consola Rails
|
|
156
|
-
report =
|
|
156
|
+
report = TenantPartition.audit
|
|
157
157
|
# => { "Partition::Conversation" => 14, "Partition::Message" => 0 }
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
O vía Rake task:
|
|
161
161
|
```bash
|
|
162
|
-
bundle exec rails
|
|
162
|
+
bundle exec rails tenant_partition:audit
|
|
163
163
|
```
|
|
164
164
|
|
|
165
165
|
### Limpieza (Cleanup)
|
|
@@ -168,12 +168,12 @@ Mueve los datos huérfanos a sus particiones correspondientes de forma atómica
|
|
|
168
168
|
|
|
169
169
|
```ruby
|
|
170
170
|
# Ruby API (Ideal para Jobs nocturnos)
|
|
171
|
-
|
|
171
|
+
TenantPartition.cleanup!
|
|
172
172
|
```
|
|
173
173
|
|
|
174
174
|
O vía Rake task:
|
|
175
175
|
```bash
|
|
176
|
-
bundle exec rails
|
|
176
|
+
bundle exec rails tenant_partition:cleanup
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
## 📊 Observabilidad
|
|
@@ -182,13 +182,13 @@ Puedes suscribirte a los eventos para enviar métricas a tu sistema de monitoreo
|
|
|
182
182
|
|
|
183
183
|
```ruby
|
|
184
184
|
# config/initializers/notifications.rb
|
|
185
|
-
ActiveSupport::Notifications.subscribe(/
|
|
185
|
+
ActiveSupport::Notifications.subscribe(/tenant_partition/) do |name, start, finish, id, payload|
|
|
186
186
|
duration = (finish - start) * 1000
|
|
187
187
|
|
|
188
188
|
case name
|
|
189
|
-
when "create.
|
|
189
|
+
when "create.tenant_partition"
|
|
190
190
|
Rails.logger.info "📦 Partición creada: #{payload[:table]} para #{payload[:value]}"
|
|
191
|
-
when "populate.
|
|
191
|
+
when "populate.tenant_partition"
|
|
192
192
|
Rails.logger.info "🧹 Limpieza: #{payload[:count]} registros movidos en #{duration.round(2)}ms"
|
|
193
193
|
end
|
|
194
194
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module TenantPartition
|
|
4
4
|
# Clase base para la gestión de infraestructura de particionamiento en PostgreSQL.
|
|
5
5
|
#
|
|
6
6
|
# Proporciona una interfaz para manejar el ciclo de vida de las tablas físicas (Particiones).
|
|
7
7
|
#
|
|
8
8
|
# @abstract Hereda de esta clase para definir un recurso de partición.
|
|
9
9
|
# @example
|
|
10
|
-
# class Partition::Chat <
|
|
10
|
+
# class Partition::Chat < TenantPartition::Base
|
|
11
11
|
# # Opcional: Sobrescribir la clave global
|
|
12
12
|
# self.partition_key = :region_code
|
|
13
13
|
# end
|
|
@@ -19,7 +19,7 @@ module ActivePartition
|
|
|
19
19
|
def self.inherited(subclass)
|
|
20
20
|
super
|
|
21
21
|
# Intentamos definir el atributo por defecto si existe configuración global
|
|
22
|
-
key =
|
|
22
|
+
key = TenantPartition.configuration&.partition_key
|
|
23
23
|
subclass.attribute key if key
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -35,8 +35,8 @@ module ActivePartition
|
|
|
35
35
|
|
|
36
36
|
# Prioridad: 1. Clave de la clase, 2. Clave global
|
|
37
37
|
def partition_key
|
|
38
|
-
@partition_key ||=
|
|
39
|
-
raise(
|
|
38
|
+
@partition_key ||= TenantPartition.configuration&.partition_key ||
|
|
39
|
+
raise(TenantPartition::Error, "Clave de partición no configurada.")
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def partition_key=(value)
|
|
@@ -61,7 +61,7 @@ module ActivePartition
|
|
|
61
61
|
# Importante: Usamos el método partition_key (no la variable) para respetar overrides
|
|
62
62
|
payload = { partition_key: partition_key, value: value, table: parent_table }
|
|
63
63
|
|
|
64
|
-
ActiveSupport::Notifications.instrument("create.
|
|
64
|
+
ActiveSupport::Notifications.instrument("create.tenant_partition", payload) do
|
|
65
65
|
name = partition_name(value)
|
|
66
66
|
sql = "CREATE TABLE IF NOT EXISTS #{name} PARTITION OF #{parent_table} FOR VALUES IN ('#{value}');"
|
|
67
67
|
connection.execute(sql)
|
|
@@ -117,7 +117,7 @@ module ActivePartition
|
|
|
117
117
|
|
|
118
118
|
payload = { partition_key: key, value: val, parent_table: parent }
|
|
119
119
|
|
|
120
|
-
ActiveSupport::Notifications.instrument("populate.
|
|
120
|
+
ActiveSupport::Notifications.instrument("populate.tenant_partition", payload) do |notification_payload|
|
|
121
121
|
total_moved = 0
|
|
122
122
|
|
|
123
123
|
loop do
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module TenantPartition
|
|
4
4
|
module Concerns
|
|
5
5
|
# Helpers y validaciones para controladores en arquitecturas Multi-tenant.
|
|
6
6
|
#
|
|
@@ -8,15 +8,15 @@ module ActivePartition
|
|
|
8
8
|
# Solo acepta el ID de partición si viene en el Header HTTP configurado explícitamente.
|
|
9
9
|
#
|
|
10
10
|
# @example Configuración requerida
|
|
11
|
-
# # config/initializers/
|
|
12
|
-
#
|
|
11
|
+
# # config/initializers/tenant_partition.rb
|
|
12
|
+
# TenantPartition.configure do |config|
|
|
13
13
|
# config.partition_key = :isp_id
|
|
14
14
|
# config.header_name = 'X-Tenant-ID' # <--- Obligatorio
|
|
15
15
|
# end
|
|
16
16
|
#
|
|
17
17
|
# @example Uso en el controlador
|
|
18
18
|
# class ApiController < ActionController::API
|
|
19
|
-
# include
|
|
19
|
+
# include TenantPartition::Concerns::Controller
|
|
20
20
|
# before_action :require_partition_key!
|
|
21
21
|
# end
|
|
22
22
|
module Controller
|
|
@@ -29,14 +29,14 @@ module ActivePartition
|
|
|
29
29
|
|
|
30
30
|
# Devuelve el valor del ID de partición actual extraído exclusivamente de los Headers.
|
|
31
31
|
#
|
|
32
|
-
# Utiliza únicamente el nombre de header definido en +
|
|
32
|
+
# Utiliza únicamente el nombre de header definido en +TenantPartition.configuration.header_name+.
|
|
33
33
|
# No realiza inferencias ni busca en parámetros de la URL.
|
|
34
34
|
#
|
|
35
35
|
# @return [String, nil] El valor del header o nil si no está presente o configurado.
|
|
36
36
|
def current_partition_id
|
|
37
37
|
return @current_partition_id if defined?(@current_partition_id)
|
|
38
38
|
|
|
39
|
-
header_key =
|
|
39
|
+
header_key = TenantPartition.configuration.header_name
|
|
40
40
|
|
|
41
41
|
# Si no se configuró un nombre de header, no podemos buscar nada.
|
|
42
42
|
return @current_partition_id = nil unless header_key.present?
|
|
@@ -52,11 +52,11 @@ module ActivePartition
|
|
|
52
52
|
def require_partition_key!
|
|
53
53
|
return if current_partition_id.present?
|
|
54
54
|
|
|
55
|
-
header_key =
|
|
55
|
+
header_key = TenantPartition.configuration.header_name
|
|
56
56
|
|
|
57
57
|
# Mensaje de error detallado dependiendo de si es un error de configuración o de petición
|
|
58
58
|
error_message = if header_key.blank?
|
|
59
|
-
"Server Configuration Error: 'header_name' is not configured in
|
|
59
|
+
"Server Configuration Error: 'header_name' is not configured in TenantPartition."
|
|
60
60
|
else
|
|
61
61
|
"Missing required header: '#{header_key}'"
|
|
62
62
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module TenantPartition
|
|
4
4
|
module Concerns
|
|
5
5
|
# Módulo de infraestructura para modelos de negocio (ApplicationRecord).
|
|
6
6
|
#
|
|
@@ -14,18 +14,18 @@ module ActivePartition
|
|
|
14
14
|
# 1. **Explícita:** Definida manualmente con `partitioned_by :key` en el modelo.
|
|
15
15
|
# 2. **Inferencia (Convención):** Busca si existe una clase de infraestructura asociada
|
|
16
16
|
# (ej: para `Conversation` busca `Partition::Conversation`) y utiliza su configuración.
|
|
17
|
-
# 3. **Global:** Utiliza la clave definida en `
|
|
17
|
+
# 3. **Global:** Utiliza la clave definida en `TenantPartition.configure`.
|
|
18
18
|
#
|
|
19
19
|
# @example Modo Automático (Inferencia)
|
|
20
20
|
# # Si Partition::LegacyChat tiene `self.partition_key = :region_id`
|
|
21
21
|
# class LegacyChat < ApplicationRecord
|
|
22
|
-
# include
|
|
22
|
+
# include TenantPartition::Concerns::Partitioned
|
|
23
23
|
# # Automáticamente configura PK: [:id, :region_id]
|
|
24
24
|
# end
|
|
25
25
|
#
|
|
26
26
|
# @example Modo Manual (Override)
|
|
27
27
|
# class Log < ApplicationRecord
|
|
28
|
-
# include
|
|
28
|
+
# include TenantPartition::Concerns::Partitioned
|
|
29
29
|
# partitioned_by :custom_id
|
|
30
30
|
# end
|
|
31
31
|
module Partitioned
|
|
@@ -34,7 +34,7 @@ module ActivePartition
|
|
|
34
34
|
included do
|
|
35
35
|
# Punto de entrada para inclusión directa.
|
|
36
36
|
# 'self' es la clase que incluye el módulo.
|
|
37
|
-
|
|
37
|
+
TenantPartition::Concerns::Partitioned.configure_model(self)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
class_methods do
|
|
@@ -45,14 +45,14 @@ module ActivePartition
|
|
|
45
45
|
# @param subclass [Class] La clase que está heredando.
|
|
46
46
|
def inherited(subclass)
|
|
47
47
|
super
|
|
48
|
-
|
|
48
|
+
TenantPartition::Concerns::Partitioned.configure_model(subclass)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Define manualmente la clave de partición, ignorando la inferencia y la config global.
|
|
52
52
|
#
|
|
53
53
|
# @param key [Symbol] Nombre de la columna de partición.
|
|
54
54
|
def partitioned_by(key)
|
|
55
|
-
|
|
55
|
+
TenantPartition::Concerns::Partitioned.apply_configuration(self, key)
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -91,7 +91,7 @@ module ActivePartition
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# 2. Configuración Global (Fallback):
|
|
94
|
-
|
|
94
|
+
TenantPartition.configuration&.partition_key
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# Aplica la configuración de Primary Key y Scopes al modelo.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module TenantPartition
|
|
4
4
|
class Configuration
|
|
5
5
|
# @return [Symbol, nil] Columna de base de datos (ej: :isp_id)
|
|
6
6
|
attr_accessor :partition_key
|
|
@@ -26,7 +26,7 @@ module ActivePartition
|
|
|
26
26
|
yield(configuration)
|
|
27
27
|
|
|
28
28
|
unless configuration.valid?
|
|
29
|
-
raise
|
|
29
|
+
raise TenantPartition::Error, "Debe configurar un 'partition_key' en el inicializador de TenantPartition."
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "rails/railtie"
|
|
4
4
|
|
|
5
|
-
module
|
|
6
|
-
# Railtie encargado de integrar
|
|
5
|
+
module TenantPartition
|
|
6
|
+
# Railtie encargado de integrar TenantPartition en el ciclo de vida de Rails.
|
|
7
7
|
#
|
|
8
8
|
# Su responsabilidad principal es inyectar los componentes de la gema (Tareas Rake,
|
|
9
9
|
# Helpers de Migración) en la aplicación anfitriona durante el proceso de arranque.
|
|
@@ -23,15 +23,15 @@ module ActivePartition
|
|
|
23
23
|
#
|
|
24
24
|
# Inyecta los helpers de migración (como +create_partitioned_table+) directamente
|
|
25
25
|
# en el adaptador de PostgreSQL para extender el DSL de las migraciones.
|
|
26
|
-
initializer "
|
|
26
|
+
initializer "tenant_partition.insert_schema_statements" do
|
|
27
27
|
ActiveSupport.on_load(:active_record) do
|
|
28
|
-
require "
|
|
28
|
+
require "tenant_partition/schema/statements"
|
|
29
29
|
|
|
30
30
|
# Validación de seguridad:
|
|
31
31
|
# Solo inyectamos el módulo si el adaptador configurado es efectivamente PostgreSQL.
|
|
32
32
|
# Esto previene errores si la gema se instala en proyectos con MySQL o SQLite.
|
|
33
33
|
if defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) && connection_db_config.adapter == "postgresql"
|
|
34
|
-
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include
|
|
34
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.include TenantPartition::Schema::Statements
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module TenantPartition
|
|
4
4
|
# Clase encargada de validar que el entorno y la configuración sean aptos
|
|
5
|
-
# para el funcionamiento de
|
|
5
|
+
# para el funcionamiento de TenantPartition.
|
|
6
6
|
class SafetyGuard
|
|
7
7
|
# Versión mínima de PostgreSQL soportada para particionamiento nativo estable.
|
|
8
8
|
MIN_POSTGRES_VERSION = 13.0
|
|
9
9
|
|
|
10
10
|
# Ejecuta todas las validaciones de seguridad.
|
|
11
|
-
# @raise [
|
|
11
|
+
# @raise [TenantPartition::Error] Si alguna validación falla.
|
|
12
12
|
# @return [void]
|
|
13
13
|
def self.validate!
|
|
14
14
|
check_configuration!
|
|
@@ -22,8 +22,8 @@ module ActivePartition
|
|
|
22
22
|
|
|
23
23
|
# Verifica que la clave de partición esté presente.
|
|
24
24
|
def self.check_configuration!
|
|
25
|
-
if
|
|
26
|
-
raise
|
|
25
|
+
if TenantPartition.configuration.partition_key.nil?
|
|
26
|
+
raise TenantPartition::Error, "Falta configuración: 'partition_key' no ha sido definido."
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ module ActivePartition
|
|
|
31
31
|
def self.check_database_adapter!
|
|
32
32
|
adapter = ActiveRecord::Base.connection_db_config.adapter
|
|
33
33
|
unless adapter == "postgresql"
|
|
34
|
-
raise
|
|
34
|
+
raise TenantPartition::Error, "Adaptador incompatible: TenantPartition solo soporta PostgreSQL (usando: #{adapter})."
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -39,7 +39,7 @@ module ActivePartition
|
|
|
39
39
|
def self.check_postgres_version!
|
|
40
40
|
version = ActiveRecord::Base.connection.select_value("SHOW server_version").to_f
|
|
41
41
|
if version < MIN_POSTGRES_VERSION
|
|
42
|
-
raise
|
|
42
|
+
raise TenantPartition::Error, "Versión de PostgreSQL insuficiente: Se requiere v#{MIN_POSTGRES_VERSION}+ (detectada: #{version})."
|
|
43
43
|
end
|
|
44
44
|
rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
|
|
45
45
|
# Si no hay conexión aún, se posterga la validación
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module TenantPartition
|
|
4
4
|
module Schema
|
|
5
5
|
# Proporciona métodos adicionales para las migraciones de ActiveRecord
|
|
6
6
|
# enfocados en la automatización del particionamiento nativo de PostgreSQL.
|
|
@@ -16,10 +16,10 @@ module ActivePartition
|
|
|
16
16
|
# @yield [t] Bloque para definir las columnas de la tabla.
|
|
17
17
|
def create_partitioned_table(table_name, **options, &block)
|
|
18
18
|
# Prioridad: 1. Opción pasada al método, 2. Configuración global
|
|
19
|
-
key = options.delete(:partition_key) ||
|
|
19
|
+
key = options.delete(:partition_key) || TenantPartition.configuration&.partition_key
|
|
20
20
|
|
|
21
21
|
unless key
|
|
22
|
-
raise
|
|
22
|
+
raise TenantPartition::Error, "Debe configurar 'partition_key' globalmente o pasarlo como opción."
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# --- LÓGICA DE PRIMARY KEY COMPUESTA ---
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# lib/tenant_partition/tasks/maintenance.rake
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
namespace :tenant_partition do
|
|
5
|
+
desc "Audita todas las tablas DEFAULT definidas en TenantPartition"
|
|
6
|
+
task audit: :environment do
|
|
7
|
+
TenantPartition.audit
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "Limpia registros huérfanos moviéndolos a sus particiones"
|
|
11
|
+
task cleanup: :environment do
|
|
12
|
+
TenantPartition.cleanup!
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -5,34 +5,34 @@ require "active_model"
|
|
|
5
5
|
require "active_support/all"
|
|
6
6
|
|
|
7
7
|
# Carga de la versión y componentes internos
|
|
8
|
-
require_relative "
|
|
9
|
-
require_relative "
|
|
10
|
-
require_relative "
|
|
11
|
-
require_relative "
|
|
12
|
-
require_relative "
|
|
13
|
-
require_relative "
|
|
8
|
+
require_relative "tenant_partition/version"
|
|
9
|
+
require_relative "tenant_partition/configuration"
|
|
10
|
+
require_relative "tenant_partition/safety_guard"
|
|
11
|
+
require_relative "tenant_partition/base"
|
|
12
|
+
require_relative "tenant_partition/concerns/partitioned"
|
|
13
|
+
require_relative "tenant_partition/concerns/controller"
|
|
14
14
|
|
|
15
15
|
# Integración con Ruby on Rails
|
|
16
|
-
require_relative "
|
|
16
|
+
require_relative "tenant_partition/railtie" if defined?(Rails)
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# TenantPartition es el punto de entrada principal para la gestión de particionamiento
|
|
19
19
|
# en PostgreSQL dentro de aplicaciones Rails.
|
|
20
20
|
#
|
|
21
21
|
# Actúa como una **Fachada** que centraliza:
|
|
22
22
|
# 1. Configuración de la gema.
|
|
23
23
|
# 2. Orquestación del ciclo de vida de los tenants (Crear/Borrar particiones).
|
|
24
24
|
# 3. Mantenimiento y limpieza de datos huérfanos.
|
|
25
|
-
module
|
|
25
|
+
module TenantPartition
|
|
26
26
|
# Error base para todas las excepciones de la gema.
|
|
27
27
|
class Error < StandardError; end
|
|
28
28
|
|
|
29
29
|
class << self
|
|
30
|
-
# @return [
|
|
30
|
+
# @return [TenantPartition::Configuration] El objeto de configuración global.
|
|
31
31
|
attr_accessor :configuration
|
|
32
32
|
|
|
33
33
|
# Configura la gema mediante un bloque e inicializa las validaciones de seguridad.
|
|
34
34
|
#
|
|
35
|
-
# @yieldparam [
|
|
35
|
+
# @yieldparam [TenantPartition::Configuration] config
|
|
36
36
|
# @return [void]
|
|
37
37
|
def configure
|
|
38
38
|
self.configuration ||= Configuration.new
|
|
@@ -102,7 +102,7 @@ module ActivePartition
|
|
|
102
102
|
#
|
|
103
103
|
# @return [Hash{String => Integer}] Mapa con el nombre del modelo y la cantidad de registros huérfanos.
|
|
104
104
|
# @example
|
|
105
|
-
#
|
|
105
|
+
# TenantPartition.audit
|
|
106
106
|
# # => { "Partition::Message" => 150, "Partition::Log" => 0 }
|
|
107
107
|
def audit
|
|
108
108
|
ensure_models_loaded!
|
|
@@ -163,10 +163,10 @@ module ActivePartition
|
|
|
163
163
|
|
|
164
164
|
private
|
|
165
165
|
|
|
166
|
-
# Encuentra todas las clases cargadas que heredan de
|
|
166
|
+
# Encuentra todas las clases cargadas que heredan de TenantPartition::Base.
|
|
167
167
|
# @return [Array<Class>]
|
|
168
168
|
def partitionable_models
|
|
169
|
-
ObjectSpace.each_object(Class).select { |klass| klass <
|
|
169
|
+
ObjectSpace.each_object(Class).select { |klass| klass < TenantPartition::Base }
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
# Cuenta registros en la tabla default de un modelo de infraestructura.
|
|
@@ -204,7 +204,7 @@ module ActivePartition
|
|
|
204
204
|
def log_error(tag, msg) = logger&.error(format_log(tag, msg))
|
|
205
205
|
|
|
206
206
|
def format_log(tag, msg)
|
|
207
|
-
"[
|
|
207
|
+
"[TenantPartition] [#{tag}] #{msg}"
|
|
208
208
|
end
|
|
209
209
|
|
|
210
210
|
def logger
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tenant_partition
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- gabriel
|
|
@@ -51,18 +51,18 @@ files:
|
|
|
51
51
|
- LICENSE.txt
|
|
52
52
|
- README.md
|
|
53
53
|
- Rakefile
|
|
54
|
-
- lib/
|
|
55
|
-
- lib/
|
|
56
|
-
- lib/
|
|
57
|
-
- lib/
|
|
58
|
-
- lib/
|
|
59
|
-
- lib/
|
|
60
|
-
- lib/
|
|
61
|
-
- lib/
|
|
62
|
-
- lib/
|
|
63
|
-
- lib/
|
|
64
|
-
- sig/
|
|
65
|
-
homepage: https://github.com/gedera/
|
|
54
|
+
- lib/tenant_partition.rb
|
|
55
|
+
- lib/tenant_partition/base.rb
|
|
56
|
+
- lib/tenant_partition/concerns/controller.rb
|
|
57
|
+
- lib/tenant_partition/concerns/partitioned.rb
|
|
58
|
+
- lib/tenant_partition/configuration.rb
|
|
59
|
+
- lib/tenant_partition/railtie.rb
|
|
60
|
+
- lib/tenant_partition/safety_guard.rb
|
|
61
|
+
- lib/tenant_partition/schema/statements.rb
|
|
62
|
+
- lib/tenant_partition/tasks/maintenance.rake
|
|
63
|
+
- lib/tenant_partition/version.rb
|
|
64
|
+
- sig/tenant_partition.rbs
|
|
65
|
+
homepage: https://github.com/gedera/tenant_partition
|
|
66
66
|
licenses:
|
|
67
67
|
- MIT
|
|
68
68
|
metadata:
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# lib/activepartition/tasks/maintenance.rake
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
namespace :active_partition do
|
|
5
|
-
desc "Audita todas las tablas DEFAULT definidas en ActivePartition"
|
|
6
|
-
task audit: :environment do
|
|
7
|
-
ActivePartition.audit
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
desc "Limpia registros huérfanos moviéndolos a sus particiones"
|
|
11
|
-
task cleanup: :environment do
|
|
12
|
-
ActivePartition.cleanup!
|
|
13
|
-
end
|
|
14
|
-
end
|