logidze 0.8.1 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -5
- data/LICENSE.txt +1 -1
- data/README.md +310 -80
- data/lib/generators/logidze/fx_helper.rb +17 -0
- data/lib/generators/logidze/inject_sql.rb +18 -0
- data/lib/generators/logidze/install/USAGE +6 -1
- data/lib/generators/logidze/install/functions/logidze_compact_history.sql +38 -0
- data/lib/generators/logidze/install/functions/logidze_filter_keys.sql +27 -0
- data/lib/generators/logidze/install/functions/logidze_logger.sql +150 -0
- data/lib/generators/logidze/install/functions/logidze_snapshot.sql +24 -0
- data/lib/generators/logidze/install/functions/logidze_version.sql +20 -0
- data/lib/generators/logidze/install/install_generator.rb +61 -3
- data/lib/generators/logidze/install/templates/hstore.rb.erb +1 -1
- data/lib/generators/logidze/install/templates/migration.rb.erb +19 -232
- data/lib/generators/logidze/install/templates/migration_fx.rb.erb +41 -0
- data/lib/generators/logidze/model/model_generator.rb +56 -20
- data/lib/generators/logidze/model/templates/migration.rb.erb +57 -36
- data/lib/generators/logidze/model/triggers/logidze.sql +6 -0
- data/lib/logidze.rb +43 -20
- data/lib/logidze/engine.rb +4 -1
- data/lib/logidze/has_logidze.rb +12 -3
- data/lib/logidze/history.rb +7 -15
- data/lib/logidze/history/type.rb +1 -1
- data/lib/logidze/history/version.rb +6 -5
- data/lib/logidze/ignore_log_data.rb +22 -0
- data/lib/logidze/ignore_log_data/cast_attribute_patch.rb +17 -0
- data/lib/logidze/meta.rb +44 -17
- data/lib/logidze/model.rb +65 -37
- data/lib/logidze/version.rb +2 -1
- data/lib/logidze/versioned_association.rb +0 -1
- metadata +47 -103
- data/.gitignore +0 -40
- data/.hound.yml +0 -3
- data/.rubocop.yml +0 -94
- data/.travis.yml +0 -39
- data/Gemfile +0 -13
- data/Rakefile +0 -28
- data/bench/performance/README.md +0 -109
- data/bench/performance/diff_bench.rb +0 -36
- data/bench/performance/insert_bench.rb +0 -20
- data/bench/performance/memory_profile.rb +0 -53
- data/bench/performance/setup.rb +0 -308
- data/bench/performance/update_bench.rb +0 -36
- data/bench/triggers/Makefile +0 -56
- data/bench/triggers/Readme.md +0 -58
- data/bench/triggers/bench.sql +0 -6
- data/bench/triggers/hstore_trigger_setup.sql +0 -38
- data/bench/triggers/jsonb_minus_2_setup.sql +0 -47
- data/bench/triggers/jsonb_minus_setup.sql +0 -49
- data/bench/triggers/keys2_trigger_setup.sql +0 -44
- data/bench/triggers/keys_trigger_setup.sql +0 -50
- data/bin/console +0 -8
- data/bin/setup +0 -9
- data/gemfiles/rails42.gemfile +0 -5
- data/gemfiles/rails5.gemfile +0 -6
- data/gemfiles/rails52.gemfile +0 -6
- data/gemfiles/railsmaster.gemfile +0 -7
- data/lib/logidze/migration.rb +0 -19
- data/logidze.gemspec +0 -33
@@ -0,0 +1,41 @@
|
|
1
|
+
class <%= @migration_class_name %> < ActiveRecord::Migration[5.0]
|
2
|
+
def change
|
3
|
+
<%- if update? -%>
|
4
|
+
reversible do |dir|
|
5
|
+
dir.up do
|
6
|
+
# Drop legacy functions (<1.0)
|
7
|
+
execute <<~SQL
|
8
|
+
DROP FUNCTION IF EXISTS logidze_version(bigint, jsonb);
|
9
|
+
DROP FUNCTION IF EXISTS logidze_snapshot(jsonb);
|
10
|
+
DROP FUNCTION IF EXISTS logidze_version(bigint, jsonb, text[]);
|
11
|
+
DROP FUNCTION IF EXISTS logidze_snapshot(jsonb, text[]);
|
12
|
+
DROP FUNCTION IF EXISTS logidze_version(bigint, jsonb, timestamp with time zone, text[]);
|
13
|
+
DROP FUNCTION IF EXISTS logidze_snapshot(jsonb, text, text[]);
|
14
|
+
DROP FUNCTION IF EXISTS logidze_exclude_keys(jsonb, VARIADIC text[]);
|
15
|
+
DROP FUNCTION IF EXISTS logidze_compact_history(jsonb);
|
16
|
+
SQL
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
<%- end -%>
|
21
|
+
<%- function_definitions.each do |f| -%>
|
22
|
+
<%- previous_version = previous_version_for(f.name) -%>
|
23
|
+
<%- if previous_version -%>
|
24
|
+
<%- if previous_version != f.version -%>
|
25
|
+
update_function :<%= f.name %>, version: <%= f.version %>, revert_to_version: <%= previous_version %>
|
26
|
+
<%- end -%>
|
27
|
+
<%- else -%>
|
28
|
+
reversible do |dir|
|
29
|
+
dir.up do
|
30
|
+
create_function :<%= f.name %>, version: <%= f.version %>
|
31
|
+
end
|
32
|
+
|
33
|
+
dir.down do
|
34
|
+
execute "DROP FUNCTION IF EXISTS <%= f.name %>(<%= f.signature %>) CASCADE"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
<%- end -%>
|
39
|
+
<%- end -%>
|
40
|
+
end
|
41
|
+
end
|
@@ -1,12 +1,20 @@
|
|
1
|
-
# rubocop:disable Metrics/BlockLength
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require "rails/generators"
|
4
4
|
require "rails/generators/active_record/migration/migration_generator"
|
5
|
+
require_relative "../inject_sql"
|
6
|
+
require_relative "../fx_helper"
|
7
|
+
|
8
|
+
using RubyNext
|
5
9
|
|
6
10
|
module Logidze
|
7
11
|
module Generators
|
8
12
|
class ModelGenerator < ::ActiveRecord::Generators::Base # :nodoc:
|
9
|
-
|
13
|
+
include InjectSql
|
14
|
+
include FxHelper
|
15
|
+
|
16
|
+
source_root File.expand_path("templates", __dir__)
|
17
|
+
source_paths << File.expand_path("triggers", __dir__)
|
10
18
|
|
11
19
|
class_option :limit, type: :numeric, optional: true, desc: "Specify history size limit"
|
12
20
|
|
@@ -21,8 +29,8 @@ module Logidze
|
|
21
29
|
|
22
30
|
class_option :path, type: :string, optional: true, desc: "Specify path to the model file"
|
23
31
|
|
24
|
-
class_option :
|
25
|
-
class_option :
|
32
|
+
class_option :except, type: :array, optional: true
|
33
|
+
class_option :only, type: :array, optional: true
|
26
34
|
|
27
35
|
class_option :timestamp_column, type: :string, optional: true,
|
28
36
|
desc: "Specify timestamp column"
|
@@ -31,13 +39,19 @@ module Logidze
|
|
31
39
|
desc: "Define whether this is an update migration"
|
32
40
|
|
33
41
|
def generate_migration
|
34
|
-
if options[:
|
35
|
-
warn "Use only one: --
|
42
|
+
if options[:except] && options[:only]
|
43
|
+
warn "Use only one: --only or --except"
|
36
44
|
exit(1)
|
37
45
|
end
|
38
46
|
migration_template "migration.rb.erb", "db/migrate/#{migration_file_name}"
|
39
47
|
end
|
40
48
|
|
49
|
+
def generate_fx_trigger
|
50
|
+
return unless fx?
|
51
|
+
|
52
|
+
template "logidze.sql", "db/triggers/logidze_on_#{table_name}_v#{next_version.to_s.rjust(2, "0")}.sql"
|
53
|
+
end
|
54
|
+
|
41
55
|
def inject_logidze_to_model
|
42
56
|
return if update?
|
43
57
|
|
@@ -75,19 +89,18 @@ module Logidze
|
|
75
89
|
options[:update]
|
76
90
|
end
|
77
91
|
|
78
|
-
def
|
79
|
-
|
80
|
-
|
81
|
-
else
|
82
|
-
class_name.constantize.column_names - options[:whitelist]
|
83
|
-
end
|
92
|
+
def filtered_columns
|
93
|
+
format_pgsql_array(options[:only] || options[:except])
|
94
|
+
end
|
84
95
|
|
85
|
-
|
96
|
+
def include_columns
|
97
|
+
return unless options[:only] || options[:except]
|
98
|
+
options[:only].present?
|
86
99
|
end
|
87
100
|
|
88
101
|
def timestamp_column
|
89
|
-
value = options[:timestamp_column] ||
|
90
|
-
return if %w
|
102
|
+
value = options[:timestamp_column] || "updated_at"
|
103
|
+
return if %w[nil null false].include?(value)
|
91
104
|
|
92
105
|
escape_pgsql_string(value)
|
93
106
|
end
|
@@ -96,18 +109,41 @@ module Logidze
|
|
96
109
|
options[:debounce_time]
|
97
110
|
end
|
98
111
|
|
112
|
+
def previous_version
|
113
|
+
@previous_version ||= all_triggers.filter_map { |path| Regexp.last_match[1].to_i if path =~ %r{logidze_on_#{table_name}_v(\d+).sql} }.max
|
114
|
+
end
|
115
|
+
|
116
|
+
def next_version
|
117
|
+
previous_version&.next || 1
|
118
|
+
end
|
119
|
+
|
120
|
+
def all_triggers
|
121
|
+
@all_triggers ||=
|
122
|
+
begin
|
123
|
+
res = nil
|
124
|
+
in_root do
|
125
|
+
res = if File.directory?("db/triggers")
|
126
|
+
Dir.entries("db/triggers")
|
127
|
+
else
|
128
|
+
[]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
res
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
99
135
|
def logidze_logger_parameters
|
100
|
-
format_pgsql_args(limit, timestamp_column,
|
136
|
+
format_pgsql_args(limit, timestamp_column, filtered_columns, include_columns, debounce_time)
|
101
137
|
end
|
102
138
|
|
103
139
|
def logidze_snapshot_parameters
|
104
|
-
format_pgsql_args(
|
140
|
+
format_pgsql_args("to_jsonb(t)", timestamp_column, filtered_columns, include_columns)
|
105
141
|
end
|
106
142
|
|
107
143
|
def format_pgsql_array(ruby_array)
|
108
144
|
return if ruby_array.blank?
|
109
145
|
|
110
|
-
"'{" + ruby_array.join(
|
146
|
+
"'{" + ruby_array.join(", ") + "}'"
|
111
147
|
end
|
112
148
|
|
113
149
|
def escape_pgsql_string(string)
|
@@ -124,10 +160,10 @@ module Logidze
|
|
124
160
|
def format_pgsql_args(*values)
|
125
161
|
args = []
|
126
162
|
values.reverse_each do |value|
|
127
|
-
formatted_value = value.presence || (args.any? &&
|
163
|
+
formatted_value = value.presence || (args.any? && "null")
|
128
164
|
args << formatted_value if formatted_value
|
129
165
|
end
|
130
|
-
args.compact.reverse.join(
|
166
|
+
args.compact.reverse.join(", ")
|
131
167
|
end
|
132
168
|
end
|
133
169
|
|
@@ -1,43 +1,64 @@
|
|
1
|
-
class <%= @migration_class_name %> < ActiveRecord::Migration
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def up
|
6
|
-
<% if update? %>
|
7
|
-
execute "DROP TRIGGER logidze_on_<%= table_name %> on <%= table_name %>;"
|
8
|
-
<% elsif !only_trigger? %>
|
1
|
+
class <%= @migration_class_name %> < ActiveRecord::Migration[5.0]
|
2
|
+
def change
|
3
|
+
<%- unless update? || only_trigger? -%>
|
9
4
|
add_column :<%= table_name %>, :log_data, :jsonb
|
10
|
-
|
5
|
+
<%- end -%>
|
11
6
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
<%- if fx? -%>
|
8
|
+
<%- if previous_version -%>
|
9
|
+
update_trigger :logidze_on_<%= table_name %>, on: :<%= table_name %>, version: <%= next_version %>, revert_to_version: <%= previous_version %>
|
10
|
+
<%- else -%>
|
11
|
+
reversible do |dir|
|
12
|
+
dir.up do
|
13
|
+
<%- if update? -%>
|
14
|
+
# Drop legacy trigger if any (<1.0)
|
15
|
+
execute "DROP TRIGGER IF EXISTS logidze_on_<%= table_name %> on <%= table_name %>;"
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
<%- end -%>
|
18
|
+
create_trigger :logidze_on_<%= table_name %>, on: :<%= table_name %>
|
19
|
+
end
|
20
|
+
|
21
|
+
dir.down do
|
22
|
+
execute "DROP TRIGGER IF EXISTS logidze_on_<%= table_name %> on <%= table_name %>;"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
<%- end -%>
|
26
|
+
<%- else -%>
|
27
|
+
reversible do |dir|
|
28
|
+
dir.up do
|
29
|
+
<%- if update? -%>
|
30
|
+
execute "DROP TRIGGER IF EXISTS logidze_on_<%= table_name %> on <%= table_name %>;"
|
31
|
+
|
32
|
+
<%- end -%>
|
33
|
+
execute <<~SQL
|
34
|
+
<%= inject_sql("logidze.sql", indent: 10) %>
|
35
|
+
SQL
|
36
|
+
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
dir.down do
|
39
|
+
<%- if update? -%>
|
40
|
+
# NOTE: We have no idea on how to revert the migration
|
41
|
+
# ('cause we don't know the previous trigger params),
|
42
|
+
# but you can do that on your own.
|
43
|
+
#
|
44
|
+
# Uncomment this line if you want to raise an error.
|
45
|
+
# raise ActiveRecord::IrreversibleMigration
|
46
|
+
<%- else -%>
|
47
|
+
execute "DROP TRIGGER IF EXISTS logidze_on_<%= table_name %> on <%= table_name %>;"
|
48
|
+
<%- end -%>
|
49
|
+
end
|
50
|
+
end
|
51
|
+
<%- end -%>
|
52
|
+
<%- if backfill? -%>
|
37
53
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
54
|
+
reversible do |dir|
|
55
|
+
dir.up do
|
56
|
+
execute <<~SQL
|
57
|
+
UPDATE <%= table_name %> as t
|
58
|
+
SET log_data = logidze_snapshot(<%= logidze_snapshot_parameters %>);
|
59
|
+
SQL
|
60
|
+
end
|
61
|
+
end
|
62
|
+
<%- end -%>
|
42
63
|
end
|
43
64
|
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
CREATE TRIGGER logidze_on_<%= table_name %>
|
2
|
+
BEFORE UPDATE OR INSERT ON <%= table_name %> FOR EACH ROW
|
3
|
+
WHEN (coalesce(current_setting('logidze.disabled', true), '') <> 'on')
|
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(<%= logidze_logger_parameters %>);
|
data/lib/logidze.rb
CHANGED
@@ -1,39 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "logidze/version"
|
3
4
|
|
4
5
|
# Logidze provides tools for adding in-table JSON-based audit to DB tables
|
5
6
|
# and ActiveRecord extensions to work with changes history.
|
6
7
|
module Logidze
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
8
|
+
require "ruby-next"
|
9
|
+
require "logidze/history"
|
10
|
+
require "logidze/model"
|
11
|
+
require "logidze/versioned_association"
|
12
|
+
require "logidze/ignore_log_data"
|
13
|
+
require "logidze/has_logidze"
|
14
|
+
require "logidze/meta"
|
12
15
|
|
13
16
|
extend Logidze::Meta
|
14
17
|
|
15
|
-
require
|
18
|
+
require "logidze/engine" if defined?(Rails)
|
16
19
|
|
17
20
|
class << self
|
18
21
|
# Determines if Logidze should append a version to the log after updating an old version.
|
19
22
|
attr_accessor :append_on_undo
|
20
|
-
|
23
|
+
# Determines whether associations versioning is enabled or not
|
24
|
+
attr_accessor :associations_versioning
|
25
|
+
# Determines if Logidze should exclude log data from SELECT statements
|
26
|
+
attr_accessor :ignore_log_data_by_default
|
27
|
+
# Whether #at should return self or nil when log_data is nil
|
28
|
+
attr_accessor :return_self_if_log_data_is_empty
|
21
29
|
|
22
|
-
|
23
|
-
|
30
|
+
# Temporary disable DB triggers.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# Logidze.without_logging { Post.update_all(active: true) }
|
34
|
+
def without_logging
|
35
|
+
with_logidze_setting("logidze.disabled", "on") { yield }
|
24
36
|
end
|
25
|
-
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
# Instructure Logidze to create a full snapshot for the new versions, not a diff
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# Logidze.with_full_snapshot { post.touch }
|
42
|
+
def with_full_snapshot
|
43
|
+
with_logidze_setting("logidze.full_snapshot", "on") { yield }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def with_logidze_setting(name, value)
|
49
|
+
ActiveRecord::Base.transaction do
|
50
|
+
ActiveRecord::Base.connection.execute "SET LOCAL #{name} TO #{value};"
|
51
|
+
res = yield
|
52
|
+
ActiveRecord::Base.connection.execute "SET LOCAL #{name} TO DEFAULT;"
|
53
|
+
res
|
54
|
+
end
|
37
55
|
end
|
38
56
|
end
|
57
|
+
|
58
|
+
self.append_on_undo = false
|
59
|
+
self.associations_versioning = false
|
60
|
+
self.ignore_log_data_by_default = false
|
61
|
+
self.return_self_if_log_data_is_empty = true
|
39
62
|
end
|
data/lib/logidze/engine.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "logidze"
|
3
4
|
|
4
5
|
module Logidze
|
5
6
|
class Engine < Rails::Engine # :nodoc:
|
7
|
+
config.logidze = Logidze
|
8
|
+
|
6
9
|
initializer "extend ActiveRecord with Logidze" do |_app|
|
7
10
|
ActiveSupport.on_load(:active_record) do
|
8
11
|
ActiveRecord::Base.send :include, Logidze::HasLogidze
|
data/lib/logidze/has_logidze.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "active_support"
|
3
4
|
|
4
5
|
module Logidze
|
5
6
|
# Add `has_logidze` method to AR::Base
|
@@ -9,9 +10,17 @@ module Logidze
|
|
9
10
|
module ClassMethods # :nodoc:
|
10
11
|
# Include methods to work with history.
|
11
12
|
#
|
12
|
-
|
13
|
-
|
13
|
+
def has_logidze(ignore_log_data: Logidze.ignore_log_data_by_default)
|
14
|
+
include Logidze::IgnoreLogData
|
14
15
|
include Logidze::Model
|
16
|
+
|
17
|
+
@ignore_log_data = ignore_log_data
|
18
|
+
|
19
|
+
self.ignored_columns += ["log_data"] if @ignore_log_data
|
20
|
+
end
|
21
|
+
|
22
|
+
def ignores_log_data?
|
23
|
+
@ignore_log_data
|
15
24
|
end
|
16
25
|
end
|
17
26
|
end
|
data/lib/logidze/history.rb
CHANGED
@@ -1,30 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
3
4
|
|
4
5
|
module Logidze
|
5
6
|
# Log data wrapper
|
6
7
|
class History
|
7
|
-
require
|
8
|
+
require "logidze/history/version"
|
8
9
|
|
9
10
|
# History key
|
10
|
-
HISTORY =
|
11
|
+
HISTORY = "h"
|
11
12
|
# Version key
|
12
|
-
VERSION =
|
13
|
+
VERSION = "v"
|
13
14
|
|
14
15
|
attr_reader :data
|
15
16
|
|
16
17
|
delegate :size, to: :versions
|
17
18
|
delegate :responsible_id, :meta, to: :current_version
|
18
19
|
|
19
|
-
### Rails 4 ###
|
20
|
-
def self.dump(object)
|
21
|
-
ActiveSupport::JSON.encode(object)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.load(json)
|
25
|
-
new(json) if json.present?
|
26
|
-
end
|
27
|
-
|
28
20
|
def initialize(data)
|
29
21
|
@data = data
|
30
22
|
end
|
@@ -107,7 +99,7 @@ module Logidze
|
|
107
99
|
|
108
100
|
# Return nearest (from the bottom) version to the specified time
|
109
101
|
def find_by_time(time)
|
110
|
-
versions.
|
102
|
+
versions.reverse_each.find { |v| v.time <= time }
|
111
103
|
end
|
112
104
|
|
113
105
|
def dup
|
@@ -128,7 +120,7 @@ module Logidze
|
|
128
120
|
|
129
121
|
def build_changes(a, b)
|
130
122
|
b.each_with_object({}) do |(k, v), acc|
|
131
|
-
acc[k] = {
|
123
|
+
acc[k] = {"old" => a[k], "new" => v} unless v == a[k]
|
132
124
|
end
|
133
125
|
end
|
134
126
|
|