tenant_partition 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f9ccf1323d98dd3c78356387de4666127584d7539098e6501d326da1d107fa0
4
- data.tar.gz: 05e849e009ee93921e6de9b8f207a6ae91781e2b177295d2ae23def5df5e07b1
3
+ metadata.gz: e675f290b7cbe1ef13c1d6db7ec36894a92c169c82ae42112a1b0979c220f9f1
4
+ data.tar.gz: 4470f638d2f110c48d9f22a1b8a685f2c598dd01292cb867617a45782d7def11
5
5
  SHA512:
6
- metadata.gz: 368ec2f0cfc8b312ca5b5ac61eb7ace18da414faae68846e6353e7058cb5079f04777a0c6418fdd03ddd66da90df188f05d23c43dce8292a556a13d30ca48da1
7
- data.tar.gz: ecab7244f3a6e106c96a27954b2df753fd1ac0143d67423b35b653c7816dfc973b946b538f1b6a6afb6dae7b0d44ff358b607bb6d394b86e17e0b9704ef86034
6
+ metadata.gz: 87115b8fc726b431102c6ea6d53103f71370d643a3fc543504ceb94df1d718eedd4041092a7f2384ecb98e9825447ba88281a32035ee244081b04579bda041c6
7
+ data.tar.gz: 3d535affbac9ea2101aaf1d8ecd415f379fb0e8cfa0d1a826b5d02e01567bd1a90f87ae01dd9be53744d8fe975e467a1362ac24f773e417b17d4635ec6b4d0e6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2026-02-13
4
+ ### Added
5
+ - **Mejora en Migraciones:** El helper `create_partitioned_table` ahora acepta la opción `id_type: :uuid` o `id_type: :bigint` (por defecto). Esto permite usar particionamiento con IDs enteros seriales estándar o UUIDs según la necesidad del proyecto.
6
+
3
7
  ## [0.2.0] - 2026-02-13
4
8
  ### Changed (Breaking Changes)
5
9
  - **Arquitectura:** Se eliminó la clase `TenantPartition::Base` y la necesidad de crear modelos de infraestructura en el namespace `Partition::`.
data/README.md CHANGED
@@ -7,6 +7,7 @@ A diferencia de otras soluciones que dependen de esquemas (schemas) o hackeos a
7
7
  ## 🚀 Características Principales
8
8
 
9
9
  * **API Simple (Opt-in):** Usa `partition_table` en tus modelos para activar la magia.
10
+ * **Migraciones Inteligentes:** Helper `create_partitioned_table` que maneja la complejidad de Postgres automáticamente.
10
11
  * **Soporte Nativo CPK:** Compatible con **Composite Primary Keys** de Rails 7.1+.
11
12
  * **Sin Magic Strings:** Usa métodos explícitos (`create_partition`, `drop_partition`).
12
13
  * **Gestión de Datos Huérfanos:** Herramientas para mover datos de la tabla "Default" a su partición correcta automáticamente.
@@ -33,7 +34,8 @@ bundle install
33
34
 
34
35
  ## ⚙️ Configuración Inicial
35
36
 
36
- Crea un inicializador para definir tu clave de partición global (por ejemplo, `:isp_id`, `:account_id`, `:tenant_id`).
37
+ ### 1. Inicializador
38
+ Crea un archivo para definir tu clave de partición global (por ejemplo, `:isp_id`, `:account_id`, `:tenant_id`).
37
39
 
38
40
  ```ruby
39
41
  # config/initializers/tenant_partition.rb
@@ -44,105 +46,105 @@ TenantPartition.configure do |config|
44
46
  end
45
47
  ```
46
48
 
47
- ---
48
-
49
- ## 🏗 Estrategias de Uso
50
-
51
- La gema utiliza un patrón "Opt-In". Incluir el módulo no altera tus modelos hasta que lo activas explícitamente. Puedes elegir la estrategia que mejor se adapte a tu proyecto:
52
-
53
- ### Opción A: Global (Recomendado)
54
- Incluye el concern en `ApplicationRecord`. Esto **NO** particiona tus tablas, solo habilita la posibilidad de usar la macro `partition_table` en el futuro. Es ideal para mantener el código limpio.
49
+ ### 2. Habilitar en ApplicationRecord
50
+ Incluye el concern en tu modelo base. **No te preocupes, esto no particiona nada por defecto**, solo habilita la posibilidad de usar la macro `partition_table` en tus modelos.
55
51
 
