logidze 0.11.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +79 -4
  3. data/LICENSE.txt +1 -1
  4. data/README.md +305 -102
  5. data/lib/generators/logidze/fx_helper.rb +17 -0
  6. data/lib/generators/logidze/inject_sql.rb +18 -0
  7. data/lib/generators/logidze/install/USAGE +6 -1
  8. data/lib/generators/logidze/install/functions/logidze_capture_exception.sql +23 -0
  9. data/lib/generators/logidze/install/functions/logidze_compact_history.sql +38 -0
  10. data/lib/generators/logidze/install/functions/logidze_filter_keys.sql +27 -0
  11. data/lib/generators/logidze/install/functions/logidze_logger.sql +203 -0
  12. data/lib/generators/logidze/install/functions/logidze_snapshot.sql +33 -0
  13. data/lib/generators/logidze/install/functions/logidze_version.sql +21 -0
  14. data/lib/generators/logidze/install/install_generator.rb +43 -1
  15. data/lib/generators/logidze/install/templates/hstore.rb.erb +1 -1
  16. data/lib/generators/logidze/install/templates/migration.rb.erb +19 -232
  17. data/lib/generators/logidze/install/templates/migration_fx.rb.erb +41 -0
  18. data/lib/generators/logidze/model/model_generator.rb +53 -13
  19. data/lib/generators/logidze/model/templates/migration.rb.erb +57 -36
  20. data/lib/generators/logidze/model/triggers/logidze.sql +6 -0
  21. data/lib/logidze.rb +37 -14
  22. data/lib/logidze/engine.rb +9 -0
  23. data/lib/logidze/has_logidze.rb +1 -1
  24. data/lib/logidze/history.rb +2 -11
  25. data/lib/logidze/ignore_log_data.rb +1 -3
  26. data/lib/logidze/meta.rb +43 -16
  27. data/lib/logidze/model.rb +51 -44
  28. data/lib/logidze/utils/check_pending.rb +57 -0
  29. data/lib/logidze/utils/function_definitions.rb +49 -0
  30. data/lib/logidze/utils/pending_migration_error.rb +25 -0
  31. data/lib/logidze/version.rb +1 -1
  32. metadata +69 -77
  33. data/.gitattributes +0 -3
  34. data/.github/ISSUE_TEMPLATE.md +0 -20
  35. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  36. data/.gitignore +0 -40
  37. data/.rubocop.yml +0 -55
  38. data/.travis.yml +0 -42
  39. data/Gemfile +0 -15
  40. data/Rakefile +0 -28
  41. data/assets/pg_log_data_chart.png +0 -0
  42. data/bench/performance/README.md +0 -109
  43. data/bench/performance/diff_bench.rb +0 -38
  44. data/bench/performance/insert_bench.rb +0 -22
  45. data/bench/performance/memory_profile.rb +0 -56
  46. data/bench/performance/setup.rb +0 -315
  47. data/bench/performance/update_bench.rb +0 -38
  48. data/bench/triggers/Makefile +0 -56
  49. data/bench/triggers/Readme.md +0 -58
  50. data/bench/triggers/bench.sql +0 -6
  51. data/bench/triggers/hstore_trigger_setup.sql +0 -38
  52. data/bench/triggers/jsonb_minus_2_setup.sql +0 -47
  53. data/bench/triggers/jsonb_minus_setup.sql +0 -49
  54. data/bench/triggers/keys2_trigger_setup.sql +0 -44
  55. data/bench/triggers/keys_trigger_setup.sql +0 -50
  56. data/bin/console +0 -8
  57. data/bin/setup +0 -9
  58. data/gemfiles/rails42.gemfile +0 -6
  59. data/gemfiles/rails5.gemfile +0 -6
  60. data/gemfiles/rails52.gemfile +0 -6
  61. data/gemfiles/rails6.gemfile +0 -6
  62. data/gemfiles/railsmaster.gemfile +0 -7
  63. data/lib/logidze/ignore_log_data/association.rb +0 -11
  64. data/lib/logidze/ignore_log_data/ignored_columns.rb +0 -46
  65. data/lib/logidze/migration.rb +0 -20
  66. data/logidze.gemspec +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75f1f01b10a1e35d8b656a27f3d71b0e1d2f81c826338fa89d36d76ac03363b9
4
- data.tar.gz: 3812d6d4b49d2fe803acc61854b1ca891ec816bbd4a3e05ff60e02bc62393ac0
3
+ metadata.gz: 7130810a9954a68eb38b0c9484b58091a599d662d36e61d4f2072dbe12612807
4
+ data.tar.gz: a4bc08a8a998826263441aa50cb263e5d3bcc36f48ede24bdad60ef4d7e3f640
5
5
  SHA512:
