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.
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).