vert-core 1.0.13 → 1.0.15

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: efcc19b5c1872506cb9d75e59020db09984c94d24cd690709c26db3c4f966194
4
- data.tar.gz: a21f57805634a5f6fbb14bfcf6513c92382309d2ee004f73c955e997a7f7b932
3
+ metadata.gz: c81e24d685ccd2412787e971650108ad2d507fea03121aecf9f74f442cdeee29
4
+ data.tar.gz: 6da8dfb2a6d8fcc3ed751a0d04958446e392417c4ed33d2a60ed4a5cbf870236
5
5
  SHA512:
6
- metadata.gz: 8fe2af573b8f87f8ee20c65944a9aa1fc74c0f3ceab41ae355d627d41d294d1c90e195fd77c0ae2a2813d6bbdf8638ac6c5f724beee917b0d21e1527eb75c54e
7
- data.tar.gz: 709adf6c940017b3cf322a35d810cedff4fe73126c5e3483668592cb21cfffb830d0889b09ddd791fd95c2bdbc7508cad129b4d67bfe3adbb68caf49fc5176e2
6
+ metadata.gz: d9c5b091cb5a213aa0b14cc27f31817617096d89ceb58bd74f327c56a422b7f17cd57681ad4a9f4cc010ce42b9002807ac6ae34bce5dae84e5ced185b62265db
7
+ data.tar.gz: 1ffaaf2ae131b78aa452abccf4ccacebaf454c594e8b9bff6d6b458c13c498ce06e5936df5bb8502f5945cf3f69e9f59d03ad71caf455e133529bac4de5d56b1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.15] - 2026-05-24
4
+
5
+ ### Added
6
+
7
+ - `Vert::Concerns::MultiTenant.multi_tenant_options(include_system_records: true)` — opt-in para que o `default_scope` inclua registros com `tenant_id IS NULL` (system records visíveis globalmente). Útil para `Role`, `Plan`, `Permission` catálogo e outros modelos com registros "globais" compartilhados entre tenants.
8
+ - Quando ativado, o scope vira `WHERE tenant_id = ? OR tenant_id IS NULL`.
9
+ - Quando ativado, `tenant_id` deixa de ser obrigatório na validação.
10
+
11
+ ### Fixed
12
+
13
+ - **Bug crítico latente** — sem a opção `include_system_records`, o `default_scope` do `MultiTenant` escondia silenciosamente todos os registros com `tenant_id NULL` durante requests autenticadas. Isso quebrava qualquer associação `belongs_to` para system records (ex: `User#role` apontando para system role) retornando nil mesmo com `role_id` presente. Consequência: TODAS as Pundit policies que checavam `user.role` retornavam 403 mesmo para Proprietário do tenant.
14
+ - Workaround anterior (sem essa release): `Role.unscoped.find_by(id:)` em cada chamada.
15
+ - Com 1.0.15: declarar `multi_tenant_options include_system_records: true` no model e remover workarounds.
16
+
17
+ ### Migration guide
18
+
19
+ ```ruby
20
+ # Antes (workaround):
21
+ class User < ApplicationRecord
22
+ belongs_to :role, optional: true
23
+
24
+ def role
25
+ return super if association(:role).loaded?
26
+ @role_with_fallback ||= Role.unscoped.find_by(id: role_id)
27
+ end
28
+ end
29
+
30
+ # Depois (vert-core 1.0.15):
31
+ class Role < ApplicationRecord
32
+ include Vert::Concerns::MultiTenant
33
+ multi_tenant_options include_system_records: true # ← novo
34
+ end
35
+
36
+ class User < ApplicationRecord
37
+ belongs_to :role, optional: true
38
+ # Sem override — agora `user.role` carrega normalmente
39
+ end
40
+ ```
41
+
42
+ ## [1.0.14] - 2026-05-24
43
+
44
+ ### Added
45
+
46
+ - `Vert::Auth::JwtAuthenticatable#jwt_authenticated?` — helper que retorna `true` após `authenticate_jwt!` ter populado o payload com sucesso. Necessário durante o rollout para usar em `before_action :foo, if: :jwt_authenticated?` (callbacks que dependem de `Vert::Current` já populado).
47
+
3
48
  ## [1.0.13] - 2026-05-24
4
49
 
5
50
  ### Added
@@ -170,6 +170,13 @@ module Vert
170
170
  @jwt_payload
171
171
  end
172
172
 
