logidze 1.2.3 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +40 -3
- data/lib/generators/logidze/fx_helper.rb +1 -1
- data/lib/generators/logidze/install/functions/logidze_logger.sql +47 -47
- data/lib/generators/logidze/install/functions/logidze_logger_after.sql +3 -0
- data/lib/generators/logidze/install/install_generator.rb +12 -3
- data/lib/generators/logidze/model/model_generator.rb +14 -8
- data/lib/generators/logidze/model/templates/migration.rb.erb +1 -1
- data/lib/generators/logidze/model/triggers/logidze_after.sql +6 -0
- data/lib/logidze/ignore_log_data.rb +0 -5
- data/lib/logidze/model.rb +16 -3
- data/lib/logidze/utils/check_pending.rb +6 -6
- data/lib/logidze/utils/pending_migration_error.rb +10 -12
- data/lib/logidze/version.rb +1 -1
- data/lib/logidze.rb +0 -1
- metadata +14 -27
- data/lib/logidze/ignore_log_data/cast_attribute_patch.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4ab83a61c2685b93d8fe58199eac1fb889c4459453db9315bd6a9880abb2bc9
|
4
|
+
data.tar.gz: f856bd257d81c6ad1fc3cbe6328bbd1adbf77670335a07a0f5234f572f8a7145
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d462f59ba20d402c5c75bb15ea40b709f278088dcd11be7e769640640450bd35a558794884fca89ccdaa403e09fb0d05c40d43bed8dd86698a41ecb870272a4b
|
7
|
+
data.tar.gz: f5bf80de51ffaff0f611e84bbf99f4291ebb82655ff23ce555d042c3ae29f3d79c172e1b7de7c1f19083892a847473ce6252364973e31eecb41d25fe8e1de65b
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,21 @@
|
|
2
2
|
|
3
3
|
## master (unreleased)
|
4
4
|
|
5
|
+
## 1.3.0 (2024-01-09)
|
6
|
+
|
7
|
+
- Add retrieving list of versions support. ([@tagirahmad][])
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
post.versions # => Enumerator
|
11
|
+
post.versions.find do
|
12
|
+
_1.title == "old title"
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
- Add `--after-trigger` option to generate _after_ triggers for partitioned tables in older PostgreSQL versions. ([@SparLaimor][], [@prog-supdex][], [@palkan][])
|
17
|
+
|
18
|
+
- **Breaking**. Ruby 2.7, Rails 6.0, PostgreSQL 10.0+ are required.
|
19
|
+
|
5
20
|
## 1.2.3 (2023-01-03)
|
6
21
|
|
7
22
|
- [Fixes [#217](https://github.com/palkan/logidze/issues/217)] Fix switch_to with `append: true` when there are changes on JSONB columns. ([@miharekar][])
|
@@ -376,3 +391,7 @@ This is a quick fix for a more general problem (see [#59](https://github.com/pal
|
|
376
391
|
[@cavi21]: https://github.com/cavi21
|
377
392
|
[@danielmklein]: https://github.com/danielmklein
|
378
393
|
[@baygeldin]: https://github.com/baygeldin
|
394
|
+
[@miharekar]: https://github.com/miharekar
|
395
|
+
[@prog-supdex]: https://github.com/prog-supdex
|
396
|
+
[@SparLaimor]: https://github.com/SparLaimor
|
397
|
+
[@tagirahmad]: https://github.com/tagirahmad
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
# Logidze
|
7
7
|
|
8
|
-
Logidze provides tools for logging DB records changes when using PostgreSQL
|
8
|
+
Logidze provides tools for logging DB records changes when using PostgreSQL. Just like [audited](https://github.com/collectiveidea/audited) and [paper_trail](https://github.com/airblade/paper_trail) do (but [faster](bench/performance)).
|
9
9
|
|
10
10
|
Logidze allows you to create a DB-level log (using triggers) and gives you an API to browse this log.
|
11
11
|
The log is stored with the record itself in JSONB column. No additional tables required.
|
@@ -14,8 +14,9 @@ The log is stored with the record itself in JSONB column. No additional tables r
|
|
14
14
|
|
15
15
|
Other requirements:
|
16
16
|
|
17
|
-
- Ruby ~> 2.
|
18
|
-
- Rails >=
|
17
|
+
- Ruby ~> 2.7
|
18
|
+
- Rails >= 6.0 (for Rails 4.2 use version <=0.12.0, for Rails 5.x use version <= 1.2.3)
|
19
|
+
- PostgreSQL >= 10.0
|
19
20
|
|
20
21
|
<a href="https://evilmartians.com/">
|
21
22
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
@@ -35,6 +36,7 @@ Other requirements:
|
|
35
36
|
- [Tracking only selected columns](#tracking-only-selected-columns)
|
36
37
|
- [Logs timestamps](#logs-timestamps)
|
37
38
|
- [Undoing a Generated Invocation](#undoing-a-generated-invocation)
|
39
|
+
- [Using with partitioned tables](#using-with-partitioned-tables)
|
38
40
|
- [Usage](#usage)
|
39
41
|
- [Basic API](#basic-api)
|
40
42
|
- [Track meta information](#track-meta-information)
|
@@ -189,6 +191,18 @@ bundle exec rails destroy logidze:model Post
|
|
189
191
|
|
190
192
|
**IMPORTANT**: If you use non-UTC time zone for Active Record (`config.active_record.default_timezone`), you MUST always infer log timestamps from a timestamp column (e.g., when back-filling data); otherwise, you may end up with inconsistent logs ([#199](https://github.com/palkan/logidze/issues/199)). In general, we recommend using UTC as the database time unless there is a very strong reason not to.
|
191
193
|
|
194
|
+
### Using with partitioned tables
|
195
|
+
|
196
|
+
Logidze supports partitioned tables for PostgreSQL 13+ without any additional configuration. For PostgreSQL 11/12, you should use _after_ triggers. To do that, provide the `--after-trigger` option to the migration:
|
197
|
+
|
198
|
+
```sh
|
199
|
+
bundle exec rails generate logidze:model Post --after-trigger
|
200
|
+
```
|
201
|
+
|
202
|
+
**NOTE:** Record changes are written as a full snapshot if the partition has changed during the update.
|
203
|
+
|
204
|
+
**IMPORTANT:** Using Logidze for partitioned tables in PostgreSQL 10 is not supported.
|
205
|
+
|
192
206
|
## Usage
|
193
207
|
|
194
208
|
### Basic API
|
@@ -243,6 +257,25 @@ Post.where(created_at: Time.zone.today.all_day).diff_from(time: 1.hour.ago)
|
|
243
257
|
|
244
258
|
**NOTE:** If `log_data` is nil, `#diff_from` returns an empty Hash as `"changes"`.
|
245
259
|
|
260
|
+
Also, it is possible to retrieve list of model's `versions`:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
post.versions # => Enumerator
|
264
|
+
|
265
|
+
# you can use Enumerator's #take to return all
|
266
|
+
post.versions.take
|
267
|
+
|
268
|
+
# or you take a few or call any Enumerable method
|
269
|
+
post.versions.take(2)
|
270
|
+
post.versions.find do
|
271
|
+
_1.title == "old title"
|
272
|
+
end
|
273
|
+
|
274
|
+
# we can also add options
|
275
|
+
post.versions(reverse: true) # from older to newer
|
276
|
+
post.versions(include_self: true) # returns self as the first one (default) or the last one record (if reverse: true)
|
277
|
+
```
|
278
|
+
|
246
279
|
There are also `#undo!` and `#redo!` options (and more general `#switch_to!`):
|
247
280
|
|
248
281
|
```ruby
|
@@ -302,6 +335,10 @@ Logidze.with_meta({ip: request.ip}, transactional: false) do
|
|
302
335
|
end
|
303
336
|
```
|
304
337
|
|
338
|
+
**Important:** If you use connection pooling (e.g., PgBouncer), using `.with_meta` without a transaction may lead to unexpected results (since meta is set for a connection). Without a transaction, we cannot guarantee that the same connection will be used for queries (including metadata cleanup).
|
339
|
+
|
340
|
+
**Important**: In Rails, `after_commit` callbacks are executed after transaction is committed, and, thus, after `with_meta` block is executed—the meta wouldn't be added to changes captured in the `after_commit` phase. One particular scenario is having associations with `touch: true` (_touch_ updates are executed after commit).
|
341
|
+
|
305
342
|
### Track responsibility
|
306
343
|
|
307
344
|
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.
|
@@ -1,9 +1,10 @@
|
|
1
1
|
CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
|
2
|
-
-- version:
|
2
|
+
-- version: 4
|
3
3
|
DECLARE
|
4
4
|
changes jsonb;
|
5
5
|
version jsonb;
|
6
|
-
|
6
|
+
full_snapshot boolean;
|
7
|
+
log_data jsonb;
|
7
8
|
new_v integer;
|
8
9
|
size integer;
|
9
10
|
history_limit integer;
|
@@ -30,59 +31,56 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
|
|
30
31
|
columns := NULLIF(TG_ARGV[2], 'null');
|
31
32
|
include_columns := NULLIF(TG_ARGV[3], 'null');
|
32
33
|
|
33
|
-
IF
|
34
|
+
IF NEW.log_data is NULL OR NEW.log_data = '{}'::jsonb
|
35
|
+
THEN
|
34
36
|
IF columns IS NOT NULL THEN
|
35
|
-
|
37
|
+
log_data = logidze_snapshot(to_jsonb(NEW.*), ts_column, columns, include_columns);
|
36
38
|
ELSE
|
37
|
-
|
39
|
+
log_data = logidze_snapshot(to_jsonb(NEW.*), ts_column);
|
38
40
|
END IF;
|
39
41
|
|
40
|
-
IF
|
41
|
-
NEW.log_data :=
|
42
|
+
IF log_data#>>'{h, -1, c}' != '{}' THEN
|
43
|
+
NEW.log_data := log_data;
|
42
44
|
END IF;
|
43
45
|
|
44
|
-
|
46
|
+
ELSE
|
45
47
|
|
46
|
-
IF
|
47
|
-
|
48
|
-
snapshot = logidze_snapshot(to_jsonb(NEW.*), ts_column, columns, include_columns);
|
49
|
-
ELSE
|
50
|
-
snapshot = logidze_snapshot(to_jsonb(NEW.*), ts_column);
|
51
|
-
END IF;
|
52
|
-
|
53
|
-
IF snapshot#>>'{h, -1, c}' != '{}' THEN
|
54
|
-
NEW.log_data := snapshot;
|
55
|
-
END IF;
|
56
|
-
RETURN NEW;
|
48
|
+
IF TG_OP = 'UPDATE' AND (to_jsonb(NEW.*) = to_jsonb(OLD.*)) THEN
|
49
|
+
RETURN NEW; -- pass
|
57
50
|
END IF;
|
58
51
|
|
59
52
|
history_limit := NULLIF(TG_ARGV[0], 'null');
|
60
53
|
debounce_time := NULLIF(TG_ARGV[4], 'null');
|
61
54
|
|
62
|
-
|
55
|
+
log_data := NEW.log_data;
|
56
|
+
|
57
|
+
current_version := (log_data->>'v')::int;
|
63
58
|
|
64
59
|
IF ts_column IS NULL THEN
|
65
60
|
ts := statement_timestamp();
|
66
|
-
|
67
|
-
ts := (to_jsonb(NEW.*)->>ts_column)::timestamp with time zone;
|
68
|
-
IF ts IS NULL OR ts = (to_jsonb(OLD.*)->>ts_column)::timestamp with time zone THEN
|
61
|
+
ELSEIF TG_OP = 'UPDATE' THEN
|
62
|
+
ts := (to_jsonb(NEW.*) ->> ts_column)::timestamp with time zone;
|
63
|
+
IF ts IS NULL OR ts = (to_jsonb(OLD.*) ->> ts_column)::timestamp with time zone THEN
|
64
|
+
ts := statement_timestamp();
|
65
|
+
END IF;
|
66
|
+
ELSEIF TG_OP = 'INSERT' THEN
|
67
|
+
ts := (to_jsonb(NEW.*) ->> ts_column)::timestamp with time zone;
|
68
|
+
IF ts IS NULL OR (extract(epoch from ts) * 1000)::bigint = (NEW.log_data #>> '{h,-1,ts}')::bigint THEN
|
69
69
|
ts := statement_timestamp();
|
70
70
|
END IF;
|
71
71
|
END IF;
|
72
72
|
|
73
|
-
|
74
|
-
RETURN NEW;
|
75
|
-
END IF;
|
73
|
+
full_snapshot := (coalesce(current_setting('logidze.full_snapshot', true), '') = 'on') OR (TG_OP = 'INSERT');
|
76
74
|
|
77
|
-
IF current_version < (
|
75
|
+
IF current_version < (log_data#>>'{h,-1,v}')::int THEN
|
78
76
|
iterator := 0;
|
79
|
-
FOR item in SELECT * FROM jsonb_array_elements(
|
77
|
+
FOR item in SELECT * FROM jsonb_array_elements(log_data->'h')
|
80
78
|
LOOP
|
81
79
|
IF (item.value->>'v')::int > current_version THEN
|
82
|
-
|
83
|
-
|
80
|
+
log_data := jsonb_set(
|
81
|
+
log_data,
|
84
82
|
'{h}',
|
85
|
-
(
|
83
|
+
(log_data->'h') - iterator
|
86
84
|
);
|
87
85
|
END IF;
|
88
86
|
iterator := iterator + 1;
|
@@ -91,7 +89,7 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
|
|
91
89
|
|
92
90
|
changes := '{}';
|
93
91
|
|
94
|
-
IF
|
92
|
+
IF full_snapshot THEN
|
95
93
|
BEGIN
|
96
94
|
changes = hstore_to_jsonb_loose(hstore(NEW.*));
|
97
95
|
EXCEPTION
|
@@ -132,48 +130,50 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
|
|
132
130
|
END IF;
|
133
131
|
|
134
132
|
IF changes = '{}' THEN
|
135
|
-
RETURN NEW;
|
133
|
+
RETURN NEW; -- pass
|
136
134
|
END IF;
|
137
135
|
|
138
|
-
new_v := (
|
136
|
+
new_v := (log_data#>>'{h,-1,v}')::int + 1;
|
139
137
|
|
140
|
-
size := jsonb_array_length(
|
138
|
+
size := jsonb_array_length(log_data->'h');
|
141
139
|
version := logidze_version(new_v, changes, ts);
|
142
140
|
|
143
141
|
IF (
|
144
142
|
debounce_time IS NOT NULL AND
|
145
|
-
(version->>'ts')::bigint - (
|
143
|
+
(version->>'ts')::bigint - (log_data#>'{h,-1,ts}')::text::bigint <= debounce_time
|
146
144
|
) THEN
|
147
145
|
-- merge new version with the previous one
|
148
|
-
new_v := (
|
149
|
-
version := logidze_version(new_v, (
|
146
|
+
new_v := (log_data#>>'{h,-1,v}')::int;
|
147
|
+
version := logidze_version(new_v, (log_data#>'{h,-1,c}')::jsonb || changes, ts);
|
150
148
|
-- remove the previous version from log
|
151
|
-
|
152
|
-
|
149
|
+
log_data := jsonb_set(
|
150
|
+
log_data,
|
153
151
|
'{h}',
|
154
|
-
(
|
152
|
+
(log_data->'h') - (size - 1)
|
155
153
|
);
|
156
154
|
END IF;
|
157
155
|
|
158
|
-
|
159
|
-
|
156
|
+
log_data := jsonb_set(
|
157
|
+
log_data,
|
160
158
|
ARRAY['h', size::text],
|
161
159
|
version,
|
162
160
|
true
|
163
161
|
);
|
164
162
|
|
165
|
-
|
166
|
-
|
163
|
+
log_data := jsonb_set(
|
164
|
+
log_data,
|
167
165
|
'{v}',
|
168
166
|
to_jsonb(new_v)
|
169
167
|
);
|
170
168
|
|
171
169
|
IF history_limit IS NOT NULL AND history_limit <= size THEN
|
172
|
-
|
170
|
+
log_data := logidze_compact_history(log_data, size - history_limit + 1);
|
173
171
|
END IF;
|
172
|
+
|
173
|
+
NEW.log_data := log_data;
|
174
174
|
END IF;
|
175
175
|
|
176
|
-
|
176
|
+
RETURN NEW; -- result
|
177
177
|
EXCEPTION
|
178
178
|
WHEN OTHERS THEN
|
179
179
|
GET STACKED DIAGNOSTICS err_sqlstate = RETURNED_SQLSTATE,
|
@@ -6,8 +6,6 @@ require "logidze/utils/function_definitions"
|
|
6
6
|
require_relative "../inject_sql"
|
7
7
|
require_relative "../fx_helper"
|
8
8
|
|
9
|
-
using RubyNext
|
10
|
-
|
11
9
|
module Logidze
|
12
10
|
module Generators
|
13
11
|
class InstallGenerator < ::Rails::Generators::Base # :nodoc:
|
@@ -19,7 +17,7 @@ module Logidze
|
|
19
17
|
source_paths << File.expand_path("functions", __dir__)
|
20
18
|
|
21
19
|
class_option :update, type: :boolean, optional: true,
|
22
|
-
|
20
|
+
desc: "Define whether this is an update migration"
|
23
21
|
|
24
22
|
def generate_migration
|
25
23
|
migration_template = fx? ? "migration_fx.rb.erb" : "migration.rb.erb"
|
@@ -81,6 +79,17 @@ module Logidze
|
|
81
79
|
def function_definitions
|
82
80
|
@function_definitions ||= Logidze::Utils::FunctionDefinitions.from_fs
|
83
81
|
end
|
82
|
+
|
83
|
+
# Generate `logidze_logger_after.sql` from the regular `logidze_logger.sql`
|
84
|
+
# by find-and-replacing a few lines
|
85
|
+
def generate_logidze_logger_after
|
86
|
+
source = File.read(File.join(__dir__, "functions", "logidze_logger.sql"))
|
87
|
+
source.sub!(/^CREATE OR REPLACE FUNCTION logidze_logger.*$/, "")
|
88
|
+
source.sub!(/^ -- version.*$/, "")
|
89
|
+
source.gsub!("RETURN NEW; -- pass", "RETURN NULL;")
|
90
|
+
source.gsub!("RETURN NEW; -- result", " EXECUTE format('UPDATE %I.%I SET \"log_data\" = $1 WHERE ctid = %L', TG_TABLE_SCHEMA, TG_TABLE_NAME, NEW.CTID) USING NEW.log_data;\n RETURN NULL;")
|
91
|
+
source
|
92
|
+
end
|
84
93
|
end
|
85
94
|
|
86
95
|
def self.next_migration_number(dir)
|
@@ -5,8 +5,6 @@ require "rails/generators/active_record/migration/migration_generator"
|
|
5
5
|
require_relative "../inject_sql"
|
6
6
|
require_relative "../fx_helper"
|
7
7
|
|
8
|
-
using RubyNext
|
9
|
-
|
10
8
|
module Logidze
|
11
9
|
module Generators
|
12
10
|
class ModelGenerator < ::ActiveRecord::Generators::Base # :nodoc:
|
@@ -19,13 +17,13 @@ module Logidze
|
|
19
17
|
class_option :limit, type: :numeric, optional: true, desc: "Specify history size limit"
|
20
18
|
|
21
19
|
class_option :debounce_time, type: :numeric, optional: true,
|
22
|
-
|
20
|
+
desc: "Specify debounce time in millisecond"
|
23
21
|
|
24
22
|
class_option :backfill, type: :boolean, optional: true,
|
25
|
-
|
23
|
+
desc: "Add query to backfill existing records history"
|
26
24
|
|
27
25
|
class_option :only_trigger, type: :boolean, optional: true,
|
28
|
-
|
26
|
+
desc: "Create trigger-only migration"
|
29
27
|
|
30
28
|
class_option :path, type: :string, optional: true, desc: "Specify path to the model file"
|
31
29
|
|
@@ -33,12 +31,14 @@ module Logidze
|
|
33
31
|
class_option :only, type: :array, optional: true
|
34
32
|
|
35
33
|
class_option :timestamp_column, type: :string, optional: true,
|
36
|
-
|
34
|
+
desc: "Specify timestamp column"
|
37
35
|
|
38
36
|
class_option :name, type: :string, optional: true, desc: "Migration name"
|
39
37
|
|
40
38
|
class_option :update, type: :boolean, optional: true,
|
41
|
-
|
39
|
+
desc: "Define whether this is an update migration"
|
40
|
+
|
41
|
+
class_option :after_trigger, type: :boolean, optional: true, desc: "Use after trigger"
|
42
42
|
|
43
43
|
def generate_migration
|
44
44
|
if options[:except] && options[:only]
|
@@ -51,7 +51,9 @@ module Logidze
|
|
51
51
|
def generate_fx_trigger
|
52
52
|
return unless fx?
|
53
53
|
|
54
|
-
|
54
|
+
template_name = after_trigger? ? "logidze_after.sql" : "logidze.sql"
|
55
|
+
|
56
|
+
template template_name, "db/triggers/logidze_on_#{table_name}_v#{next_version.to_s.rjust(2, "0")}.sql"
|
55
57
|
end
|
56
58
|
|
57
59
|
def inject_logidze_to_model
|
@@ -94,6 +96,10 @@ module Logidze
|
|
94
96
|
options[:update]
|
95
97
|
end
|
96
98
|
|
99
|
+
def after_trigger?
|
100
|
+
options[:after_trigger]
|
101
|
+
end
|
102
|
+
|
97
103
|
def filtered_columns
|
98
104
|
format_pgsql_array(options[:only] || options[:except])
|
99
105
|
end
|
@@ -37,7 +37,7 @@ class <%= @migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::M
|
|
37
37
|
|
38
38
|
<%- end -%>
|
39
39
|
execute <<~SQL
|
40
|
-
<%= inject_sql("logidze.sql", indent: 10) %>
|
40
|
+
<%= inject_sql(after_trigger? ? "logidze_after.sql" : "logidze.sql", indent: 10) %>
|
41
41
|
SQL
|
42
42
|
end
|
43
43
|
|
@@ -0,0 +1,6 @@
|
|
1
|
+
CREATE TRIGGER <%= %Q("logidze_on_#{full_table_name}") %>
|
2
|
+
AFTER UPDATE OR INSERT ON <%= %Q("#{full_table_name}") %> FOR EACH ROW
|
3
|
+
WHEN (coalesce(current_setting('logidze.disabled', true), '') <> 'on' AND pg_trigger_depth() < 1)
|
4
|
+
-- Parameters: history_size_limit (integer), timestamp_column (text), filtered_columns (text[]),
|
5
|
+
-- include_columns (boolean), debounce_time_ms (integer)
|
6
|
+
EXECUTE PROCEDURE logidze_logger_after(<%= logidze_logger_parameters %>);
|
@@ -5,11 +5,6 @@ module Logidze
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
if Rails::VERSION::MAJOR == 5
|
9
|
-
require "logidze/ignore_log_data/cast_attribute_patch"
|
10
|
-
include CastAttributePatch
|
11
|
-
end
|
12
|
-
|
13
8
|
scope :with_log_data, lambda {
|
14
9
|
if ignored_columns == ["log_data"]
|
15
10
|
select(arel_table[Arel.star])
|
data/lib/logidze/model.rb
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
require "active_support"
|
4
4
|
|
5
5
|
module Logidze
|
6
|
-
using RubyNext
|
7
|
-
|
8
6
|
# Extends model with methods to browse history
|
9
7
|
module Model
|
10
8
|
require "logidze/history/type"
|
@@ -96,6 +94,20 @@ module Logidze
|
|
96
94
|
|
97
95
|
build_dup(log_entry, time)
|
98
96
|
end
|
97
|
+
|
98
|
+
def logidze_versions(reverse: false, include_self: false)
|
99
|
+
versions_meta = log_data.versions.dup
|
100
|
+
|
101
|
+
if reverse
|
102
|
+
versions_meta.reverse!
|
103
|
+
versions_meta.shift unless include_self
|
104
|
+
else
|
105
|
+
versions_meta.pop unless include_self
|
106
|
+
end
|
107
|
+
|
108
|
+
Enumerator.new { |yielder| versions_meta.each { yielder << at(version: _1.version) } }
|
109
|
+
end
|
110
|
+
|
99
111
|
# rubocop: enable Metrics/MethodLength
|
100
112
|
|
101
113
|
# Revert record to the version at specified time (without saving to DB)
|
@@ -116,6 +128,7 @@ module Logidze
|
|
116
128
|
|
117
129
|
# Return a dirty copy of specified version of record
|
118
130
|
def at_version(version)
|
131
|
+
return nil unless log_data
|
119
132
|
return self if log_data.version == version
|
120
133
|
|
121
134
|
log_entry = log_data.find_by_version(version)
|
@@ -217,7 +230,7 @@ module Logidze
|
|
217
230
|
|
218
231
|
# Loads log_data field from the database, stores to the attributes hash and returns it
|
219
232
|
def reload_log_data
|
220
|
-
self.log_data = self.class.where(self.class.primary_key => id).pluck("#{self.class.table_name}.log_data"
|
233
|
+
self.log_data = self.class.where(self.class.primary_key => id).pluck(:"#{self.class.table_name}.log_data").first
|
221
234
|
end
|
222
235
|
|
223
236
|
# Nullify log_data column for a single record
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
3
|
+
require_relative "function_definitions"
|
4
|
+
require_relative "pending_migration_error"
|
5
5
|
|
6
6
|
module Logidze
|
7
7
|
module Utils
|
@@ -31,10 +31,10 @@ module Logidze
|
|
31
31
|
def notify_or_raise!
|
32
32
|
case Logidze.on_pending_upgrade
|
33
33
|
when :warn
|
34
|
-
warn "\n**************************************************\n"\
|
35
|
-
"⛔️ WARNING: Logidze needs an upgrade and might not work correctly.\n"\
|
36
|
-
"Please, make sure to run `bundle exec rails generate logidze:install --update` "\
|
37
|
-
"and apply generated migration."\
|
34
|
+
warn "\n**************************************************\n" \
|
35
|
+
"⛔️ WARNING: Logidze needs an upgrade and might not work correctly.\n" \
|
36
|
+
"Please, make sure to run `bundle exec rails generate logidze:install --update` " \
|
37
|
+
"and apply generated migration." \
|
38
38
|
"\n**************************************************\n\n"
|
39
39
|
when :raise
|
40
40
|
raise Logidze::Utils::PendingMigrationError, "Logidze needs upgrade. Run `bundle exec rails generate logidze:install --update` and apply generated migration."
|
@@ -5,19 +5,17 @@ require "rails/generators"
|
|
5
5
|
module Logidze
|
6
6
|
module Utils
|
7
7
|
class PendingMigrationError < StandardError
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
include ActiveSupport::ActionableError
|
8
|
+
require "active_record"
|
9
|
+
require "active_support/actionable_error"
|
10
|
+
include ActiveSupport::ActionableError
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
12
|
+
action "Upgrade Logidze" do
|
13
|
+
Rails::Generators.invoke("logidze:install", ["--update"])
|
14
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate
|
15
|
+
if ActiveRecord::Base.dump_schema_after_migration
|
16
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(
|
17
|
+
ActiveRecord::Base.connection_db_config
|
18
|
+
)
|
21
19
|
end
|
22
20
|
end
|
23
21
|
end
|
data/lib/logidze/version.rb
CHANGED
data/lib/logidze.rb
CHANGED
@@ -5,7 +5,6 @@ require "logidze/version"
|
|
5
5
|
# Logidze provides tools for adding in-table JSON-based audit to DB tables
|
6
6
|
# and ActiveRecord extensions to work with changes history.
|
7
7
|
module Logidze
|
8
|
-
require "ruby-next"
|
9
8
|
require "logidze/history"
|
10
9
|
require "logidze/model"
|
11
10
|
require "logidze/versioned_association"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logidze
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -16,42 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '6.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '6.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '6.0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: ruby-next-core
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0.9'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0.9'
|
40
|
+
version: '6.0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: ammeter
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,14 +86,14 @@ dependencies:
|
|
100
86
|
requirements:
|
101
87
|
- - ">="
|
102
88
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0
|
89
|
+
version: '1.0'
|
104
90
|
type: :development
|
105
91
|
prerelease: false
|
106
92
|
version_requirements: !ruby/object:Gem::Requirement
|
107
93
|
requirements:
|
108
94
|
- - ">="
|
109
95
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0
|
96
|
+
version: '1.0'
|
111
97
|
- !ruby/object:Gem::Dependency
|
112
98
|
name: rake
|
113
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,14 +114,14 @@ dependencies:
|
|
128
114
|
requirements:
|
129
115
|
- - ">="
|
130
116
|
- !ruby/object:Gem::Version
|
131
|
-
version: '
|
117
|
+
version: '4.0'
|
132
118
|
type: :development
|
133
119
|
prerelease: false
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
135
121
|
requirements:
|
136
122
|
- - ">="
|
137
123
|
- !ruby/object:Gem::Version
|
138
|
-
version: '
|
124
|
+
version: '4.0'
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
126
|
name: timecop
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -167,6 +153,7 @@ files:
|
|
167
153
|
- lib/generators/logidze/install/functions/logidze_compact_history.sql
|
168
154
|
- lib/generators/logidze/install/functions/logidze_filter_keys.sql
|
169
155
|
- lib/generators/logidze/install/functions/logidze_logger.sql
|
156
|
+
- lib/generators/logidze/install/functions/logidze_logger_after.sql
|
170
157
|
- lib/generators/logidze/install/functions/logidze_snapshot.sql
|
171
158
|
- lib/generators/logidze/install/functions/logidze_version.sql
|
172
159
|
- lib/generators/logidze/install/install_generator.rb
|
@@ -177,6 +164,7 @@ files:
|
|
177
164
|
- lib/generators/logidze/model/model_generator.rb
|
178
165
|
- lib/generators/logidze/model/templates/migration.rb.erb
|
179
166
|
- lib/generators/logidze/model/triggers/logidze.sql
|
167
|
+
- lib/generators/logidze/model/triggers/logidze_after.sql
|
180
168
|
- lib/logidze.rb
|
181
169
|
- lib/logidze/engine.rb
|
182
170
|
- lib/logidze/has_logidze.rb
|
@@ -184,7 +172,6 @@ files:
|
|
184
172
|
- lib/logidze/history/type.rb
|
185
173
|
- lib/logidze/history/version.rb
|
186
174
|
- lib/logidze/ignore_log_data.rb
|
187
|
-
- lib/logidze/ignore_log_data/cast_attribute_patch.rb
|
188
175
|
- lib/logidze/meta.rb
|
189
176
|
- lib/logidze/model.rb
|
190
177
|
- lib/logidze/utils/check_pending.rb
|
@@ -209,14 +196,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
209
196
|
requirements:
|
210
197
|
- - ">="
|
211
198
|
- !ruby/object:Gem::Version
|
212
|
-
version: 2.
|
199
|
+
version: 2.7.0
|
213
200
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
214
201
|
requirements:
|
215
202
|
- - ">="
|
216
203
|
- !ruby/object:Gem::Version
|
217
204
|
version: '0'
|
218
205
|
requirements: []
|
219
|
-
rubygems_version: 3.
|
206
|
+
rubygems_version: 3.4.20
|
220
207
|
signing_key:
|
221
208
|
specification_version: 4
|
222
209
|
summary: PostgreSQL JSONB-based model changes tracking
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Logidze
|
4
|
-
module IgnoreLogData
|
5
|
-
# Fixes unexpected behavior (see more https://github.com/rails/rails/pull/34528):
|
6
|
-
# instead of using a type passed to `attribute` call, ignored column uses
|
7
|
-
# a type coming from the DB (in this case `.log_data` would return a plain hash
|
8
|
-
# instead of `Logidze::History`)
|
9
|
-
module CastAttributePatch
|
10
|
-
def log_data
|
11
|
-
return attributes["log_data"] if attributes["log_data"].is_a?(Logidze::History)
|
12
|
-
|
13
|
-
self.log_data = Logidze::History::Type.new.cast_value(super)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|