audit_log_rails 0.1.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 +7 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +776 -0
- data/Rakefile +12 -0
- data/lib/audit_logger/actor_context_resolver.rb +65 -0
- data/lib/audit_logger/audit_log.rb +47 -0
- data/lib/audit_logger/auditable.rb +47 -0
- data/lib/audit_logger/change_extractor.rb +75 -0
- data/lib/audit_logger/config_validator.rb +76 -0
- data/lib/audit_logger/configuration.rb +48 -0
- data/lib/audit_logger/humanizer.rb +112 -0
- data/lib/audit_logger/model_config.rb +80 -0
- data/lib/audit_logger/railtie.rb +17 -0
- data/lib/audit_logger/record_audit_entry.rb +61 -0
- data/lib/audit_logger/version.rb +5 -0
- data/lib/audit_logger.rb +44 -0
- data/lib/generators/audit_logger/install_generator.rb +33 -0
- data/lib/generators/audit_logger/templates/audit_logger_initializer.rb.tt +48 -0
- data/lib/generators/audit_logger/templates/create_audit_logs.rb.tt +32 -0
- data/sig/audit_logger.rbs +4 -0
- metadata +107 -0
data/README.md
ADDED
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
# AuditLogger
|
|
2
|
+
|
|
3
|
+
`AuditLogger` e uma gem Rails para auditar alteracoes em models ActiveRecord com:
|
|
4
|
+
|
|
5
|
+
- tabela propria de auditoria
|
|
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:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
gem "audit_log_rails"
|
|
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.
|
|
97
|
+
|
|
98
|
+
Em outras palavras:
|
|
99
|
+
|
|
100
|
+
- a gem cria o arquivo para voce
|
|
101
|
+
- mas voce precisa ajustar o conteudo conforme seu `Current.user`, `Current.request`, sessao, perfis, tenants e assim por diante
|
|
102
|
+
|
|
103
|
+
## O Que O Generator Cria Na Pratica
|
|
104
|
+
|
|
105
|
+
### Migration
|
|
106
|
+
|
|
107
|
+
A migration criada pela gem prepara a tabela `audit_logs`, onde os registros de auditoria serao persistidos.
|
|
108
|
+
|
|
109
|
+
### Initializer
|
|
110
|
+
|
|
111
|
+
O initializer criado pela gem e o lugar onde voce diz:
|
|
112
|
+
|
|
113
|
+
- quem e o usuario atual
|
|
114
|
+
- qual id deve ser salvo em `changed_by_id`
|
|
115
|
+
- qual tipo deve ser salvo em `changed_by_type`
|
|
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
|
|
121
|
+
|
|
122
|
+
## Estrutura Da Tabela
|
|
123
|
+
|
|
124
|
+
A migration inicial cria a tabela `audit_logs` com os campos:
|
|
125
|
+
|
|
126
|
+
- `model_class_name`
|
|
127
|
+
- `id_object`
|
|
128
|
+
- `action`
|
|
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`
|
|
139
|
+
|
|
140
|
+
## Configuracao Global Explicada
|
|
141
|
+
|
|
142
|
+
Depois de rodar o generator, a gem cria automaticamente o arquivo:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
config/initializers/audit_logger.rb
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Voce nao precisa criar esse arquivo manualmente do zero.
|
|
149
|
+
|
|
150
|
+
O papel desse arquivo e ensinar a gem a descobrir informacoes do seu sistema.
|
|
151
|
+
|
|
152
|
+
Importante:
|
|
153
|
+
|
|
154
|
+
- esse arquivo e criado automaticamente pela gem quando voce roda o generator
|
|
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
|
|
157
|
+
|
|
158
|
+
Se o seu sistema usa outro contexto, por exemplo `Current.admin`, `Current.account`, `session[:jwt]` ou qualquer outro objeto, basta trocar nos resolvers.
|
|
159
|
+
|
|
160
|
+
Exemplo do initializer:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
AuditLogger.configure do |config|
|
|
164
|
+
# Descomente apenas o que fizer sentido no seu projeto.
|
|
165
|
+
# A gem nao tem como adivinhar sozinha onde voce guarda usuario logado,
|
|
166
|
+
# request, tenant, sessao ou token.
|
|
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
|
|
198
|
+
|
|
199
|
+
Esse bloco:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
AuditLogger.configure do |config|
|
|
203
|
+
...
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
serve para configurar o comportamento global da gem no projeto inteiro.
|
|
208
|
+
|
|
209
|
+
Tudo que estiver aqui vira o comportamento padrao da auditoria, a menos que uma model sobrescreva algo localmente.
|
|
210
|
+
|
|
211
|
+
### Explicando Cada Configuracao
|
|
212
|
+
|
|
213
|
+
#### `config.changed_by_id_resolver`
|
|
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:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
config.changed_by_other_resolver = lambda do
|
|
265
|
+
{
|
|
266
|
+
name: Current.user&.name,
|
|
267
|
+
email: Current.user&.email,
|
|
268
|
+
request_id: Current.request_id
|
|
269
|
+
}.compact
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Esse resolver preenche o campo `changed_by_other`, que e um JSON livre.
|
|
274
|
+
|
|
275
|
+
Use esse campo quando quiser guardar metadados extras, por exemplo:
|
|
276
|
+
|
|
277
|
+
- nome do usuario
|
|
278
|
+
- email
|
|
279
|
+
- request id
|
|
280
|
+
- tenant
|
|
281
|
+
- school_id
|
|
282
|
+
- url
|
|
283
|
+
- user_agent
|
|
284
|
+
|
|
285
|
+
Esse campo existe justamente para voce nao ficar preso so a `changed_by_id` e `changed_by_type`.
|
|
286
|
+
|
|
287
|
+
#### `config.uuid_resolver`
|
|
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.
|
|
322
|
+
|
|
323
|
+
Se estiver `false`, a gem continua gravando o payload bruto, mas nao humaniza automaticamente.
|
|
324
|
+
|
|
325
|
+
#### `config.i18n_scopes`
|
|
326
|
+
|
|
327
|
+
Exemplo:
|
|
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:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
375
|
+
attribute :user, :request, :audit_uuid, :request_id
|
|
376
|
+
end
|
|
377
|
+
```
|
|
378
|
+
|
|
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
|
+
```ruby
|
|
384
|
+
class ApplicationController < ActionController::Base
|
|
385
|
+
before_action :store_current_context
|
|
386
|
+
|
|
387
|
+
private
|
|
388
|
+
|
|
389
|
+
def store_current_context
|
|
390
|
+
Current.user = current_user
|
|
391
|
+
Current.request = request
|
|
392
|
+
Current.request_id = request.request_id
|
|
393
|
+
Current.audit_uuid ||= session[:audit_uuid] ||= SecureRandom.uuid
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
O objetivo desse passo e simples:
|
|
399
|
+
|
|
400
|
+
- deixar `Current.user` disponivel para a gem
|
|
401
|
+
- deixar `Current.request` disponivel para a gem
|
|
402
|
+
- manter um `audit_uuid` estavel dentro da sessao
|
|
403
|
+
|
|
404
|
+
Se o seu sistema ja tiver outra estrategia para isso, nao precisa copiar exatamente esse exemplo.
|
|
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:
|
|
411
|
+
|
|
412
|
+
```ruby
|
|
413
|
+
class Student < ApplicationRecord
|
|
414
|
+
auditable
|
|
415
|
+
end
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
So isso ja faz a gem registrar:
|
|
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.
|
|
433
|
+
|
|
434
|
+
Exemplo:
|
|
435
|
+
|
|
436
|
+
```ruby
|
|
437
|
+
student = Student.find(1)
|
|
438
|
+
student.audit_logs
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Essa associacao ja filtra:
|
|
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
|
|
449
|
+
|
|
450
|
+
```ruby
|
|
451
|
+
student = Student.create!(name: "Joao")
|
|
452
|
+
|
|
453
|
+
student.update!(name: "Maria")
|
|
454
|
+
|
|
455
|
+
student.audit_logs.count
|
|
456
|
+
# => 2
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Como A Associacao Funciona
|
|
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:
|
|
478
|
+
|
|
479
|
+
```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:
|
|
492
|
+
|
|
493
|
+
```ruby
|
|
494
|
+
self.audit_logs
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Exemplo:
|
|
498
|
+
|
|
499
|
+
```ruby
|
|
500
|
+
student = Student.find(1)
|
|
501
|
+
|
|
502
|
+
student.audit_logs
|
|
503
|
+
student.audit_logs.where(action: "update")
|
|
504
|
+
student.audit_logs.order(created_at: :desc)
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Esse e o caminho ideal quando voce quer:
|
|
508
|
+
|
|
509
|
+
- mostrar historico dentro da tela do proprio registro
|
|
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
|
|
514
|
+
|
|
515
|
+
Se voce quiser montar:
|
|
516
|
+
|
|
517
|
+
- relatorios gerais
|
|
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:
|
|
526
|
+
|
|
527
|
+
```ruby
|
|
528
|
+
class AuditLog < AuditLogger::AuditLog
|
|
529
|
+
end
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Isso nao substitui a model da gem.
|
|
533
|
+
Isso apenas cria um ponto de entrada mais natural dentro da sua aplicacao.
|
|
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) }
|
|
541
|
+
end
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Exemplo De Controller
|
|
545
|
+
|
|
546
|
+
```ruby
|
|
547
|
+
class AuditLogsController < ApplicationController
|
|
548
|
+
def index
|
|
549
|
+
@q = AuditLog.ransack(params[:q])
|
|
550
|
+
@audit_logs = @q.result.order(created_at: :desc)
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Exemplo De Rotas
|
|
556
|
+
|
|
557
|
+
```ruby
|
|
558
|
+
Rails.application.routes.draw do
|
|
559
|
+
resources :audit_logs, only: [:index, :show]
|
|
560
|
+
end
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### Exemplo De Consulta Geral
|
|
564
|
+
|
|
565
|
+
```ruby
|
|
566
|
+
AuditLog.where(model_class_name: "Student")
|
|
567
|
+
AuditLog.where(action: "update")
|
|
568
|
+
AuditLog.order(created_at: :desc)
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## Regra Pratica
|
|
572
|
+
|
|
573
|
+
Se a necessidade for:
|
|
574
|
+
|
|
575
|
+
- historico do proprio registro: use `self.audit_logs`
|
|
576
|
+
- relatorio geral do sistema: use `AuditLogger::AuditLog`
|
|
577
|
+
- tela administrativa, filtro, `ransack`, rota propria: crie `AuditLog < AuditLogger::AuditLog`
|
|
578
|
+
|
|
579
|
+
## Sobrescrevendo Configuracao Em Uma Model
|
|
580
|
+
|
|
581
|
+
Se uma model precisar de comportamento diferente do padrao global, voce pode sobrescrever localmente.
|
|
582
|
+
|
|
583
|
+
Exemplo:
|
|
584
|
+
|
|
585
|
+
```ruby
|
|
586
|
+
class Student < ApplicationRecord
|
|
587
|
+
auditable \
|
|
588
|
+
humanize: true,
|
|
589
|
+
i18n_scopes: ["school.student", "activerecord.attributes"],
|
|
590
|
+
ignored_attributes: [:updated_at],
|
|
591
|
+
humanizer: ->(_model_klass, attribute, old_value, new_value) do
|
|
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
|
|
608
|
+
|
|
609
|
+
A gem usa callbacks de commit:
|
|
610
|
+
|
|
611
|
+
- `after_create_commit`
|
|
612
|
+
- `after_update_commit`
|
|
613
|
+
- `after_destroy_commit`
|
|
614
|
+
|
|
615
|
+
Isso significa que o log so e gravado quando a transacao foi confirmada com sucesso.
|
|
616
|
+
Se houver rollback, a auditoria nao e persistida.
|
|
617
|
+
|
|
618
|
+
## Contrato Do Payload Bruto
|
|
619
|
+
|
|
620
|
+
### Create
|
|
621
|
+
|
|
622
|
+
Em `create`, a gem grava um snapshot completo dos atributos auditaveis:
|
|
623
|
+
|
|
624
|
+
```json
|
|
625
|
+
{
|
|
626
|
+
"type": "create",
|
|
627
|
+
"fields": {
|
|
628
|
+
"name": {
|
|
629
|
+
"value": "Joao"
|
|
630
|
+
},
|
|
631
|
+
"status": {
|
|
632
|
+
"value": "active"
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### Update
|
|
639
|
+
|
|
640
|
+
Em `update`, a gem grava apenas os campos alterados:
|
|
641
|
+
|
|
642
|
+
```json
|
|
643
|
+
{
|
|
644
|
+
"type": "update",
|
|
645
|
+
"fields": {
|
|
646
|
+
"status": {
|
|
647
|
+
"old_value": "active",
|
|
648
|
+
"new_value": "inactive"
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Destroy
|
|
655
|
+
|
|
656
|
+
Em `destroy`, a gem grava um snapshot antes da remocao:
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
{
|
|
660
|
+
"type": "destroy",
|
|
661
|
+
"fields": {
|
|
662
|
+
"name": {
|
|
663
|
+
"value": "Joao"
|
|
664
|
+
},
|
|
665
|
+
"status": {
|
|
666
|
+
"value": "inactive"
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
## Contrato Do Payload Humanizado
|
|
673
|
+
|
|
674
|
+
O campo `audited_changes_humanize` segue a mesma estrutura do payload bruto, mas adiciona labels amigaveis:
|
|
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
|
+
```
|
|
688
|
+
|
|
689
|
+
## Humanizacao Com I18n
|
|
690
|
+
|
|
691
|
+
Por padrao, a gem tenta buscar labels nesta ordem:
|
|
692
|
+
|
|
693
|
+
1. `activerecord.attributes.<model>.<attribute>`
|
|
694
|
+
2. `attributes.<attribute>`
|
|
695
|
+
3. fallback para `attribute.humanize`
|
|
696
|
+
|
|
697
|
+
Exemplo:
|
|
698
|
+
|
|
699
|
+
```yml
|
|
700
|
+
pt-BR:
|
|
701
|
+
activerecord:
|
|
702
|
+
attributes:
|
|
703
|
+
student:
|
|
704
|
+
name: "Nome"
|
|
705
|
+
status: "Situacao"
|
|
706
|
+
attributes:
|
|
707
|
+
status: "Status"
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
## Metadados Extras Do Ator
|
|
711
|
+
|
|
712
|
+
O campo `changed_by_other` foi pensado para guardar metadados livres do ator e da requisicao, por exemplo:
|
|
713
|
+
|
|
714
|
+
```ruby
|
|
715
|
+
config.changed_by_other_resolver = lambda do
|
|
716
|
+
{
|
|
717
|
+
name: Current.user&.name,
|
|
718
|
+
email: Current.user&.email,
|
|
719
|
+
url: Current.request&.original_url,
|
|
720
|
+
user_agent: Current.request&.user_agent,
|
|
721
|
+
school_id: Current.school&.id
|
|
722
|
+
}.compact
|
|
723
|
+
end
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
## Defaults Atuais Da Gem
|
|
727
|
+
|
|
728
|
+
Se voce nao configurar nada, a gem usa:
|
|
729
|
+
|
|
730
|
+
- `changed_by_other_resolver = -> { {} }`
|
|
731
|
+
- `humanize_by_default = true`
|
|
732
|
+
- `i18n_scopes = ["activerecord.attributes", "attributes"]`
|
|
733
|
+
- `ignored_attributes = [:created_at, :updated_at, :lock_version]`
|
|
734
|
+
|
|
735
|
+
Se `uuid_resolver` nao retornar valor, a gem gera um UUID automaticamente.
|
|
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:
|
|
746
|
+
|
|
747
|
+
```bash
|
|
748
|
+
bin/setup
|
|
749
|
+
bundle install
|
|
750
|
+
bundle exec rake test
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
## Testes
|
|
754
|
+
|
|
755
|
+
A gem usa:
|
|
756
|
+
|
|
757
|
+
- `Minitest`
|
|
758
|
+
- `ActiveRecord`
|
|
759
|
+
- `SQLite` em memoria para a suite local
|
|
760
|
+
|
|
761
|
+
Para rodar os testes:
|
|
762
|
+
|
|
763
|
+
```bash
|
|
764
|
+
bundle exec rake test
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
## Proximos Passos Sugeridos
|
|
768
|
+
|
|
769
|
+
- refinar README com mais exemplos por dominio
|
|
770
|
+
- adicionar testes especificos de rollback
|
|
771
|
+
- documentar estrategias para soft delete
|
|
772
|
+
- melhorar metadata publica da gem no `gemspec`
|
|
773
|
+
|
|
774
|
+
## License
|
|
775
|
+
|
|
776
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|