6
- metadata.gz: 7b38094dd8e8b7de7295b354e0906067641080e90169f0222b163b43a491672921eff25a78062680650e1e8565727d01dcd4694a84fecbeb7ff60a918d2fbe6c
7
- data.tar.gz: 7ba5a0605b9b45e63bb6c02b9d18b05f78331369ed4aeb2564434d1e627013e360b1ef0ab2f2fdd94203de6ed341d3136d800e2b8aa838d6fe6c501eecb5d98d
6
+ metadata.gz: 1136d0509508787e18f3839f63293b384f315438cb2675f4ff517b2ca3afa9da9e7e71d013a234a745fac860c499bf29ebcb0949bc245576c53d02a2217f30d9
7
+ data.tar.gz: 9ee0339acaddf4c442da9699485e33acc57bc0ae20c98a80b5b08ea940cc59a6bc6a93c5d2d5d56d6eac95d4f86f4704bf85536f601304908b6cb5fc28dfb913
data/CHANGELOG.md CHANGED
@@ -2,6 +2,78 @@
2
2
 
3
3
  ## master (unreleased)
4
4
 
5
+ ## 1.2.0 (2021-06-11)
6
+
7
+ - Add user-defined exception handling ([@skryukov][])
8
+
9
+ By default, Logidze raises an exception which causes the entire transaction to fail.
10
+ To change this behavior, it's now possible to override `logidze_capture_exception(error_data jsonb)` function.
11
+
12
+ - [Fixes [#69](https://github.com/palkan/logidze/issues/69)] Fallback on NUMERIC_VALUE_OUT_OF_RANGE exception ([@skryukov][])
13
+
14
+ - [Fixes [#192](https://github.com/palkan/logidze/issues/192)] Skip `log_data` column during `apply_column_diff` ([@skryukov][])
15
+
16
+ ## 1.1.0 (2021-03-31)
17
+
18
+ - Add pending upgrade checks [Experimental]. ([@skryukov][])
19
+
20
+ Now Logidze can check for a pending upgrade. Use `Logidze.pending_upgrade = :warn` to be notified by warning, or `Logidze.pending_upgrade = :error` if you want Logidze to raise an error.
21
+
22
+ - [Fixes [#171](https://github.com/palkan/logidze/issues/171)] Stringify jsonb column values within snapshots. ([@skryukov][])
23
+
24
+ - [Fixes [#175](https://github.com/palkan/logidze/issues/175)] Set dynamic ActiveRecord version for migrations. ([@skryukov][])
25
+
26
+ - [Fixes [#184](https://github.com/palkan/logidze/issues/184)] Remove Rails meta-gem dependency ([@bf4][])
27
+
28
+ ## 1.0.0 (2020-11-09)
29
+
30
+ - Add `--name` option to model generator to specify the migration name. ([@palkan][])
31
+
32
+ When you update Logidze installation for a model multiple times, you might hit the `DuplicateMigrationNameError` (see [#167](https://github.com/palkan/logidze/issues/167)).
33
+
34
+ - Add `.with_full_snapshot` to add full snapshots to the log instead of diffs. ([@palkan][])
35
+
36
+ Useful in combination with `.without_logging`: first, you perform multiple updates without logging, then
37
+ you do something like `with_full_snapshot { record.touch }` to create a log entry with the current state.
38
+
39
+ - Add `#create_logidze_snapshot!` and `.create_logidze_snapshot` methods. ([@palkan][])
40
+
41
+ - Add integration with `fx` gem. ([@palkan][])
42
+
43
+ Now it's possible to use Logidze with `schema.rb`. Add `fx` gem to the project, and new migrations will be
44
+ using Fx `create_function` / `create_trigger` functions.
45
+
46
+ - Refactored columns filtering. ([@palkan][])
47
+
48
+ Renamed `--whitelist/--blacklist` to `--only/--except` correspondingly.
49
+
50
+ The _only_-logic has been changed: previously we collected the list of columns to ignore at the migration generation time,
51
+ now we filter the columns within the trigger function (thus, schema changes do not affect the columns being tracked).
52
+
53
+ - **Dropped support for Rails 4.2, Ruby 2.4 and PostgreSQL 9.5**. ([@palkan][])
54
+
55
+ ## 0.12.0 (2020-01-02)
56
+
57
+ - PR [#143](https://github.com/palkan/logidze/pull/143) Add `:transactional` option to `#with_meta` and `#with_responsible` ([@oleg-kiviljov][])
58
+
59
+ Now it's possible to set meta and responsible without wrapping the block into a DB transaction. For backward compatibility `:transactional` option by default is set to `true`.
60
+
61
+ Usage:
62
+
63
+ ```ruby
64
+ Logidze.with_meta({ip: request.ip}, transactional: false) do
65
+ post.save!
66
+ end
67
+ ```
68
+
69
+ or
70
+
71
+ ```ruby
72
+ Logidze.with_responsible(user.id, transactional: false) do
73
+ post.save!
74
+ end
75
+ ```
76
+
5
77
  ## 0.11.0 (2019-08-15)
6
78
 
7
79
  - **Breaking** Return `nil` when `log_data` is not loaded instead of raising an exception. ([@palkan][])
@@ -101,7 +173,7 @@ Please run `rails generate logidze:install --update` to regenerate stored functi
101
173
  This feature checks if several logs came in within a debounce time period then only keep the latest one
102
174
  by merging the latest in previous others.
103
175
 
104
- The concept is similar to https://underscorejs.org/#debounce
176
+ The concept is similar to [https://underscorejs.org/#debounce](https://underscorejs.org/#debounce).
105
177
 
106
178
  without `debounce_time`
107
179
 
@@ -162,7 +234,7 @@ with `debounce_time` of `10ms`
162
234
 
163
235
  ## 0.7.0 (2018-08-29)
164
236
 
165
- - [Fixes [#75](https://github.com/palkan/logidze/issues/70)] Fix association versioning with an optional belongs to ([@ankursethi-uscis][])
237
+ - [Fixes [#75](https://github.com/palkan/logidze/issues/70)] Fix association versioning with an optional belongs to ([@amalagaura][])
166
238
 
167
239
  - [PR [#79](https://github.com/palkan/logidze/pull/13)] Allow adding meta information to versions using `with_meta` (addressed [Issue [#60]](https://github.com/palkan/logidze/issues/60)). ([@DmitryTsepelev][])
168
240
 
@@ -230,7 +302,7 @@ This is a quick fix for a more general problem (see [#59](https://github.com/pal
230
302
 
231
303
  ## 0.5.1 (2017-06-15)
232
304
 
233
- - _(Fix)_ Drop _all_ created functions upon rolling back (https://github.com/palkan/logidze/commit/b8e150cc18b3316a8cf0c78f7117339481fb49c6). ([@vassilevsky][])
305
+ - _(Fix)_ Drop _all_ created functions upon rolling back ([commit](https://github.com/palkan/logidze/commit/b8e150cc18b3316a8cf0c78f7117339481fb49c6)). ([@vassilevsky][])
234
306
 
235
307
  ## 0.5.0 (2017-03-28)
236
308
 
@@ -280,7 +352,10 @@ This is a quick fix for a more general problem (see [#59](https://github.com/pal
280
352
  [@charlie-wasp]: https://github.com/charlie-wasp
281
353
  [@akxcv]: https://github.com/akxcv
282
354
  [@vassilevsky]: https://github.com/vassilevsky
283
- [@ankursethi-uscis]: https://github.com/ankursethi-uscis
355
+ [@amalagaura]: https://github.com/amalagaura
284
356
  [@dmitrytsepelev]: https://github.com/DmitryTsepelev
285
357
  [@zocoi]: https://github.com/zocoi
286
358
  [@duderman]: https://github.com/duderman
359
+ [@oleg-kiviljov]: https://github.com/oleg-kiviljov
360
+ [@skryukov]: https://github.com/skryukov
361
+ [@bf4]: https://github.com/bf4
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016-2019 palkan
3
+ Copyright (c) 2016-2020 Vladimir Dementyev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,37 +1,67 @@
1
- [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com) [![Gem Version](https://badge.fury.io/rb/logidze.svg)](https://rubygems.org/gems/logidze) [![Build Status](https://travis-ci.org/palkan/logidze.svg?branch=master)](https://travis-ci.org/palkan/logidze) [![Open Source Helpers](https://www.codetriage.com/palkan/logidze/badges/users.svg)](https://www.codetriage.com/palkan/logidze)
1
+ [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com)
2
+ [![Gem Version](https://badge.fury.io/rb/logidze.svg)](https://rubygems.org/gems/logidze)
3
+ [![Build](https://github.com/palkan/logidze/workflows/Build/badge.svg)](https://github.com/palkan/logidze/actions)
4
+ [![Open Source Helpers](https://www.codetriage.com/palkan/logidze/badges/users.svg)](https://www.codetriage.com/palkan/logidze)
2
5
 
3
6
  # Logidze
4
7
 
5
- Logidze provides tools for logging DB records changes. Just like [audited](https://github.com/collectiveidea/audited) and [paper_trail](https://github.com/airblade/paper_trail) do (but [faster](bench/performance)).
8
+ Logidze provides tools for logging DB records changes when using PostgreSQL (>=9.6). Just like [audited](https://github.com/collectiveidea/audited) and [paper_trail](https://github.com/airblade/paper_trail) do (but [faster](bench/performance)).
6
9
 
7
10
  Logidze allows you to create a DB-level log (using triggers) and gives you an API to browse this log.
8
11
  The log is stored with the record itself in JSONB column. No additional tables required.
9
- Currently, only PostgreSQL 9.5+ is supported (for PostgreSQL 9.4 try [jsonbx](http://www.pgxn.org/dist/jsonbx/1.0.0/) extension).
10
12
 
11
- [Read the story behind Logidze](https://evilmartians.com/chronicles/introducing-logidze?utm_source=logidze)
12
-
13
- [How is Logidze pronounced?](https://github.com/palkan/logidze/issues/73)
13
+ 🤔 [How is Logidze pronounced?](https://github.com/palkan/logidze/issues/73)
14
14
 
15
15
  Other requirements:
16
16
 
17
- - Ruby ~> 2.1
18
- - Rails >= 4.2 (**Rails 6 is supported**)
17
+ - Ruby ~> 2.5
18
+ - Rails >= 5.0 (for Rails 4.2 use version <=0.12.0)
19
19
 
20
20
  <a href="https://evilmartians.com/">
21
21
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
22
22
 
23
+ ## Links
24
+
25
+ - [Logidze 1.0: Active Record, Postgres, Rails, and time travel](https://evilmartians.com/chronicles/logidze-1-0-active-record-postgresql-rails-and-time-travel?utm_source=logidze)
26
+ - [Logidze: for all those tired of versioning data](https://evilmartians.com/chronicles/introducing-logidze?utm_source=logidze)
27
+
28
+ ## Table of contents
29
+
30
+ - [Installation & Configuration](#installation)
31
+ - [Using with schema.rb](#using-with-schemarb)
32
+ - [Configuring models](#configuring-models)
33
+ - [Backfill data](#backfill-data)
34
+ - [Log size limits](#log-size-limits)
35
+ - [Tracking only selected columns](#tracking-only-selected-columns)
36
+ - [Logs timestamps](#logs-timestamps)
37
+ - [Usage](#usage)
38
+ - [Basic API](#basic-api)
39
+ - [Track meta information](#track-meta-information)
40
+ - [Track responsibility](#track-responsibility)
41
+ - [Disable logging temporary](#disable-logging-temporary)
42
+ - [Reset log](#reset-log)
43
+ - [Creating full snapshot instead of diffs](#full-snapshots)
44
+ - [Associations versioning](#associations-versioning)
45
+ - [Dealing with large logs](#dealing-with-large-logs)
46
+ - [Handling records deletion](#handling-records-deletion)
47
+ - [Handling PG exceptions](#handling-pg-exceptions)
48
+ - [Upgrading](#upgrading)
49
+ - [Log format](#log-format)
50
+ - [Troubleshooting 🚨](#troubleshooting)
51
+ - [Development](#development)
52
+
23
53
  ## Installation
24
54
 
25
55
  Add Logidze to your application's Gemfile:
26
56
 
27
57
  ```ruby
28
- gem "logidze"
58
+ gem "logidze", "~> 1.1"
29
59
  ```
30
60
 
31
61
  Install required DB extensions and create trigger function:
32
62
 
33
63
  ```sh
34
- rails generate logidze:install
64
+ bundle exec rails generate logidze:install
35
65
  ```
36
66
 
37
67
  This creates a migration for adding trigger function and enabling the hstore extension.
@@ -39,95 +69,119 @@ This creates a migration for adding trigger function and enabling the hstore ext
39
69
  Run migrations:
40
70
 
41
71
  ```sh
42
- rake db:migrate
72
+ bundle exec rails db:migrate
43
73
  ```
44
74
 
45
- **NOTE:** you **must** use SQL schema format since Logidze uses DB functions and triggers:
75
+ **NOTE:** Logidze uses DB functions and triggers, hence you need to use SQL format for a schema dump:
46
76
 
47
77
  ```ruby
48
78
  # application.rb
49
79
  config.active_record.schema_format = :sql
50
80
  ```
51
81
 
52
- 3. Add log column and triggers to the model:
82
+ ### Using with schema.rb
83
+
84
+ Logidze seamlessly integrates with [fx][] gem to make it possible to continue using `schema.rb` for the database schema dump.
85
+
86
+ Add `fx` gem to your Gemfile and run the same Logidze generators: `rails g logidze:install` or `rails g logidze:model`.
87
+
88
+ If for some reason Logidze couldn't detect the presence of Fx in your bundle, you can enforce it by passing `--fx` option to generators.
89
+
90
+ On the other hand, if you have `fx` gem but don't want Logidze to use it—pass `--no-fx` option.
91
+
92
+ ### Configuring models
93
+
94
+ Run the following migration to enable changes tracking for an Active Record model and adding a `log_data::jsonb` column to the table:
53
95
 
54
96
  ```sh
55
- rails generate logidze:model Post
56
- rake db:migrate
97
+ bundle exec rails generate logidze:model Post
98
+ bundle exec rails db:migrate
57
99
  ```
58
100
 
59
101
  This also adds `has_logidze` line to your model, which adds methods for working with logs.
60
102
 
61
- You can provide the `limit` option to `generate` to limit the size of the log (by default it's unlimited):
103
+ By default, Logidze tries to infer the path to the model file from the model name and may fail, for example, if you have unconventional project structure. In that case, you should specify the path explicitly:
62
104
 
63
105
  ```sh
64
- rails generate logidze:model Post --limit=10
106
+ bundle exec rails generate logidze:model Post --path "app/models/custom/post.rb"
65
107
  ```
66
108
 
67
- To backfill table data (i.e., create initial snapshots) add `backfill` option:
109
+ ### Backfill data
110
+
111
+ To backfill table data (i.e., create initial snapshots) add `backfill` option to the generator:
68
112
 
69
113
  ```sh
70
- rails generate logidze:model Post --backfill
114
+ bundle exec rails generate logidze:model Post --backfill
71
115
  ```
72
116
 
73
- You can log only particular columns changes. There are mutually exclusive `blacklist` and `whitelist` options for this:
117
+ Now your migration should contain and `UPDATE ...` statement to populate the `log_data` column with the current state.
74
118
 
75
- ```sh
76
- # track all columns, except `created_at` and `active`
77
- rails generate logidze:model Post --blacklist=created_at,active
78
- # track only `title` and `body` columns
79
- rails generate logidze:model Post --whitelist=title,body
80
- ```
119
+ Otherwise a full snapshot will be created the first time the record is updated.
81
120
 
82
- By default, Logidze tries to infer the path to the model file from the model name and may fail, for example, if you have unconventional project structure. In that case, you should specify the path explicitly:
121
+ You can create a snapshot manually by performing the following query:
83
122
 
84
- ```sh
85
- rails generate logidze:model Post --path "app/models/custom/post.rb"
123
+ ```sql
124
+ UPDATE <my_table> as t
125
+ SET log_data = logidze_snapshot(to_jsonb(t))
86
126
  ```
87
127
 
88
- By default, Logidze tries to get a timestamp for a version from record's `updated_at` field whenever appropriate. If
89
- your model does not have that column, Logidze will gracefully fall back to `statement_timestamp()`.
90
- To change the column name or disable this feature completely, you can use the `timestamp_column` option:
128
+ Or by using the following methods:
91
129
 
92
- ```sh
93
- # will try to get the timestamp value from `time` column
94
- rails generate logidze:model Post --timestamp_column time
95
- # will always set version timestamp to `statement_timestamp()`
96
- rails generate logidze:model Post --timestamp_column nil # "null" and "false" will also work
97
- ```
130
+ ```ruby
131
+ Model.create_logidze_snapshot
98
132
 
99
- If you want to update Logidze settings for the model, run migration with `--update` flag:
133
+ # specify the timestamp column to use for the initial version (by default the current time is used)
134
+ Model.create_logidze_snapshot(timestamp: :created_at)
100
135
 
101
- ```sh
102
- rails generate logidze:model Post --update --whitelist=title,body,rating
103
- ```
136
+ # filter columns
137
+ Model.create_logidze_snapshot(only: %(name))
138
+ Model.create_logidze_snapshot(except: %(password))
104
139
 
105
- Logidze also supports associations versioning. It is an experimental feature and disabled by default. You can learn more
106
- in the [wiki](https://github.com/palkan/logidze/wiki/Associations-versioning).
140
+ # or call a similar method (but with !) on a record
107
141
 
108
- ## Troubleshooting
142
+ my_model = Model.find(params[:id])
143
+ my_model.create_logidze_snapshot!(timestamp: :created_at)
144
+ ```
109
145
 
110
- The most common problem is `"permission denied to set parameter "logidze.xxx"` caused by `ALTER DATABASE ...` query.
111
- Logidze requires at least database owner privileges (which is not always possible).
146
+ A snapshot is only created if `log_data` is null.
112
147
 
113
- Here is a quick and straightforward [workaround](https://github.com/palkan/logidze/issues/11#issuecomment-260703464) by [@nobodyzzz](https://github.com/nobodyzzz).
148
+ ### Log size limits
114
149
 
115
- **NOTE**: if you're using PostgreSQL >= 9.6 you need neither the workaround nor owner privileges because Logidze (>= 0.3.1) can work without `ALTER DATABASE ...`.
150
+ You can provide the `limit` option to `generate` to limit the size of the log (by default it's unlimited):
116
151
 
117
- Nevertheless, you still need super-user privileges to enable `hstore` extension (or you can use [PostgreSQL Extension Whitelisting](https://github.com/dimitri/pgextwlist)).
152
+ ```sh
153
+ bundle exec rails generate logidze:model Post --limit=10
154
+ ```
118
155
 
119
- ## Upgrade from previous versions
156
+ ### Tracking only selected columns
120
157
 
121
- We try to make an upgrade process as simple as possible. For now, the only required action is to create and run a migration:
158
+ You can log only particular columns changes. There are mutually exclusive `except` and `only` options for this:
122
159
 
123
160
  ```sh
124
- rails generate logidze:install --update
161
+ # track all columns, except `created_at` and `active`
162
+ bundle exec rails generate logidze:model Post --except=created_at,active
163
+ # track only `title` and `body` columns
164
+ bundle exec rails generate logidze:model Post --only=title,body
125
165
  ```
126
166
 
127
- This updates core `logdize_logger` DB function. No need to update tables or triggers.
167
+ ### Logs timestamps
168
+
169
+ By default, Logidze tries to get a timestamp for a version from record's `updated_at` field whenever appropriate. If
170
+ your model does not have that column, Logidze will gracefully fall back to `statement_timestamp()`.
171
+
172
+ To change the column name or disable this feature completely, you can use the `timestamp_column` option:
173
+
174
+ ```sh
175
+ # will try to get the timestamp value from `time` column
176
+ bundle exec rails generate logidze:model Post --timestamp_column time
177
+ # will always set version timestamp to `statement_timestamp()`
178
+ bundle exec rails generate logidze:model Post --timestamp_column nil # "null" and "false" will also work
179
+ ```
128
180
 
129
181
  ## Usage
130
182
 
183
+ ### Basic API
184
+
131
185
  Your model now has `log_data` column, which stores changes log.
132
186
 
133
187
  To retrieve record version at a given time use `#at` or `#at!` methods:
@@ -157,6 +211,9 @@ You can also get revision by version number:
157
211
  post.at(version: 2)
158
212
  ```
159
213
 
214
+ **NOTE:** If `log_data` is nil, `#at(time:)` returns self and `#at(version:)` returns `nil`.
215
+ You can opt-in to return `nil` for time-based `#at` as well by setting `Logidze.return_self_if_log_data_is_empty = false`.
216
+
160
217
  It is also possible to get version for relations:
161
218
 
162
219
  ```ruby
@@ -173,6 +230,8 @@ post.diff_from(time: 1.hour.ago)
173
230
  Post.where(created_at: Time.zone.today.all_day).diff_from(time: 1.hour.ago)
174
231
  ```
175
232
 
233
+ **NOTE:** If `log_data` is nil, `#diff_from` returns an empty Hash as `"changes"`.
234
+
176
235
  There are also `#undo!` and `#redo!` options (and more general `#switch_to!`):
177
236
 
178
237
  ```ruby
@@ -210,58 +269,27 @@ Alternatively, you can configure Logidze always to default to `append: true`.
210
269
  Logidze.append_on_undo = true
211
270
  ```
212
271
 
213
- ### How to not load log data by default, or dealing with large logs
214
-
215
- By default, Active Record _selects_ all the table columns when no explicit `select` statement specified.
272
+ ### Track meta information
216
273
 
217
- That could slow down queries execution if you have field values which exceed the size of the data block (typically 8KB). PostgreSQL turns on its [TOAST](https://wiki.postgresql.org/wiki/TOAST) mechanism), which requires reading from multiple physical locations for fetching the row's data.
218
-
219
- If you do not use compaction (`generate logidze:model ... --limit N`) for `log_data`, you're likely to face this problem.
220
-
221
- Logidze provides a way to avoid loading `log_data` by default (and load it on demand):
274
+ You can store any meta information you want inside your version (it could be IP address, user agent, etc.). To add it you should wrap your code with a block:
222
275
 
223
276
  ```ruby
224
- class User < ActiveRecord::Base
225
- # Add `ignore_log_data` option to macros
226
- has_logidze ignore_log_data: true
277
+ Logidze.with_meta(ip: request.ip) do
278
+ post.save!
227
279
  end
228
280
  ```
229
281
 
230
- If you want Logidze to behave this way by default, configure the global option:
231
-
232
- ```ruby
233
- # config/initializers/logidze.rb
234
- Logidze.ignore_log_data_by_default = true
235
-
236
- # or
237
-
238
- # config/application.rb
239
- config.logidze.ignore_log_data_by_default = true
240
- ```
241
-
242
- However, you can override it by explicitly passing `ignore_log_data: false` to the `ignore_log_data`.
243
- You can also enforce loading `log_data` in-place by using the `.with_log_data` scope, e.g. `User.all.with_log_data` loads all
244
- the _users_ with `log_data` included.
245
-
246
- The chart below shows the difference in PG query time before and after turning `ignore_log_data` on. (Special thanks to [@aderyabin](https://github.com/aderyabin) for sharing it.)
247
-
248
- ![](./assets/pg_log_data_chart.png)
249
-
250
- If you try to call `#log_data` on the model loaded in such way, you'll get `nil`. If you want to fetch log data (e.g., during the console debugging)–use **`user.reload_log_data`**, which forces loading this column from the DB.
251
-
252
- ## Track meta information
282
+ Meta expects a hash to be passed so you won't need to encode and decode JSON manually.
253
283
 
254
- You can store any meta information you want inside your version (it could be IP address, user agent, etc.). To add it you should wrap your code with a block:
284
+ By default `.with_meta` wraps the block into a DB transaction. That could lead to an unexpected behavior, especially, when using `.with_meta` within an around_action. To avoid wrapping the block into a DB transaction use `transactional: false` option.
255
285
 
256
286
  ```ruby
257
- Logidze.with_meta(ip: request.ip) do
287
+ Logidze.with_meta({ip: request.ip}, transactional: false) do
258
288
  post.save!
259
289
  end
260
290
  ```
261
291
 
262
- Meta expects a hash to be passed so you won't need to encode and decode JSON manually.
263
-
264
- ## Track responsibility (aka _whodunnit_)
292
+ ### Track responsibility
265
293
 
266
294
  A special application of meta information is storing the author of the change, which is called _Responsible ID_. There is more likely that you would like to store the `current_user.id` that way.
267
295
 
@@ -304,7 +332,15 @@ class ApplicationController < ActionController::Base
304
332
  end
305
333
  ```
306
334
 
307
- ## Disable logging temporary
335
+ By default `.with_responsible` wraps the block into a DB transaction. That could lead to an unexpected behavior, especially, when using `.with_responsible` within an around_action. To avoid wrapping the block into a DB transaction use `transactional: false` option.
336
+
337
+ ```ruby
338
+ Logidze.with_responsible(user.id, transactional: false) do
339
+ post.save!
340
+ end
341
+ ```
342
+
343
+ ### Disable logging temporary
308
344
 
309
345
  If you want to make update without logging (e.g., mass update), you can turn it off the following way:
310
346
 
@@ -316,7 +352,7 @@ Logidze.without_logging { Post.update_all(seen: true) }
316
352
  Post.without_logging { Post.update_all(seen: true) }
317
353
  ```
318
354
 
319
- ## Reset log
355
+ ### Reset log
320
356
 
321
357
  Reset the history for a record (or records):
322
358
 
@@ -328,6 +364,137 @@ record.reset_log_data
328
364
  User.where(active: true).reset_log_data
329
365
  ```
330
366
 
367
+ ### Full snapshots
368
+
369
+ You can instruct Logidze to create a full snapshot instead of a diff for a particular log entry.
370
+
371
+ It could be useful in combination with `.without_logging`: first, you perform multiple updates without logging, then
372
+ you want to create a log entry with the current state. To do that, you should use the `Logidze.with_full_snapshot` method:
373
+
374
+ ```ruby
375
+ record = Model.find(params[:id])
376
+
377
+ Logidze.without_logging do
378
+ # perform multiple write operations with record
379
+ end
380
+
381
+ Logidze.with_full_snapshot do
382
+ record.touch
383
+ end
384
+ ```
385
+
386
+ ### Associations versioning
387
+
388
+ Logidze also supports associations versioning. This feature is disabled by default (due to the number of edge cases). You can learn more
389
+ in the [wiki](https://github.com/palkan/logidze/wiki/Associations-versioning).
390
+
391
+ ## Dealing with large logs
392
+
393
+ By default, Active Record _selects_ all the table columns when no explicit `select` statement specified.
394
+
395
+ That could slow down queries execution if you have field values which exceed the size of the data block (typically 8KB). PostgreSQL turns on its [TOAST](https://wiki.postgresql.org/wiki/TOAST) mechanism), which requires reading from multiple physical locations for fetching the row's data.
396
+
397
+ If you do not use compaction (`generate logidze:model ... --limit N`) for `log_data`, you're likely to face this problem.
398
+
399
+ Logidze provides a way to avoid loading `log_data` by default (and load it on demand):
400
+
401
+ ```ruby
402
+ class User < ActiveRecord::Base
403
+ # Add `ignore_log_data` option to macros
404
+ has_logidze ignore_log_data: true
405
+ end
406
+ ```
407
+
408
+ If you want Logidze to behave this way by default, configure the global option:
409
+
410
+ ```ruby
411
+ # config/initializers/logidze.rb
412
+ Logidze.ignore_log_data_by_default = true
413
+
414
+ # or
415
+
416
+ # config/application.rb
417
+ config.logidze.ignore_log_data_by_default = true
418
+ ```
419
+
420
+ However, you can override it by explicitly passing `ignore_log_data: false` to the `ignore_log_data`.
421
+ You can also enforce loading `log_data` in-place by using the `.with_log_data` scope, e.g. `User.all.with_log_data` loads all
422
+ the _users_ with `log_data` included.
423
+
424
+ The chart below shows the difference in PG query time before and after turning `ignore_log_data` on. (Special thanks to [@aderyabin](https://github.com/aderyabin) for sharing it.)
425
+
426
+ ![](./assets/pg_log_data_chart.png)
427
+
428
+ If you try to call `#log_data` on the model loaded in such way, you'll get `nil`. If you want to fetch log data (e.g., during the console debugging)–use **`user.reload_log_data`**, which forces loading this column from the DB.
429
+
430
+ ## Handling records deletion
431
+
432
+ Unlike, for example, PaperTrail, Logidze is designed to **only track changes**. If the record has been deleted, **everything is lost**.
433
+
434
+ If you want to keep changes history after records deletion as well, consider using specialized tools for soft-delete, such as, [Discard](https://github.com/jhawthorn/discard) or [Paranoia](https://github.com/rubysherpas/paranoia).
435
+
436
+ See also the discussion: [#61](https://github.com/palkan/logidze/issues/61).
437
+
438
+ ## Handling PG exceptions
439
+
440
+ By default, Logidze raises an exception which causes the entire transaction to fail.
441
+ To change this behavior, it's now possible to override `logidze_capture_exception(error_data jsonb)` function.
442
+
443
+ For example, you may want to raise a warning instead of an exception and complete the transaction without updating log_data.
444
+
445
+ Related issues: [#193](https://github.com/palkan/logidze/issues/193)
446
+
447
+ ## Upgrading
448
+
449
+ We try to make an upgrade process as simple as possible. For now, the only required action is to create and run a migration:
450
+
451
+ ```sh
452
+ bundle exec rails generate logidze:install --update
453
+ ```
454
+
455
+ This updates core `logdize_logger` DB function. No need to update tables or triggers.
456
+
457
+ **NOTE:** When using `fx`, you can omit the `--update` flag. The migration containing only the updated functions would be created.
458
+
459
+ If you want to update Logidze settings for the model, run migration with `--update` flag:
460
+
461
+ ```sh
462
+ bundle exec rails generate logidze:model Post --update --only=title,body,rating
463
+ ```
464
+
465
+ You can also use the `--name` option to specify the migration name to avoid duplicate migration names:
466
+
467
+ ```sh
468
+ $ bundle exec rails generate logidze:model Post --update --only=title,body,rating --name add_only_filter_to_posts_log_data
469
+
470
+ create db/migrate/20202309142344_add_only_filter_to_posts_log_data.rb
471
+ ```
472
+
473
+ ### Pending upgrade check [Experimental]
474
+
475
+ Logidze can check for a pending upgrade. Use `Logidze.pending_upgrade = :warn` to be notified by warning, or `Logidze.pending_upgrade = :error` if you want Logidze to raise an error.
476
+
477
+ ### Upgrading from 0.x to 1.0 (edge)
478
+
479
+ #### Schema and migrations
480
+
481
+ Most SQL function definitions have changed without backward compatibility.
482
+ Perform the following steps to upgrade:
483
+
484
+ 1. Re-install Logidze: `bundle exec rails generate logidze:install --update`.
485
+
486
+ 1. Re-install Logidze triggers **for all models**: `bundle exec rails generate logidze:model <model> --update`.
487
+
488
+ **NOTE:** If you had previously specified whitelist/blacklist attributes, you will need to include the `--only`/`--except` [option](#tracking-only-selected-columns) as appropriate. You can easily copy these column lists from the previous logidze migration for the model.
489
+
490
+ 1. Remove the `include Logidze::Migration` line from the old migration files (if any)—this module has been removed.
491
+
492
+ Rewrite legacy logidze migrations to not use the `#current_setting(name)` and `#current_setting_missing_supported?` methods, or copy them from the latest [0.x release](https://github.com/palkan/logidze/blob/0-stable/lib/logidze/migration.rb).
493
+
494
+ #### API changes
495
+
496
+ The deprecated `time` positional argument has been removed from `#at` and `#diff_from` methods. Now you need to use keyword arguments, i.e., `model.at(some_tome) -> model.at(time: some_time)`.
497
+
331
498
  ## Log format
332
499
 
333
500
  The `log_data` column has the following format:
@@ -356,19 +523,55 @@ The `log_data` column has the following format:
356
523
 
357
524
  If you specify the limit in the trigger definition, then log size will not exceed the specified size. When a new change occurs, and there is no more room for it, the two oldest changes will be merged.
358
525
 
359
- ## Development
526
+ ## Troubleshooting
360
527
 
361
- For development setup run `./bin/setup`. This runs `bundle install` and creates test DB.
528
+ ### `log_data` is nil when using Rails fixtures
362
529
 
363
- ## Contributing
530
+ Rails fixtures are populated with triggers disabled. Thus, `log_data` is null initially for all records.
531
+ You can use `#create_logidze_snapshot` manually to build initial snapshots.
532
+
533
+ ### How to make this work with Apartment 🤔
534
+
535
+ First, read [Apartment docs](https://github.com/influitive/apartment#installing-extensions-into-persistent-schemas) on installing PostgreSQL extensions. You need to use the described approach to install Hstore (and drop the migration provided by Logidze during installation).
536
+
537
+ Secondly, set `config.use_sql = true` in the Apartment configuration.
538
+
539
+ Finally, when using `fx` along with `schema.rb`, you might face a problem with duplicate trigger definitions (for different schemas).
540
+ Here is a patch to fix this: [dump_triggers.rake](etc/dump_triggers.rake).
541
+
542
+ Related issues: [#50](https://github.com/palkan/logidze/issues/50).
543
+
544
+ ### `PG::UntranslatableCharacter: ERROR`
364
545
 
365
- Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/logidze.
546
+ That could happen when your row data contain null bytes. You should sanitize the data before writing to the database.
547
+ From the [PostgreSQL docs](https://www.postgresql.org/docs/current/datatype-json.html): `jsonb type also rejects \u0000 (because that cannot be represented in PostgreSQL's text type)`.
366
548
 
367
- ## Future ideas
549
+ Related issues: [#155](https://github.com/palkan/logidze/issues/155).
368
550
 
369
- - Enhance update_all to support mass-logging.
370
- - Other DB adapters.
551
+ ### `pg_restore` fails to restore a dump
552
+
553
+ First, when restoring data dumps you should consider using `--disable-triggers` option (unless you have a strong reason to invoke the triggers).
554
+
555
+ When restoring data dumps for a particular PostgreSQL schema (e.g., when using Apartment), you may encounter the issue with non-existent Logidze functions. That happens because `pg_dump` adds `SELECT pg_catalog.set_config('search_path', '', false);`, and, thus, breaks our existing triggers/functions, because they live either in "public" or in a tenant's namespace (see [this thread](https://postgrespro.com/list/thread-id/2448092)).
556
+
557
+ ### `PG::NumericValueOutOfRange: ERROR: value overflows numeric format`
558
+
559
+ Due to the usage of `hstore_to_jsonb_loose` under the hood, there could be a situation when you have a string representing a number in the scientific notation (e.g., "557236406134e62000323100"). Postgres would try to convert it to a number (a pretty big one, for sure) and fail with the exception.
560
+
561
+ Related issues: [#69](https://github.com/palkan/logidze/issues/69).
562
+
563
+ ## Development
564
+
565
+ We use [Dip](https://github.com/bibendi/dip) for development. Provision the project by running `dip provision` and then use `dip bundle`, `dip rspec` or `dip bash` to interact with a Docker development environment.
566
+
567
+ If you prefer developing on your local machine, make user you have Postgres installed and run `./bin/setup`.
568
+
569
+ ## Contributing
570
+
571
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/logidze](https://github.com/palkan/logidze).
371
572
 
372
573
  ## License
373
574
 
374
575
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
576
+
577
+ [fx]: https://github.com/teoljungberg/fx