56
52
  ```ruby
57
53
  # app/models/application_record.rb
58
54
  class ApplicationRecord < ActiveRecord::Base
59
55
  primary_abstract_class
60
56
 
61
- # Habilita la herramienta, pero permanece inactiva por defecto.
62
- include TenantPartition::Concerns::Partitioned
63
- end
64
- ```
65
-
66
- ### Opción B: Local (A la carta)
67
- Si prefieres no tocar `ApplicationRecord` o estás en un sistema legacy, puedes incluir el concern solo en los modelos específicos.
68
-
69
- ```ruby
70
- # app/models/conversation.rb
71
- class Conversation < ApplicationRecord
57
+ # Habilita la herramienta (modo inactivo por defecto)
72
58
  include TenantPartition::Concerns::Partitioned
73
- partition_table # Activación inmediata
74
59
  end
75
60
  ```
76
61
 
77
62
  ---
78
63
 
79
- ## 📖 Referencia de Macros y Métodos
64
+ ## 🛠 Guía de Implementación
80
65
 
81
- Una vez que incluyes `TenantPartition::Concerns::Partitioned` en tu clase, obtienes acceso a las siguientes herramientas:
66
+ ### Paso 1: Migración de Base de Datos
82
67
 
83
- ### 1. La Macro de Activación: `partition_table`
68
+ Olvídate del SQL manual. Usa el helper `create_partitioned_table` que hace todo el trabajo sucio por ti:
69
+ 1. Crea la tabla padre con `PARTITION BY LIST`.
70
+ 2. Configura la Primary Key Compuesta `[:id, :partition_key]`.
71
+ 3. Crea automáticamente la partición `_default` para capturar datos no asignados.
84
72
 
85
- Es el interruptor de encendido. Debe llamarse al inicio de la definición del modelo.
73
+ #### Opción A: Usando Enteros (BigInt) - Recomendado
86
74
 
87
75
  ```ruby
88
- class Conversation < ApplicationRecord
89
- # Uso estándar (usa la key configurada globalmente, ej: :isp_id)
90
- partition_table
76
+ class CreateConversations < ActiveRecord::Migration[7.1]
77
+ def change
78
+ # partition_key: usa el default de la config (:isp_id) si no se especifica.
79
+ # id_type: :bigint por defecto.
80
+ create_partitioned_table :conversations do |t|
81
+ t.string :subject
82
+ t.text :body
83
+ t.timestamps
91
84
 
92
- # Uso personalizado (para modelos con keys únicas, ej: :year)
93
- # partition_table key: :year
85
+ # Nota: No definas :id ni :isp_id aquí, el helper lo hace por ti.
86
+ end
87
+ end
94
88
  end
95
89
  ```
96
90
 
97
- **¿Qué hace esta macro internamente?**
98
- 1. Configura la **Primary Key Compuesta** (`[:id, :partition_key]`).
99
- 2. Registra el modelo en el sistema de mantenimiento de la gema.
100
- 3. Inyecta los métodos de gestión de infraestructura (ver abajo).
101
- 4. Agrega el scope `for_partition(value)`.
91
+ #### Opción B: Usando UUIDs
102
92
 
103
- ### 2. Métodos de Gestión de Infraestructura (Class Methods)
93
+ ```ruby
94
+ class CreateConversations < ActiveRecord::Migration[7.1]
95
+ def change
96
+ enable_extension 'pgcrypto' # Necesario para gen_random_uuid()
104
97
 
105
- Estos métodos se inyectan en tu modelo **solo después** de llamar a `partition_table`. Úsalos para gestionar el ciclo de vida de las tablas físicas.
98
+ create_partitioned_table :conversations, id_type: :uuid do |t|
99
+ t.string :subject
100
+ t.timestamps
101
+ end
102
+ end
103
+ end
104
+ ```
106
105
 
107
- | Método | Descripción | Ejemplo |
108
- | :--- | :--- | :--- |
109
- | `create_partition(val)` | Crea la tabla física en Postgres (`CREATE TABLE ... PARTITION OF ...`). | `Conversation.create_partition(100)` |
110
- | `drop_partition(val)` | Elimina la tabla física y sus datos (`DROP TABLE ...`). | `Conversation.drop_partition(100)` |
111
- | `partition_table_exists?(val)` | Devuelve `true` si la tabla física existe en la BD. | `Conversation.partition_table_exists?(100)` |
112
- | `partition_table_name(val)` | Devuelve el nombre real de la tabla hija. | `Conversation.partition_table_name(100)` <br> *=> "conversations_isp_100"* |
106
+ ### Paso 2: Configurar el Modelo
113
107
 
114
- ---
108
+ Usa la macro `partition_table` para activar la funcionalidad en el modelo correspondiente.
115
109
 