173
+ # True após `authenticate_jwt!` ter populado o payload com sucesso.
174
+ # Útil para `before_action :foo, if: :jwt_authenticated?` em callbacks
175
+ # que dependem de Vert::Current já populado.
176
+ def jwt_authenticated?
177
+ @jwt_payload.present?
178
+ end
179
+
173
180
  # Override no serviço para retornar o registro User correspondente
174
181
  # ao `sub` do JWT.
175
182
  def current_jwt_user
@@ -2,22 +2,55 @@
2
2
 
3
3
  module Vert
4
4
  module Concerns
5
+ # MultiTenant
6
+ # -----------
7
+ # Adiciona escopo automático por `tenant_id` baseado em `Vert::Current.tenant_id`.
8
+ #
9
+ # ## Uso básico
10
+ # class Order < ApplicationRecord
11
+ # include Vert::Concerns::MultiTenant
12
+ # end
13
+ #
14
+ # ## System records (tenant_id NULL)
15
+ #
16
+ # Alguns modelos têm registros "globais" que devem ser visíveis em todos
17
+ # os tenants — ex: `Role` system, `Plan`, `Permission` catálogo. Esses
18
+ # registros são identificados por `tenant_id IS NULL`.
19
+ #
20
+ # Por default, o `default_scope` filtra `where(tenant_id = current)`, o que
21
+ # exclui esses registros silenciosamente quando há tenant ativo. Para
22
+ # incluí-los, use o opt-in:
23
+ #
24
+ # class Role < ApplicationRecord
25
+ # include Vert::Concerns::MultiTenant
26
+ # multi_tenant_options include_system_records: true
27
+ # end
28
+ #
29
+ # Com essa opção, o default_scope passa a ser:
30
+ # where("tenant_id = ? OR tenant_id IS NULL", Vert::Current.tenant_id)
31
+ #
32
+ # Aplica-se também à validação: `tenant_id` deixa de ser obrigatório.
33
+ #
34
+ # ## Bypass manual
35
+ #
36
+ # Para queries específicas que precisam ignorar o escopo:
37
+ #
38
+ # Order.unscoped.where(id: external_id)
39
+ # Order.unscoped_for_tenant(other_tenant_id).find(id)
5
40
  module MultiTenant
6
41
  extend ActiveSupport::Concern
7
42
 
8
- included do
9
- validates :tenant_id, presence: true, if: :require_tenant_id?
10
- default_scope do
11
- if Vert::Current.tenant_id.present?
12
- where(tenant_id: Vert::Current.tenant_id)
13
- else
14
- all
15
- end
43
+ class_methods do
44
+ # Configura comportamento do MultiTenant. Aceita:
45
+ # - include_system_records: true → default_scope inclui tenant_id NULL
46
+ def multi_tenant_options(include_system_records: false)
47
+ @multi_tenant_include_system_records = include_system_records
48
+ end
49
+
50
+ def multi_tenant_include_system_records?
51
+ @multi_tenant_include_system_records == true
16
52
  end
17
- before_validation :set_tenant_id, on: :create
18
- end
19
53
 
20
- class_methods do
21
54
  def unscoped_for_tenant(tenant_id)
22
55
  unscoped.where(tenant_id: tenant_id)
23
56
  end
@@ -31,9 +64,30 @@ module Vert
31
64
  end
32
65
  end
33
66
 
67
+ included do
68
+ validates :tenant_id, presence: true, if: :require_tenant_id?
69
+
70
+ default_scope do
71
+ current_tenant = Vert::Current.tenant_id
72
+ if current_tenant.present?
73
+ if multi_tenant_include_system_records?
74
+ where("#{table_name}.tenant_id = ? OR #{table_name}.tenant_id IS NULL", current_tenant)
75
+ else
76
+ where(tenant_id: current_tenant)
77
+ end
78
+ else
79
+ all
80
+ end
81
+ end
82
+
83
+ before_validation :set_tenant_id, on: :create
84
+ end
85
+
34
86
  private
35
87
 
36
88
  def require_tenant_id?
89
+ return false if self.class.multi_tenant_include_system_records?
90
+
37
91
  true
38
92
  end
39
93
 
data/lib/vert/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vert
4
- VERSION = "1.0.13"
4
+ VERSION = "1.0.15"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vert-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.13
4
+ version: 1.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vert Team
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-05-24 00:00:00.000000000 Z
10
+ date: 2026-05-25 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport