audit_log_rails 0.1.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +276 -634
- data/lib/audit_log_rails.rb +4 -0
- data/lib/audit_logger/audit_log.rb +13 -1
- data/lib/audit_logger/change_extractor.rb +15 -3
- data/lib/audit_logger/config_validator.rb +22 -0
- data/lib/audit_logger/configuration.rb +19 -1
- data/lib/audit_logger/humanizer.rb +47 -1
- data/lib/audit_logger/model_config.rb +27 -2
- data/lib/audit_logger/owner_resolver.rb +45 -0
- data/lib/audit_logger/persistence.rb +30 -0
- data/lib/audit_logger/railtie.rb +1 -0
- data/lib/audit_logger/record_audit_entry.rb +13 -20
- data/lib/audit_logger/record_event.rb +73 -0
- data/lib/audit_logger/version.rb +1 -1
- data/lib/audit_logger.rb +35 -0
- data/lib/generators/audit_logger/templates/add_owner_to_audit_logs.rb.tt +18 -0
- data/lib/generators/audit_logger/templates/audit_logger_initializer.rb.tt +13 -0
- data/lib/generators/audit_logger/templates/create_audit_logs.rb.tt +3 -0
- data/lib/generators/audit_logger/upgrade_generator.rb +30 -0
- metadata +19 -8
data/README.md
CHANGED
|
@@ -1,776 +1,418 @@
|
|
|
1
|
-
#
|
|
1
|
+
# audit_log_rails
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/audit_log_rails)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- integracao simples por `auditable`
|
|
7
|
-
- payload bruto e payload humanizado
|
|
8
|
-
- configuracao global por initializer
|
|
9
|
-
- override por model
|
|
10
|
-
|
|
11
|
-
## Compatibilidade
|
|
12
|
-
|
|
13
|
-
No estado atual da gem, as versoes suportadas sao:
|
|
14
|
-
|
|
15
|
-
- Ruby `>= 3.1.0`
|
|
16
|
-
- Rails / ActiveRecord `>= 7.0` e `< 9.0`
|
|
17
|
-
|
|
18
|
-
Em outras palavras, a gem foi preparada para funcionar com projetos Rails 7 e Rails 8, desde que a versao do Ruby seja compativel.
|
|
19
|
-
|
|
20
|
-
## Visao Geral
|
|
21
|
-
|
|
22
|
-
Ao adicionar `auditable` em uma model, a gem registra eventos de:
|
|
23
|
-
|
|
24
|
-
- `create`
|
|
25
|
-
- `update`
|
|
26
|
-
- `destroy`
|
|
27
|
-
|
|
28
|
-
Os registros sao persistidos na tabela `audit_logs` com:
|
|
29
|
-
|
|
30
|
-
- identificacao da model auditada
|
|
31
|
-
- tipo da acao
|
|
32
|
-
- uuid de correlacao
|
|
33
|
-
- dados do ator que executou a acao
|
|
34
|
-
- payload bruto da alteracao
|
|
35
|
-
- payload humanizado para exibicao
|
|
36
|
-
|
|
37
|
-
## Instalacao Passo A Passo
|
|
38
|
-
|
|
39
|
-
Se esta for a primeira vez que voce vai usar a gem, siga exatamente esta ordem.
|
|
40
|
-
|
|
41
|
-
### Passo 1 - Adicionar a gem no projeto Rails
|
|
42
|
-
|
|
43
|
-
No `Gemfile` da aplicacao cliente, adicione:
|
|
5
|
+
Auditoria de models ActiveRecord para Rails: registra quem criou, alterou ou excluiu cada registro, quando e o que mudou (valor antigo e valor novo), numa tabela própria, com uma versão já traduzida para exibir na tela.
|
|
44
6
|
|
|
45
7
|
```ruby
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Observacao importante:
|
|
50
|
-
|
|
51
|
-
- o nome publicado da gem e `audit_log_rails`
|
|
52
|
-
- o namespace Ruby continua sendo `AuditLogger`
|
|
53
|
-
- por isso, no `Gemfile` voce instala `audit_log_rails`, mas no codigo continua usando `AuditLogger`
|
|
54
|
-
|
|
55
|
-
Depois rode:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
bundle install
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Passo 2 - Gerar os arquivos iniciais da gem
|
|
62
|
-
|
|
63
|
-
Depois que a gem estiver instalada, rode:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
bin/rails generate audit_logger:install
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Esse comando e importante porque ele cria automaticamente os arquivos base que o projeto precisa para comecar.
|
|
70
|
-
|
|
71
|
-
### Passo 3 - Entender o que a gem criou
|
|
72
|
-
|
|
73
|
-
Ao rodar `bin/rails generate audit_logger:install`, a gem cria:
|
|
74
|
-
|
|
75
|
-
- `db/migrate/..._create_audit_logs.rb`
|
|
76
|
-
- `config/initializers/audit_logger.rb`
|
|
77
|
-
|
|
78
|
-
Ou seja:
|
|
79
|
-
|
|
80
|
-
- voce **nao precisa criar manualmente** o initializer da gem do zero
|
|
81
|
-
- voce **nao precisa escrever manualmente** a migration base do zero
|
|
82
|
-
- a gem ja entrega esses arquivos como ponto de partida
|
|
83
|
-
|
|
84
|
-
### Passo 4 - Rodar a migration
|
|
85
|
-
|
|
86
|
-
Depois que os arquivos forem gerados, rode:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
bin/rails db:migrate
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Esse comando cria a tabela `audit_logs` no banco da aplicacao.
|
|
93
|
-
|
|
94
|
-
### Passo 5 - Ajustar o initializer ao seu projeto
|
|
95
|
-
|
|
96
|
-
Depois que o arquivo `config/initializers/audit_logger.rb` for criado, voce deve abrir esse arquivo e adaptar os resolvers para a realidade do seu sistema.
|
|
8
|
+
class Contrato < ApplicationRecord
|
|
9
|
+
auditable owner: :pessoa
|
|
10
|
+
end
|
|
97
11
|
|
|
98
|
-
|
|
12
|
+
contrato.update!(cargo: assessor)
|
|
13
|
+
contrato.audit_logs.last.audited_changes_humanize
|
|
14
|
+
# => { "type" => "update",
|
|
15
|
+
# "fields" => { "cargo_id" => { "label" => "Cargo", "old_value" => 3, "new_value" => 7,
|
|
16
|
+
# "old_value_label" => "PROFESSOR", "new_value_label" => "ASSESSOR" } } }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Sumário
|
|
20
|
+
|
|
21
|
+
- [Recursos](#recursos)
|
|
22
|
+
- [Compatibilidade](#compatibilidade)
|
|
23
|
+
- [Instalação](#instalação)
|
|
24
|
+
- [Configuração](#configuração)
|
|
25
|
+
- [Quem fez a alteração (contexto do ator)](#quem-fez-a-alteração-contexto-do-ator)
|
|
26
|
+
- [Auditando uma model](#auditando-uma-model)
|
|
27
|
+
- [Consultando os logs](#consultando-os-logs)
|
|
28
|
+
- [Campos sensíveis](#campos-sensíveis)
|
|
29
|
+
- [Histórico de um agregado (dono)](#histórico-de-um-agregado-dono)
|
|
30
|
+
- [Rótulo de associações](#rótulo-de-associações)
|
|
31
|
+
- [Eventos manuais](#eventos-manuais)
|
|
32
|
+
- [Falhas não derrubam a requisição](#falhas-não-derrubam-a-requisição)
|
|
33
|
+
- [Formato dos dados](#formato-dos-dados)
|
|
34
|
+
- [Tradução dos campos (I18n)](#tradução-dos-campos-i18n)
|
|
35
|
+
- [Atualizando da 0.1.x](#atualizando-da-01x)
|
|
36
|
+
- [Limitações e cuidados](#limitações-e-cuidados)
|
|
37
|
+
- [Desenvolvimento](#desenvolvimento)
|
|
38
|
+
|
|
39
|
+
## Recursos
|
|
40
|
+
|
|
41
|
+
- **`auditable` na model:** registra `create`, `update` e `destroy` depois do commit. Se houver rollback, nada é gravado.
|
|
42
|
+
- **Só o que mudou:** em `update`, guarda apenas os campos alterados, com `old_value` e `new_value`.
|
|
43
|
+
- **Dois formatos:** o bruto (`audited_changes`) e o humanizado (`audited_changes_humanize`), com o nome traduzido de cada campo.
|
|
44
|
+
- **Campos sensíveis mascarados:** a troca de senha ou token fica registrada, sem os valores.
|
|
45
|
+
- **Rótulo de associações:** em `belongs_to`, o humanizado traz o nome do registro ("PROFESSOR → ASSESSOR"), e não só o id.
|
|
46
|
+
- **Dono do registro:** junta num só histórico a pessoa, os contratos e os dependentes dela.
|
|
47
|
+
- **Eventos manuais:** para o que não passa por callback, como login, pedido de reset de senha e `update_all`.
|
|
48
|
+
- **Falha segura:** se gravar o log falhar, a requisição do usuário não quebra.
|
|
49
|
+
- **Contexto do ator:** usuário, tipo, metadados, IP e um uuid que agrupa as alterações da mesma requisição.
|
|
99
50
|
|
|
100
|
-
|
|
101
|
-
- mas voce precisa ajustar o conteudo conforme seu `Current.user`, `Current.request`, sessao, perfis, tenants e assim por diante
|
|
51
|
+
## Compatibilidade
|
|
102
52
|
|
|
103
|
-
|
|
53
|
+
| | Versão |
|
|
54
|
+
|---|---|
|
|
55
|
+
| Ruby | `>= 3.1` |
|
|
56
|
+
| Rails / ActiveRecord | `>= 7.0` e `< 9.0` |
|
|
57
|
+
| Banco | PostgreSQL (a migration usa `jsonb`). Em outros bancos, troque as colunas JSON por `text`: a gem serializa sozinha. |
|
|
104
58
|
|
|
105
|
-
|
|
59
|
+
A gem se chama `audit_log_rails`, mas o namespace Ruby é `AuditLogger`.
|
|
106
60
|
|
|
107
|
-
|
|
61
|
+
## Instalação
|
|
108
62
|
|
|
109
|
-
|
|
63
|
+
1. Adicione a gem ao `Gemfile` e instale:
|
|
110
64
|
|
|
111
|
-
|
|
65
|
+
```ruby
|
|
66
|
+
gem "audit_log_rails"
|
|
67
|
+
```
|
|
112
68
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- quais metadados vao para `changed_by_other`
|
|
117
|
-
- como o `uuid` sera resolvido
|
|
118
|
-
- como o IP sera obtido
|
|
119
|
-
- quais atributos devem ser ignorados
|
|
120
|
-
- como a humanizacao deve funcionar
|
|
69
|
+
```bash
|
|
70
|
+
bundle install
|
|
71
|
+
```
|
|
121
72
|
|
|
122
|
-
|
|
73
|
+
2. Gere a migration e o initializer:
|
|
123
74
|
|
|
124
|
-
|
|
75
|
+
```bash
|
|
76
|
+
bin/rails generate audit_logger:install
|
|
77
|
+
```
|
|
125
78
|
|
|
126
|
-
|
|
127
|
-
- `
|
|
128
|
-
- `
|
|
129
|
-
- `uuid`
|
|
130
|
-
- `changed_by_id`
|
|
131
|
-
- `changed_by_type`
|
|
132
|
-
- `changed_by_other`
|
|
133
|
-
- `audited_changes`
|
|
134
|
-
- `audited_changes_humanize`
|
|
135
|
-
- `ip_remote`
|
|
136
|
-
- `created_at`
|
|
137
|
-
- `updated_at`
|
|
138
|
-
- `deleted_at`
|
|
79
|
+
O comando cria:
|
|
80
|
+
- `db/migrate/<timestamp>_create_audit_logs.rb`: a tabela `audit_logs`;
|
|
81
|
+
- `config/initializers/audit_logger.rb`: a configuração, com os exemplos comentados.
|
|
139
82
|
|
|
140
|
-
|
|
83
|
+
3. Rode a migration:
|
|
141
84
|
|
|
142
|
-
|
|
85
|
+
```bash
|
|
86
|
+
bin/rails db:migrate
|
|
87
|
+
```
|
|
143
88
|
|
|
144
|
-
|
|
145
|
-
config/initializers/audit_logger.rb
|
|
146
|
-
```
|
|
89
|
+
4. Ajuste o initializer para o seu app: quem é o usuário atual, o IP e assim por diante. Veja [Configuração](#configuração).
|
|
147
90
|
|
|
148
|
-
|
|
91
|
+
5. Coloque `auditable` nas models que quiser auditar.
|
|
149
92
|
|
|
150
|
-
|
|
93
|
+
### A tabela `audit_logs`
|
|
151
94
|
|
|
152
|
-
|
|
95
|
+
| Coluna | Conteúdo |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `model_class_name` | Classe auditada, ex.: `Contrato` |
|
|
98
|
+
| `id_object` | Id do registro auditado (texto) |
|
|
99
|
+
| `action` | `create`, `update`, `destroy` ou o nome do evento manual |
|
|
100
|
+
| `uuid` | Agrupa os logs da mesma requisição ou fluxo |
|
|
101
|
+
| `changed_by_id` / `changed_by_type` | Quem fez a alteração |
|
|
102
|
+
| `changed_by_other` | JSON livre com metadados do ator (nome, e-mail, request id...) |
|
|
103
|
+
| `audited_changes` | Payload bruto |
|
|
104
|
+
| `audited_changes_humanize` | Payload humanizado |
|
|
105
|
+
| `ip_remote` | IP de origem |
|
|
106
|
+
| `owner_type` / `owner_id` | Registro "dono" do log (desde a 0.2.0) |
|
|
107
|
+
| `created_at`, `updated_at`, `deleted_at` | Datas |
|
|
153
108
|
|
|
154
|
-
|
|
155
|
-
- os exemplos relacionados a `Current.user`, `Current.request` e `Current.audit_uuid` sao apenas sugestoes
|
|
156
|
-
- voce precisa descomentar e adaptar somente o que fizer sentido no seu projeto
|
|
109
|
+
A migration também cria os índices de consulta: por model e id, uuid, ação, ator, dono e data.
|
|
157
110
|
|
|
158
|
-
|
|
111
|
+
## Configuração
|
|
159
112
|
|
|
160
|
-
|
|
113
|
+
Tudo fica em `config/initializers/audit_logger.rb`:
|
|
161
114
|
|
|
162
115
|
```ruby
|
|
163
116
|
AuditLogger.configure do |config|
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
# config.changed_by_id_resolver = -> { Current.user&.id }
|
|
169
|
-
# config.changed_by_type_resolver = -> { Current.user&.class&.name }
|
|
170
|
-
#
|
|
171
|
-
# config.changed_by_other_resolver = lambda do
|
|
172
|
-
# {
|
|
173
|
-
# name: Current.user&.name,
|
|
174
|
-
# email: Current.user&.email,
|
|
175
|
-
# request_id: Current.request_id
|
|
176
|
-
# }.compact
|
|
177
|
-
# end
|
|
178
|
-
#
|
|
179
|
-
# config.uuid_resolver = -> { Current.audit_uuid }
|
|
180
|
-
# config.ip_resolver = -> { Current.request&.remote_ip }
|
|
181
|
-
|
|
182
|
-
# humanize serve para utilizar traduções do i18n de forma que os attributes do model seja traduzidos humanizando a leitura.
|
|
183
|
-
config.humanize_by_default = true
|
|
184
|
-
|
|
185
|
-
# Configura o padrão do scope do i18n para a humanização dos atributos do model.
|
|
186
|
-
config.i18n_scopes = ["activerecord.attributes", "attributes"]
|
|
187
|
-
|
|
188
|
-
# Atributos que devem ser ignorados na auditoria.
|
|
189
|
-
# Por exemplo, `created_at`, `updated_at`, `lock_version` e outros campos de auditoria.
|
|
190
|
-
config.ignored_attributes = [:created_at, :updated_at, :lock_version]
|
|
191
|
-
|
|
192
|
-
# Configura a humanização dos atributos do model.
|
|
193
|
-
config.humanizer = ->(_model_klass, _attribute, _old_value, _new_value) { nil }
|
|
194
|
-
end
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### O Que Esse Arquivo Faz
|
|
117
|
+
# Quem fez a alteração.
|
|
118
|
+
config.changed_by_id_resolver = -> { Current.user&.id }
|
|
119
|
+
config.changed_by_type_resolver = -> { Current.user&.class&.name }
|
|
120
|
+
config.changed_by_other_resolver = -> { { nome: Current.user&.nome, request_id: Current.request_id }.compact }
|
|
198
121
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
AuditLogger.configure do |config|
|
|
203
|
-
...
|
|
204
|
-
end
|
|
205
|
-
```
|
|
122
|
+
# Agrupamento e origem.
|
|
123
|
+
config.uuid_resolver = -> { Current.request_id }
|
|
124
|
+
config.ip_resolver = -> { Current.ip }
|
|
206
125
|
|
|
207
|
-
|
|
126
|
+
# O que entra no log.
|
|
127
|
+
config.ignored_attributes = %i[created_at updated_at lock_version]
|
|
128
|
+
config.masked_attributes += %i[token_api]
|
|
129
|
+
config.mask_value = "[FILTRADO]"
|
|
208
130
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
Exemplo:
|
|
216
|
-
|
|
217
|
-
```ruby
|
|
218
|
-
config.changed_by_id_resolver = -> { Current.user&.id }
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Esse resolver diz para a gem qual valor deve ser salvo na coluna `changed_by_id`.
|
|
222
|
-
|
|
223
|
-
Na pratica:
|
|
224
|
-
|
|
225
|
-
- se o usuario logado for `Current.user`
|
|
226
|
-
- a gem vai salvar `Current.user.id`
|
|
227
|
-
|
|
228
|
-
Se no seu sistema o ator principal for outro, voce troca aqui.
|
|
229
|
-
|
|
230
|
-
Exemplo:
|
|
231
|
-
|
|
232
|
-
```ruby
|
|
233
|
-
config.changed_by_id_resolver = -> { Current.admin&.id }
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
#### `config.changed_by_type_resolver`
|
|
237
|
-
|
|
238
|
-
Exemplo:
|
|
239
|
-
|
|
240
|
-
```ruby
|
|
241
|
-
config.changed_by_type_resolver = -> { Current.user&.class&.name }
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Esse resolver diz qual valor sera salvo em `changed_by_type`.
|
|
245
|
-
|
|
246
|
-
Voce pode usar isso para salvar:
|
|
247
|
-
|
|
248
|
-
- nome da classe
|
|
249
|
-
- perfil
|
|
250
|
-
- role
|
|
251
|
-
- tipo de usuario
|
|
252
|
-
|
|
253
|
-
Exemplo:
|
|
254
|
-
|
|
255
|
-
```ruby
|
|
256
|
-
config.changed_by_type_resolver = -> { Current.user&.profile }
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
#### `config.changed_by_other_resolver`
|
|
260
|
-
|
|
261
|
-
Exemplo:
|
|
131
|
+
# Humanização.
|
|
132
|
+
config.humanize_by_default = true
|
|
133
|
+
config.i18n_scopes = ["activerecord.attributes", "attributes"]
|
|
134
|
+
config.resolve_associations = true
|
|
135
|
+
config.humanizer = nil
|
|
262
136
|
|
|
263
|
-
|
|
264
|
-
config.
|
|
265
|
-
{
|
|
266
|
-
name: Current.user&.name,
|
|
267
|
-
email: Current.user&.email,
|
|
268
|
-
request_id: Current.request_id
|
|
269
|
-
}.compact
|
|
137
|
+
# Falhas.
|
|
138
|
+
config.raise_errors = Rails.env.test?
|
|
139
|
+
config.error_handler = ->(error, record, action) { Rails.error.report(error, context: { action: action, record: record&.class&.name }) }
|
|
270
140
|
end
|
|
271
141
|
```
|
|
272
142
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
Exemplo:
|
|
290
|
-
|
|
291
|
-
```ruby
|
|
292
|
-
config.uuid_resolver = -> { Current.audit_uuid }
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
Esse resolver define o `uuid` do log.
|
|
296
|
-
|
|
297
|
-
Esse `uuid` e util para correlacionar varios logs da mesma sessao ou do mesmo fluxo.
|
|
298
|
-
|
|
299
|
-
Se esse resolver nao retornar valor, a gem gera um UUID automaticamente.
|
|
300
|
-
|
|
301
|
-
#### `config.ip_resolver`
|
|
302
|
-
|
|
303
|
-
Exemplo:
|
|
304
|
-
|
|
305
|
-
```ruby
|
|
306
|
-
config.ip_resolver = -> { Current.request&.remote_ip }
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Esse resolver informa qual IP deve ser salvo em `ip_remote`.
|
|
310
|
-
|
|
311
|
-
Se voce nao quiser salvar IP, pode deixar `nil`.
|
|
312
|
-
|
|
313
|
-
#### `config.humanize_by_default`
|
|
314
|
-
|
|
315
|
-
Exemplo:
|
|
316
|
-
|
|
317
|
-
```ruby
|
|
318
|
-
config.humanize_by_default = true
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Se estiver `true`, a gem tenta gerar `audited_changes_humanize` por padrao.
|
|
143
|
+
| Opção | Padrão | O que faz |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `changed_by_id_resolver` | `nil` | Valor gravado em `changed_by_id` |
|
|
146
|
+
| `changed_by_type_resolver` | `nil` | Valor gravado em `changed_by_type` (classe, perfil, papel...) |
|
|
147
|
+
| `changed_by_other_resolver` | `-> { {} }` | Hash gravado em `changed_by_other` |
|
|
148
|
+
| `uuid_resolver` | `nil` | Uuid de correlação; sem valor, a gem gera um por log |
|
|
149
|
+
| `ip_resolver` | `nil` | Valor gravado em `ip_remote` |
|
|
150
|
+
| `ignored_attributes` | `created_at`, `updated_at`, `lock_version` | Campos que nunca entram no log |
|
|
151
|
+
| `masked_attributes` | `password`, `password_digest`, `encrypted_password`, `reset_password_token`, `confirmation_token`, `unlock_token` | Campos registrados sem o valor ([Campos sensíveis](#campos-sensíveis)) |
|
|
152
|
+
| `mask_value` | `"[FILTRADO]"` | Texto que substitui o valor mascarado |
|
|
153
|
+
| `humanize_by_default` | `true` | Gera o `audited_changes_humanize` |
|
|
154
|
+
| `i18n_scopes` | `["activerecord.attributes", "attributes"]` | Onde procurar a tradução dos campos |
|
|
155
|
+
| `resolve_associations` | `true` | Rótulo do registro em `belongs_to` ([Rótulo de associações](#rótulo-de-associações)) |
|
|
156
|
+
| `humanizer` | `nil` | Humanização personalizada ([Tradução dos campos](#tradução-dos-campos-i18n)) |
|
|
157
|
+
| `raise_errors` | `false` | Deixa o erro de gravação do log subir (útil em teste) |
|
|
158
|
+
| `error_handler` | `nil` | `->(error, record, action)` chamado quando gravar o log falha |
|
|
322
159
|
|
|
323
|
-
Se
|
|
160
|
+
Todo resolver é um lambda. Se um resolver lançar erro, a gem registra um aviso e grava o campo vazio; o log não se perde por isso. Uma configuração inválida, como um valor que não é lambda, gera `AuditLogger::ConfigurationError` na subida do app.
|
|
324
161
|
|
|
325
|
-
|
|
162
|
+
## Quem fez a alteração (contexto do ator)
|
|
326
163
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
```ruby
|
|
330
|
-
config.i18n_scopes = ["activerecord.attributes", "attributes"]
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Esses sao os escopos que a gem usa para tentar traduzir o nome dos atributos.
|
|
334
|
-
|
|
335
|
-
Por exemplo, ao auditar `Student.status`, a gem tenta procurar traducoes nesses caminhos.
|
|
336
|
-
|
|
337
|
-
#### `config.ignored_attributes`
|
|
338
|
-
|
|
339
|
-
Exemplo:
|
|
340
|
-
|
|
341
|
-
```ruby
|
|
342
|
-
config.ignored_attributes = [:created_at, :updated_at, :lock_version]
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
Aqui voce informa quais atributos nao devem entrar na auditoria por padrao.
|
|
346
|
-
|
|
347
|
-
Isso e util para evitar ruido.
|
|
348
|
-
|
|
349
|
-
#### `config.humanizer`
|
|
350
|
-
|
|
351
|
-
Exemplo:
|
|
352
|
-
|
|
353
|
-
```ruby
|
|
354
|
-
config.humanizer = ->(_model_klass, _attribute, _old_value, _new_value) { nil }
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Esse e um humanizer global opcional.
|
|
358
|
-
|
|
359
|
-
Ele existe para casos em que voce quer controlar manualmente como um campo sera apresentado.
|
|
360
|
-
|
|
361
|
-
Importante:
|
|
362
|
-
|
|
363
|
-
- se ele retornar `nil`, a gem usa o fallback padrao com I18n
|
|
364
|
-
- se ele retornar um `Hash`, esse hash e usado para montar o payload humanizado
|
|
365
|
-
- se ele retornar um valor simples, a gem usa esse valor como representacao humanizada
|
|
366
|
-
|
|
367
|
-
## Exemplo Real Com `Current`
|
|
368
|
-
|
|
369
|
-
Uma forma comum de integrar a gem no app cliente e usar `CurrentAttributes`.
|
|
370
|
-
|
|
371
|
-
Se o seu projeto ainda nao tiver um `Current`, voce pode criar algo assim:
|
|
164
|
+
A gem não sabe sozinha quem está logado. O jeito mais comum é usar `ActiveSupport::CurrentAttributes`:
|
|
372
165
|
|
|
373
166
|
```ruby
|
|
167
|
+
# app/models/current.rb
|
|
374
168
|
class Current < ActiveSupport::CurrentAttributes
|
|
375
|
-
attribute :user, :
|
|
169
|
+
attribute :user, :request_id, :ip
|
|
376
170
|
end
|
|
377
171
|
```
|
|
378
172
|
|
|
379
|
-
Esse objeto serve como um lugar central para guardar o contexto atual da requisicao.
|
|
380
|
-
|
|
381
|
-
Depois, no controller base da aplicacao, voce pode preencher esses dados:
|
|
382
|
-
|
|
383
173
|
```ruby
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
def store_current_context
|
|
390
|
-
Current.user = current_user
|
|
391
|
-
Current.request = request
|
|
174
|
+
# app/controllers/application_controller.rb
|
|
175
|
+
class ApplicationController < ActionController::Base # ou ActionController::API
|
|
176
|
+
before_action do
|
|
177
|
+
Current.user = current_user # na API com JWT, o usuário que o token identificou
|
|
392
178
|
Current.request_id = request.request_id
|
|
393
|
-
Current.
|
|
179
|
+
Current.ip = request.remote_ip
|
|
394
180
|
end
|
|
395
181
|
end
|
|
396
182
|
```
|
|
397
183
|
|
|
398
|
-
|
|
184
|
+
Com `uuid_resolver = -> { Current.request_id }`, todos os logs de uma mesma requisição ficam com o mesmo uuid. Por exemplo, salvar a pessoa e os contratos dela num formulário só.
|
|
399
185
|
|
|
400
|
-
|
|
401
|
-
- deixar `Current.request` disponivel para a gem
|
|
402
|
-
- manter um `audit_uuid` estavel dentro da sessao
|
|
186
|
+
Fora de requisição (jobs, rake, console), não há `Current.user`. O log é gravado com o ator vazio. Se quiser identificar a origem, preencha o `Current` no job.
|
|
403
187
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
## Ativando A Auditoria Na Model
|
|
407
|
-
|
|
408
|
-
Depois da instalacao e da configuracao global, voce ativa a auditoria na model com `auditable`.
|
|
409
|
-
|
|
410
|
-
Exemplo:
|
|
188
|
+
## Auditando uma model
|
|
411
189
|
|
|
412
190
|
```ruby
|
|
413
|
-
class
|
|
191
|
+
class Pessoa < ApplicationRecord
|
|
414
192
|
auditable
|
|
415
193
|
end
|
|
416
194
|
```
|
|
417
195
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
- criacao
|
|
421
|
-
- atualizacao
|
|
422
|
-
- remocao
|
|
423
|
-
|
|
424
|
-
## Consultando Os Logs Da Model
|
|
425
|
-
|
|
426
|
-
Ao usar `auditable`, a gem tambem define automaticamente a associacao:
|
|
427
|
-
|
|
428
|
-
```ruby
|
|
429
|
-
student.audit_logs
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
Ou seja, voce nao precisa criar manualmente um `has_many :audit_logs` basico para comecar.
|
|
196
|
+
Opções, todas opcionais e válidas só para a model:
|
|
433
197
|
|
|
434
|
-
Exemplo
|
|
198
|
+
| Opção | Exemplo | O que faz |
|
|
199
|
+
|---|---|---|
|
|
200
|
+
| `ignored_attributes` | `[:sincronizado_em]` | Soma aos ignorados globais |
|
|
201
|
+
| `masked_attributes` | `[:token_generate]` | Soma aos mascarados globais |
|
|
202
|
+
| `owner` | `:pessoa` ou `->(r) { r.contrato&.pessoa }` | Registro dono do log ([Histórico de um agregado](#histórico-de-um-agregado-dono)) |
|
|
203
|
+
| `humanize` | `false` | Liga ou desliga a humanização nesta model |
|
|
204
|
+
| `i18n_scopes` | `["meu_app.campos", "activerecord.attributes"]` | Escopos de tradução próprios |
|
|
205
|
+
| `humanizer` | `->(klass, campo, antigo, novo) { ... }` | Humanização própria da model |
|
|
435
206
|
|
|
436
207
|
```ruby
|
|
437
|
-
|
|
438
|
-
|
|
208
|
+
class User < ApplicationRecord
|
|
209
|
+
auditable masked_attributes: [:token_generate], ignored_attributes: [:last_sign_in_at]
|
|
210
|
+
end
|
|
439
211
|
```
|
|
440
212
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
- `model_class_name`
|
|
444
|
-
- `id_object`
|
|
445
|
-
|
|
446
|
-
Assim, cada model passa a enxergar apenas os logs que pertencem a ela.
|
|
447
|
-
|
|
448
|
-
### Exemplo Pratico
|
|
213
|
+
## Consultando os logs
|
|
449
214
|
|
|
450
215
|
```ruby
|
|
451
|
-
|
|
216
|
+
pessoa.audit_logs # logs do próprio registro
|
|
217
|
+
pessoa.audit_logs.where(action: "update").order(created_at: :desc)
|
|
452
218
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
# => 2
|
|
219
|
+
AuditLogger::AuditLog.of_owner(pessoa) # pessoa + registros que têm ela como dona
|
|
220
|
+
AuditLogger::AuditLog.where(model_class_name: "Contrato", action: "destroy")
|
|
221
|
+
AuditLogger::AuditLog.where(changed_by_id: user.id)
|
|
457
222
|
```
|
|
458
223
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
A gem grava na tabela:
|
|
462
|
-
|
|
463
|
-
- `model_class_name`: nome da classe auditada, por exemplo `Student`
|
|
464
|
-
- `id_object`: id do registro auditado, salvo como string
|
|
465
|
-
|
|
466
|
-
Com isso, a associacao consegue ligar corretamente:
|
|
467
|
-
|
|
468
|
-
- a model atual
|
|
469
|
-
- ao id correto do objeto auditado
|
|
470
|
-
|
|
471
|
-
Se no futuro voce quiser um nome diferente para a associacao ou um escopo proprio, ainda pode sobrescrever isso manualmente no projeto cliente.
|
|
472
|
-
|
|
473
|
-
## Preciso Criar Uma Model No Projeto Cliente?
|
|
474
|
-
|
|
475
|
-
Para o uso basico, nao.
|
|
476
|
-
|
|
477
|
-
A gem ja fornece a model:
|
|
224
|
+
Para telas administrativas, filtros com Ransack ou scopes próprios, crie uma model no app que herda da model da gem:
|
|
478
225
|
|
|
479
226
|
```ruby
|
|
480
|
-
AuditLogger::AuditLog
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
Entao, para comecar, voce pode usar:
|
|
484
|
-
|
|
485
|
-
- `self.audit_logs`
|
|
486
|
-
- `student.audit_logs`
|
|
487
|
-
- `AuditLogger::AuditLog.all`
|
|
488
|
-
|
|
489
|
-
## Uso Simples Com O Proprio Objeto
|
|
490
|
-
|
|
491
|
-
Se voce ja tem o objeto em maos e quer acessar os logs dele, o jeito mais simples e:
|
|
227
|
+
class AuditLog < AuditLogger::AuditLog
|
|
228
|
+
scope :recentes, -> { order(created_at: :desc) }
|
|
492
229
|
|
|
493
|
-
|
|
494
|
-
|
|
230
|
+
def self.ransackable_attributes(_auth = nil) = %w[model_class_name action changed_by_id created_at]
|
|
231
|
+
end
|
|
495
232
|
```
|
|
496
233
|
|
|
497
|
-
|
|
234
|
+
## Campos sensíveis
|
|
498
235
|
|
|
499
|
-
|
|
500
|
-
student = Student.find(1)
|
|
236
|
+
Pôr a senha em `ignored_attributes` faz a troca de senha sumir do histórico. Em `masked_attributes`, a alteração aparece, mas sem os valores:
|
|
501
237
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
student.audit_logs.order(created_at: :desc)
|
|
238
|
+
```json
|
|
239
|
+
{ "type": "update", "fields": { "encrypted_password": { "old_value": "[FILTRADO]", "new_value": "[FILTRADO]" } } }
|
|
505
240
|
```
|
|
506
241
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
-
|
|
510
|
-
- montar uma aba de auditoria no detalhe do objeto
|
|
511
|
-
- buscar apenas os logs daquele registro especifico
|
|
512
|
-
|
|
513
|
-
## Uso Avancado Para Relatorios, Rota E Ransack
|
|
242
|
+
- O valor nulo continua nulo, para mostrar que o campo estava vazio.
|
|
243
|
+
- No `create`, um campo mascarado preenchido aparece como `"[FILTRADO]"`.
|
|
244
|
+
- Se um campo estiver nos ignorados e nos mascarados ao mesmo tempo, vale o ignorado.
|
|
514
245
|
|
|
515
|
-
|
|
246
|
+
## Histórico de um agregado (dono)
|
|
516
247
|
|
|
517
|
-
|
|
518
|
-
- controllers e rotas proprias
|
|
519
|
-
- filtros com `ransack`
|
|
520
|
-
- telas administrativas
|
|
521
|
-
- scopes personalizados
|
|
522
|
-
|
|
523
|
-
ai vale a pena criar uma model no projeto cliente como wrapper da model da gem.
|
|
524
|
-
|
|
525
|
-
Exemplo:
|
|
248
|
+
Cada log guarda um registro "dono" (`owner_type`/`owner_id`). Sem configuração, o dono é o próprio registro. Para juntar o histórico de várias models num lugar só:
|
|
526
249
|
|
|
527
250
|
```ruby
|
|
528
|
-
class
|
|
251
|
+
class Pessoa < ApplicationRecord
|
|
252
|
+
auditable # dona de si mesma
|
|
529
253
|
end
|
|
530
|
-
```
|
|
531
254
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
### Exemplo Com Scopes
|
|
536
|
-
|
|
537
|
-
```ruby
|
|
538
|
-
class AuditLog < AuditLogger::AuditLog
|
|
539
|
-
scope :recent, -> { order(created_at: :desc) }
|
|
540
|
-
scope :from_model, ->(model_name) { where(model_class_name: model_name) }
|
|
255
|
+
class Contrato < ApplicationRecord
|
|
256
|
+
belongs_to :pessoa
|
|
257
|
+
auditable owner: :pessoa # associação
|
|
541
258
|
end
|
|
542
|
-
```
|
|
543
259
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
class AuditLogsController < ApplicationController
|
|
548
|
-
def index
|
|
549
|
-
@q = AuditLog.ransack(params[:q])
|
|
550
|
-
@audit_logs = @q.result.order(created_at: :desc)
|
|
551
|
-
end
|
|
260
|
+
class Dependente < ApplicationRecord
|
|
261
|
+
belongs_to :contrato
|
|
262
|
+
auditable owner: ->(dependente) { dependente.contrato&.pessoa } # caminho mais longo
|
|
552
263
|
end
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
### Exemplo De Rotas
|
|
556
264
|
|
|
557
|
-
|
|
558
|
-
Rails.application.routes.draw do
|
|
559
|
-
resources :audit_logs, only: [:index, :show]
|
|
560
|
-
end
|
|
265
|
+
AuditLogger::AuditLog.of_owner(pessoa) # tudo da pessoa, do mais recente ao mais antigo
|
|
561
266
|
```
|
|
562
267
|
|
|
563
|
-
|
|
268
|
+
Se a associação do dono estiver vazia, o dono passa a ser o próprio registro. Com STI, o `owner_type` é a classe base.
|
|
564
269
|
|
|
565
|
-
|
|
566
|
-
AuditLog.where(model_class_name: "Student")
|
|
567
|
-
AuditLog.where(action: "update")
|
|
568
|
-
AuditLog.order(created_at: :desc)
|
|
569
|
-
```
|
|
270
|
+
## Rótulo de associações
|
|
570
271
|
|
|
571
|
-
|
|
272
|
+
Com `resolve_associations = true`, a troca de um `belongs_to` ganha no formato humanizado o nome do registro antigo e do novo:
|
|
572
273
|
|
|
573
|
-
|
|
274
|
+
```json
|
|
275
|
+
{ "label": "Cargo", "old_value": 3, "new_value": 7, "old_value_label": "PROFESSOR", "new_value_label": "ASSESSOR" }
|
|
276
|
+
```
|
|
574
277
|
|
|
575
|
-
|
|
576
|
-
- relatorio geral do sistema: use `AuditLogger::AuditLog`
|
|
577
|
-
- tela administrativa, filtro, `ransack`, rota propria: crie `AuditLog < AuditLogger::AuditLog`
|
|
278
|
+
No `create` e no `destroy`, o rótulo vai em `value_label`. Ele vem do `to_s` definido na model associada; se ela não tiver um, de `display_name`, `descricao`, `nome`, `name` ou `title`. A busca inclui registros excluídos por soft delete (`with_deleted`), porque o histórico precisa do nome antigo.
|
|
578
279
|
|
|
579
|
-
|
|
280
|
+
Cada rótulo custa uma consulta ao banco. Em tabelas com muitas associações e muita escrita, avalie desligar a opção na model (`humanize: false`) ou no global.
|
|
580
281
|
|
|
581
|
-
|
|
282
|
+
## Eventos manuais
|
|
582
283
|
|
|
583
|
-
|
|
284
|
+
O que não passa pelos callbacks da model é registrado com `AuditLogger.record_event`. É o caso do `update_all`, do login e do pedido de reset de senha.
|
|
584
285
|
|
|
585
286
|
```ruby
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
label: "Campo #{attribute}",
|
|
594
|
-
old_value: old_value,
|
|
595
|
-
new_value: new_value
|
|
596
|
-
}
|
|
597
|
-
end
|
|
598
|
-
end
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
Nesse caso:
|
|
602
|
-
|
|
603
|
-
- a model usa `i18n_scopes` proprios
|
|
604
|
-
- ignora `updated_at` localmente
|
|
605
|
-
- pode ter um humanizer proprio so dela
|
|
606
|
-
|
|
607
|
-
## Como A Auditoria Funciona
|
|
287
|
+
# Com um registro: usa a humanização e o dono da model dele.
|
|
288
|
+
AuditLogger.record_event(
|
|
289
|
+
action: "password_reset_requested",
|
|
290
|
+
record: user,
|
|
291
|
+
fields: { encrypted_password: { old_value: "[FILTRADO]", new_value: "[FILTRADO]" } },
|
|
292
|
+
metadata: { origem: "esqueci_minha_senha" }
|
|
293
|
+
)
|
|
608
294
|
|
|
609
|
-
|
|
295
|
+
# Sem registro (ex.: login de CPF que não existe).
|
|
296
|
+
AuditLogger.record_event(action: "login_failed", model_class_name: "User", id_object: cpf, metadata: { motivo: "senha" })
|
|
610
297
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
Se houver rollback, a auditoria nao e persistida.
|
|
298
|
+
# Alteração em massa, com dono explícito.
|
|
299
|
+
ids = perfil.users.where(id: user_ids).pluck(:id)
|
|
300
|
+
perfil.users.where(id: ids).update_all(g_perfil_id: nil)
|
|
301
|
+
AuditLogger.record_event(action: "usuarios_desvinculados", record: perfil, metadata: { user_ids: ids })
|
|
302
|
+
```
|
|
617
303
|
|
|
618
|
-
|
|
304
|
+
| Parâmetro | Obrigatório | Conteúdo |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `action` | sim | Nome do evento |
|
|
307
|
+
| `record` | não | Registro relacionado; define model, id e dono |
|
|
308
|
+
| `model_class_name`, `id_object` | não | Usados quando não há `record` |
|
|
309
|
+
| `owner` | não | Dono explícito (sobrepõe o da model) |
|
|
310
|
+
| `fields` | não | Mesmo formato do log automático |
|
|
311
|
+
| `metadata` | não | Hash livre, gravado em `audited_changes["metadata"]` |
|
|
619
312
|
|
|
620
|
-
|
|
313
|
+
## Falhas não derrubam a requisição
|
|
621
314
|
|
|
622
|
-
|
|
315
|
+
O log é gravado depois do commit, quando o dado do usuário já foi salvo. Se a gravação do log falhar (banco fora, JSON inválido, erro no humanizer), a gem:
|
|
623
316
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
"fields": {
|
|
628
|
-
"name": {
|
|
629
|
-
"value": "Joao"
|
|
630
|
-
},
|
|
631
|
-
"status": {
|
|
632
|
-
"value": "active"
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
```
|
|
317
|
+
1. registra o erro com `Rails.logger.error`;
|
|
318
|
+
2. chama o `error_handler`, se houver;
|
|
319
|
+
3. segue sem lançar exceção. A requisição não vira erro 500.
|
|
637
320
|
|
|
638
|
-
|
|
321
|
+
Em teste, use `config.raise_errors = true` para o erro aparecer.
|
|
639
322
|
|
|
640
|
-
|
|
323
|
+
## Formato dos dados
|
|
641
324
|
|
|
642
|
-
|
|
643
|
-
{
|
|
644
|
-
"type": "update",
|
|
645
|
-
"fields": {
|
|
646
|
-
"status": {
|
|
647
|
-
"old_value": "active",
|
|
648
|
-
"new_value": "inactive"
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
```
|
|
325
|
+
`audited_changes` (bruto) e `audited_changes_humanize` (humanizado) têm a mesma estrutura:
|
|
653
326
|
|
|
654
|
-
|
|
327
|
+
```jsonc
|
|
328
|
+
// create e destroy: retrato completo
|
|
329
|
+
{ "type": "create", "fields": { "nome": { "value": "Ana" }, "cargo_id": { "value": 3 } } }
|
|
655
330
|
|
|
656
|
-
|
|
331
|
+
// update: só o que mudou
|
|
332
|
+
{ "type": "update", "fields": { "nome": { "old_value": "Ana", "new_value": "Ana Maria" } } }
|
|
657
333
|
|
|
658
|
-
|
|
659
|
-
{
|
|
660
|
-
"type": "destroy",
|
|
661
|
-
"fields": {
|
|
662
|
-
"name": {
|
|
663
|
-
"value": "Joao"
|
|
664
|
-
},
|
|
665
|
-
"status": {
|
|
666
|
-
"value": "inactive"
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
334
|
+
// evento manual
|
|
335
|
+
{ "type": "login_failed", "fields": {}, "metadata": { "motivo": "senha" } }
|
|
670
336
|
```
|
|
671
337
|
|
|
672
|
-
|
|
338
|
+
O humanizado acrescenta em cada campo:
|
|
339
|
+
- `label`: o nome traduzido;
|
|
340
|
+
- `old_value_label`, `new_value_label` ou `value_label`: o rótulo, nos campos de `belongs_to`.
|
|
673
341
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
```json
|
|
677
|
-
{
|
|
678
|
-
"type": "update",
|
|
679
|
-
"fields": {
|
|
680
|
-
"status": {
|
|
681
|
-
"label": "Situacao",
|
|
682
|
-
"old_value": "active",
|
|
683
|
-
"new_value": "inactive"
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
```
|
|
342
|
+
## Tradução dos campos (I18n)
|
|
688
343
|
|
|
689
|
-
|
|
344
|
+
O `label` é procurado nesta ordem, para cada escopo de `i18n_scopes`:
|
|
690
345
|
|
|
691
|
-
|
|
346
|
+
1. `<escopo>.<model>.<campo>`, ex.: `activerecord.attributes.pessoa.nome`;
|
|
347
|
+
2. `<escopo>.<campo>`, ex.: `attributes.nome`.
|
|
692
348
|
|
|
693
|
-
|
|
694
|
-
2. `attributes.<attribute>`
|
|
695
|
-
3. fallback para `attribute.humanize`
|
|
349
|
+
Se não achar nada, usa `campo.humanize`.
|
|
696
350
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
```yml
|
|
351
|
+
```yaml
|
|
700
352
|
pt-BR:
|
|
701
353
|
activerecord:
|
|
702
354
|
attributes:
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
attributes:
|
|
707
|
-
status: "Status"
|
|
355
|
+
pessoa:
|
|
356
|
+
nome: "Nome"
|
|
357
|
+
cargo_id: "Cargo"
|
|
708
358
|
```
|
|
709
359
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
360
|
+
Para controlar a apresentação de um campo, use um `humanizer`, global ou na model. Ele pode devolver:
|
|
361
|
+
- `nil`: usa a tradução padrão;
|
|
362
|
+
- um `Hash`: as chaves são mescladas ao campo (ex.: `{ label:, old_value:, new_value: }`);
|
|
363
|
+
- um valor simples: vira o `value` do campo.
|
|
713
364
|
|
|
714
365
|
```ruby
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
url: Current.request&.original_url,
|
|
720
|
-
user_agent: Current.request&.user_agent,
|
|
721
|
-
school_id: Current.school&.id
|
|
722
|
-
}.compact
|
|
366
|
+
auditable humanizer: ->(_klass, campo, antigo, novo) do
|
|
367
|
+
next unless campo == "situacao"
|
|
368
|
+
|
|
369
|
+
{ label: "Situação", old_value: SITUACOES[antigo], new_value: SITUACOES[novo] }
|
|
723
370
|
end
|
|
724
371
|
```
|
|
725
372
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
Se voce nao configurar nada, a gem usa:
|
|
373
|
+
Uma falha no humanizer é registrada como aviso, e o campo cai na tradução padrão.
|
|
729
374
|
|
|
730
|
-
|
|
731
|
-
- `humanize_by_default = true`
|
|
732
|
-
- `i18n_scopes = ["activerecord.attributes", "attributes"]`
|
|
733
|
-
- `ignored_attributes = [:created_at, :updated_at, :lock_version]`
|
|
375
|
+
## Atualizando da 0.1.x
|
|
734
376
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
## Limitacoes Atuais
|
|
738
|
-
|
|
739
|
-
- a migration usa `jsonb`, entao o uso esperado em producao e com PostgreSQL
|
|
740
|
-
- nos testes da gem, esses campos foram simulados com `text` em SQLite em memoria
|
|
741
|
-
- `url` e `user_agent` ainda nao possuem colunas proprias; hoje o recomendado e armazenar isso em `changed_by_other`
|
|
742
|
-
|
|
743
|
-
## Desenvolvimento
|
|
744
|
-
|
|
745
|
-
Depois de clonar o repositorio:
|
|
377
|
+
A 0.2.0 traz as colunas `owner_type` e `owner_id`. Para criá-las:
|
|
746
378
|
|
|
747
379
|
```bash
|
|
748
|
-
bin/
|
|
749
|
-
|
|
750
|
-
bundle exec rake test
|
|
380
|
+
bin/rails generate audit_logger:upgrade
|
|
381
|
+
bin/rails db:migrate
|
|
751
382
|
```
|
|
752
383
|
|
|
753
|
-
|
|
384
|
+
Sem as colunas a gem continua funcionando; só não grava o dono. As demais novidades estão no [CHANGELOG](CHANGELOG.md):
|
|
385
|
+
- campos mascarados;
|
|
386
|
+
- falha segura;
|
|
387
|
+
- rótulos de associação;
|
|
388
|
+
- eventos manuais.
|
|
754
389
|
|
|
755
|
-
|
|
390
|
+
## Limitações e cuidados
|
|
756
391
|
|
|
757
|
-
- `
|
|
758
|
-
-
|
|
759
|
-
- `
|
|
392
|
+
- **Operações em massa não disparam callbacks.** `update_all`, `delete_all`, `insert_all`, `upsert_all` e `update_column(s)` não geram log; registre com `AuditLogger.record_event`.
|
|
393
|
+
- **Soft delete:**
|
|
394
|
+
- com `paranoia`, o `destroy` dispara os callbacks de destroy, então confira no seu app se o `after_destroy_commit` é chamado;
|
|
395
|
+
- com `discard`, a exclusão é um `update` e aparece como tal, com `discarded_at` mudando.
|
|
396
|
+
- **PostgreSQL:** a migration gerada usa `jsonb`. Nos testes da gem, as colunas são `text` em SQLite.
|
|
397
|
+
- **`url` e `user_agent`** não têm coluna própria: guarde em `changed_by_other`.
|
|
760
398
|
|
|
761
|
-
|
|
399
|
+
## Desenvolvimento
|
|
762
400
|
|
|
763
401
|
```bash
|
|
764
|
-
|
|
402
|
+
bin/setup
|
|
403
|
+
bundle exec rake test # Minitest com SQLite em memória
|
|
404
|
+
bundle exec rubocop
|
|
405
|
+
bundle-audit check # dependências com CVE
|
|
765
406
|
```
|
|
766
407
|
|
|
767
|
-
|
|
408
|
+
Para publicar uma versão:
|
|
768
409
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
410
|
+
1. Atualize `lib/audit_logger/version.rb` e o `CHANGELOG.md`.
|
|
411
|
+
2. Rode `bundle exec rake test`.
|
|
412
|
+
3. Gere o pacote com `gem build audit_logger.gemspec`.
|
|
413
|
+
4. Publique com `gem push audit_log_rails-<versão>.gem` (pede login e o código de verificação do RubyGems).
|
|
414
|
+
5. Crie a tag `v<versão>` no Git e envie: `git tag v<versão> && git push origin main --tags`.
|
|
773
415
|
|
|
774
|
-
##
|
|
416
|
+
## Licença
|
|
775
417
|
|
|
776
|
-
|
|
418
|
+
Código aberto sob a [licença MIT](LICENSE.txt).
|