116
- ## 🛠 Guía de Implementación
110
+ ```ruby
111
+ # app/models/conversation.rb
112
+ class Conversation < ApplicationRecord
113
+ # ¡Esto es todo!
114
+ # Automáticamente configura la Primary Key compuesta [:id, :isp_id]
115
+ # y los scopes necesarios.
116
+ partition_table
117
+ end
118
+ ```
117
119
 
118
- ### 1. Migración de Base de Datos
120
+ **¿Necesitas una key diferente para un solo modelo?**
121
+ ```ruby
122
+ class AuditLog < ApplicationRecord
123
+ # Este modelo se particiona por año, ignorando la config global
124
+ partition_table key: :year
125
+ end
126
+ ```
119
127
 
120
- PostgreSQL necesita que la tabla padre se cree con la opción `PARTITION BY LIST`.
128
+ ### Paso 3: Crear y Eliminar Tenants
121
129
 
122
- ```ruby
123
- class CreateConversations < ActiveRecord::Migration[7.1]
124
- def up
125
- # 1. Crear la tabla padre particionada (id: false es importante)
126
- create_table :conversations, id: false, options: "PARTITION BY LIST (isp_id)" do |t|
127
- t.bigserial :id, null: false
128
- t.integer :isp_id, null: false # Tu partition key
130
+ Gestiona el ciclo de vida de las particiones utilizando los métodos de clase inyectados.
129
131
 
130
- t.string :subject
131
- t.timestamps
132
- end
132
+ ```ruby
133
+ # Crear partición física para el ISP con ID 100
134
+ Conversation.create_partition(100)
135
+ # => Crea la tabla "conversations_isp_100"
133
136
 
134
- # 2. Definir la Primary Key Compuesta (Requerido por Postgres)
135
- execute "ALTER TABLE conversations ADD PRIMARY KEY (id, isp_id);"
137
+ # Verificar si existe
138
+ Conversation.partition_table_exists?(100)
139
+ # => true
136
140
 
137
- # 3. Crear tabla DEFAULT (Recomendado para evitar errores de inserción)
138
- execute "CREATE TABLE conversations_default PARTITION OF conversations DEFAULT;"
139
- end
140
- # ...
141
+ # Eliminar partición (CUIDADO: Borra datos)
142
+ Conversation.drop_partition(100)
143
+ # => Elimina "conversations_isp_100"
141
144
  ```
142
145
 
143
- ### 2. Callbacks de Aprovisionamiento
144
-
145
- Es común automatizar la creación de particiones cuando se crea un nuevo Tenant (ej. un nuevo ISP o Cliente).
146
+ #### Automatización con Callbacks
147
+ Es común crear las particiones automáticamente cuando nace un nuevo Tenant.
146
148
 
147
149
  ```ruby
148
150
  # app/models/isp.rb
@@ -150,7 +152,7 @@ class Isp < ApplicationRecord
150
152
  after_create :provision_infrastructure
151
153
 
152
154
  def provision_infrastructure
153
- # Método helper que crea las particiones en TODOS los modelos registrados
155
+ # Helper global que crea particiones en TODOS los modelos registrados
154
156
  TenantPartition.create!(self.id)
155
157
  end
156
158
  end
@@ -160,10 +162,10 @@ end
160
162
 
161
163
  ## 🧹 Mantenimiento y Datos Huérfanos
162
164
 
163
- Si insertas datos con un `isp_id` para el cual no has creado una partición (y tienes una tabla `DEFAULT`), los datos caerán ahí.
165
+ Si insertas datos con un `isp_id` para el cual no has creado una partición, Postgres los guardará en la tabla `_default` que creamos en la migración. `TenantPartition` incluye herramientas para detectar y corregir esto.
164
166
 
165
167
  ### Auditoría
166
- Verifica si tienes datos en las tablas default:
168
+ Verifica si tienes datos "mal ubicados" en las tablas default:
167
169
 
168
170
  ```bash
169
171
  bundle exec rake tenant_partition:audit
@@ -172,7 +174,7 @@ bundle exec rake tenant_partition:audit
172
174
  ```
173
175
 
174
176
  ### Limpieza (Cleanup)
175
- Crea las particiones faltantes y mueve los datos automáticamente:
177
+ Crea las particiones faltantes y mueve los datos automáticamente a su hogar correcto:
176
178
 
177
179
  ```bash
178
180
  bundle exec rake tenant_partition:cleanup
@@ -184,9 +186,9 @@ bundle exec rake tenant_partition:cleanup
184
186
 
185
187
  ## 🛡️ Producción y Seguridad
186
188
 
187
- La gema incluye un `SafetyGuard` que impide ejecutar comandos destructivos (`drop_partition`, `destroy!`) en entorno de producción a menos que se fuerce explícitamente.
189
+ La gema incluye un `SafetyGuard` que impide ejecutar comandos destructivos (`drop_partition`, `destroy!`) en entorno de producción para evitar catástrofes.
188
190
 
189
- Para ejecutar tareas destructivas en producción, debes setear la variable de entorno:
191
+ Si realmente necesitas borrar un tenant en producción, debes autorizarlo explícitamente:
190
192
 
191
193
  ```bash
192
194
  DISABLE_TENANT_PARTITION_GUARD=true bundle exec rake tenant_partition:destroy_tenant[123]
@@ -194,6 +196,25 @@ DISABLE_TENANT_PARTITION_GUARD=true bundle exec rake tenant_partition:destroy_te
194
196
 
195
197
  ---
196
198
 
199
+ ## 📖 Referencia de API
200
+
201
+ ### `TenantPartition` (Global)
202
+ * `configure { ... }`: Configuración inicial.
203
+ * `create!(id)`: Crea particiones para el ID dado en **todos** los modelos registrados.
204
+ * `destroy!(id)`: Elimina particiones para el ID dado en **todos** los modelos.
205
+ * `exists?(id)`: Devuelve `true` si existe infraestructura para ese ID.
206
+
207
+ ### Métodos de Instancia (Modelos)
208
+ * `partition_table(key: nil)`: Macro de activación.
209
+
210
+ ### Métodos de Clase (Modelos)
211
+ * `create_partition(value)`: Crea tabla física.
212
+ * `drop_partition(value)`: Borra tabla física.
213
+ * `partition_table_exists?(value)`: Verifica existencia.
214
+ * `partition_table_name(value)`: Devuelve el nombre real de la tabla en Postgres.
215
+
216
+ ---
217
+
197
218
  ## License
198
219
 
199
220
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -10,15 +10,18 @@ module TenantPartition
10
10
  # @param table_name [Symbol] Nombre de la tabla.
11
11
  # @param options [Hash] Opciones de migración estándar.
12
12
  # @option options [Symbol] :partition_key Clave opcional para sobreescribir la global.
13
+ # @option options [Symbol] :id_type Tipo de ID (:uuid o :bigint). Default: :bigint.
13
14
  # @yield [t] Bloque de definición de tabla (ActiveRecord::ConnectionAdapters::TableDefinition).
14
15
  def create_partitioned_table(table_name, **options, &block)
15
16
  key = options.delete(:partition_key) || TenantPartition.configuration&.partition_key
17
+ id_type = options.delete(:id_type) || :bigint # Default conservador (BigInt)
18
+
16
19
  raise TenantPartition::Error, "Falta 'partition_key'." unless key
17
20
 
18
21
  configure_pk_and_options(options, key)
19
22
 
20
23
  create_table(table_name, **options) do |t|
21
- setup_partitioning(t, key, block)
24
+ setup_partitioning(t, key, id_type, block)
22
25
  end
23
26
 
24
27
  create_default_partition(table_name)
@@ -33,9 +36,17 @@ module TenantPartition
33
36
  private
34
37
 
35
38
  # Configura las columnas y definiciones dentro del bloque create_table.
36
- def setup_partitioning(table, key, block)
37
- table.uuid :id, null: false, default: -> { "gen_random_uuid()" }
39
+ def setup_partitioning(table, key, id_type, block)
40
+ # Definimos la ID según la preferencia del usuario
41
+ if id_type == :uuid
42
+ table.uuid :id, null: false, default: -> { "gen_random_uuid()" }
43
+ else
44
+ table.bigserial :id, null: false
45
+ end
46
+
47
+ # Ejecutamos el bloque del usuario (definición de columnas adicionales)
38
48
  block.call(table)
49
+
39
50
  ensure_partition_column(table, key)
40
51
  end
41
52
 
@@ -50,7 +61,9 @@ module TenantPartition
50
61
  def ensure_partition_column(table, key)
51
62
  return if table.columns.any? { |c| c.name == key.to_s }
52
63
 
53
- table.column key, :string, null: false
64
+ # Si no la definió, la creamos (asumiendo integer por defecto para claves foráneas típicas)
65
+ # Si el usuario quiere un UUID como partition key, debería definirlo explícitamente en el bloque.
66
+ table.integer key, null: false
54
67
  end
55
68
 
56
69
  # Crea la tabla _default para capturar datos sin partición asignada.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TenantPartition
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
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.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - gabriel