logidze 0.12.0 → 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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -5
  3. data/LICENSE.txt +1 -1
  4. data/README.md +263 -103
  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_compact_history.sql +38 -0
  9. data/lib/generators/logidze/install/functions/logidze_filter_keys.sql +27 -0
  10. data/lib/generators/logidze/install/functions/logidze_logger.sql +150 -0
  11. data/lib/generators/logidze/install/functions/logidze_snapshot.sql +24 -0
  12. data/lib/generators/logidze/install/functions/logidze_version.sql +20 -0
  13. data/lib/generators/logidze/install/install_generator.rb +58 -1
  14. data/lib/generators/logidze/install/templates/hstore.rb.erb +1 -1
  15. data/lib/generators/logidze/install/templates/migration.rb.erb +19 -232
  16. data/lib/generators/logidze/install/templates/migration_fx.rb.erb +41 -0
  17. data/lib/generators/logidze/model/model_generator.rb +49 -13
  18. data/lib/generators/logidze/model/templates/migration.rb.erb +57 -36
  19. data/lib/generators/logidze/model/triggers/logidze.sql +6 -0
  20. data/lib/logidze.rb +27 -14
  21. data/lib/logidze/history.rb +1 -10
  22. data/lib/logidze/ignore_log_data.rb +1 -4
  23. data/lib/logidze/model.rb +48 -35
  24. data/lib/logidze/version.rb +1 -1
  25. metadata +48 -73
  26. data/.gitattributes +0 -3
  27. data/.github/ISSUE_TEMPLATE.md +0 -20
  28. data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
  29. data/.gitignore +0 -40
  30. data/.rubocop.yml +0 -55
  31. data/.travis.yml +0 -46
  32. data/Gemfile +0 -15
  33. data/Rakefile +0 -28
  34. data/assets/pg_log_data_chart.png +0 -0
  35. data/bench/performance/README.md +0 -109
  36. data/bench/performance/diff_bench.rb +0 -38
  37. data/bench/performance/insert_bench.rb +0 -22
  38. data/bench/performance/memory_profile.rb +0 -56
  39. data/bench/performance/setup.rb +0 -315
  40. data/bench/performance/update_bench.rb +0 -38
  41. data/bench/triggers/Makefile +0 -56
  42. data/bench/triggers/Readme.md +0 -58
  43. data/bench/triggers/bench.sql +0 -6
  44. data/bench/triggers/hstore_trigger_setup.sql +0 -38
  45. data/bench/triggers/jsonb_minus_2_setup.sql +0 -47
  46. data/bench/triggers/jsonb_minus_setup.sql +0 -49
  47. data/bench/triggers/keys2_trigger_setup.sql +0 -44
  48. data/bench/triggers/keys_trigger_setup.sql +0 -50
  49. data/bin/console +0 -8
  50. data/bin/setup +0 -9
  51. data/gemfiles/rails42.gemfile +0 -6
  52. data/gemfiles/rails5.gemfile +0 -6
  53. data/gemfiles/rails52.gemfile +0 -6
  54. data/gemfiles/rails6.gemfile +0 -6
  55. data/gemfiles/railsmaster.gemfile +0 -7
  56. data/lib/logidze/ignore_log_data/ignored_columns.rb +0 -46
  57. data/lib/logidze/migration.rb +0 -20
  58. data/logidze.gemspec +0 -41
@@ -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
@@ -2,11 +2,19 @@
2
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:
13
+ include InjectSql
14
+ include FxHelper
15
+
9
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 :blacklist, type: :array, optional: true
25
- class_option :whitelist, type: :array, optional: true
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[:blacklist] && options[:whitelist]
35
- warn "Use only one: --whitelist or --blacklist"
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,14 +89,13 @@ module Logidze
75
89
  options[:update]
76
90
  end
77
91
 
78
- def columns_blacklist
79
- array = if !options[:whitelist]
80
- options[:blacklist]
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
- format_pgsql_array(array)
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
@@ -96,12 +109,35 @@ 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, columns_blacklist, debounce_time)
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("to_jsonb(t)", timestamp_column, columns_blacklist)
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)
@@ -1,43 +1,64 @@
1
- class <%= @migration_class_name %> < ActiveRecord::Migration<%= ActiveRecord::VERSION::MAJOR < 5 ? '' : '[5.0]' %>
2
- require 'logidze/migration'
3
- include Logidze::Migration
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
- <% end %>
5
+ <%- end -%>
11
6
 
12
- execute <<-SQL
13
- CREATE TRIGGER logidze_on_<%= table_name %>
14
- BEFORE UPDATE OR INSERT ON <%= table_name %> FOR EACH ROW
15
- WHEN (coalesce(#{current_setting('logidze.disabled')}, '') <> 'on')
16
- EXECUTE PROCEDURE logidze_logger(<%= logidze_logger_parameters %>);
17
- SQL
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
- <% if backfill? %>
20
- execute <<-SQL
21
- UPDATE <%= table_name %> as t
22
- SET log_data = logidze_snapshot(<%= logidze_snapshot_parameters %>);
23
- SQL
24
- <% end %>
25
- end
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
- def down
28
- <% if update? %>
29
- # NOTE: We have no idea on how to revert the migration
30
- # ('cause we don't know the previous trigger params),
31
- # but you can do that on your own.
32
- #
33
- # Uncomment this line if you want to raise an error.
34
- # raise ActiveRecord::IrreversibleMigration
35
- <% else %>
36
- execute "DROP TRIGGER IF EXISTS logidze_on_<%= table_name %> on <%= table_name %>;"
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
- <% if !only_trigger? %>
39
- remove_column :<%= table_name %>, :log_data
40
- <% end %>
41
- <% end %>
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 %>);
@@ -5,6 +5,7 @@ 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"
8
9
  require "logidze/history"
9
10
  require "logidze/model"
10
11
  require "logidze/versioned_association"
@@ -19,31 +20,43 @@ module Logidze
19
20
  class << self
20
21
  # Determines if Logidze should append a version to the log after updating an old version.
21
22
  attr_accessor :append_on_undo
22
-
23
- attr_writer :associations_versioning
24
-
25
- def associations_versioning
26
- @associations_versioning || false
27
- end
28
-
23
+ # Determines whether associations versioning is enabled or not
24
+ attr_accessor :associations_versioning
29
25
  # Determines if Logidze should exclude log data from SELECT statements
30
- attr_writer :ignore_log_data_by_default
31
-
32
- def ignore_log_data_by_default
33
- @ignore_log_data_by_default || false
34
- end
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
35
29
 
36
30
  # Temporary disable DB triggers.
37
31
  #
38
32
  # @example
39
33
  # Logidze.without_logging { Post.update_all(active: true) }
40
34
  def without_logging
35
+ with_logidze_setting("logidze.disabled", "on") { yield }
36
+ end
37
+
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)
41
49
  ActiveRecord::Base.transaction do
42
- ActiveRecord::Base.connection.execute "SET LOCAL logidze.disabled TO on;"
50
+ ActiveRecord::Base.connection.execute "SET LOCAL #{name} TO #{value};"
43
51
  res = yield
44
- ActiveRecord::Base.connection.execute "SET LOCAL logidze.disabled TO DEFAULT;"
52
+ ActiveRecord::Base.connection.execute "SET LOCAL #{name} TO DEFAULT;"
45
53
  res
46
54
  end
47
55
  end
48
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
49
62
  end
@@ -17,15 +17,6 @@ module Logidze
17
17
  delegate :size, to: :versions
18
18
  delegate :responsible_id, :meta, to: :current_version
19
19
 
20
- ### Rails 4 ###
21
- def self.dump(object)
22
- ActiveSupport::JSON.encode(object)
23
- end
24
-
25
- def self.load(json)
26
- new(json) if json.present?
27
- end
28
-
29
20
  def initialize(data)
30
21
  @data = data
31
22
  end
@@ -108,7 +99,7 @@ module Logidze
108
99
 
109
100
  # Return nearest (from the bottom) version to the specified time
110
101
  def find_by_time(time)
111
- versions.reverse.find { |v| v.time <= time }
102
+ versions.reverse_each.find { |v| v.time <= time }
112
103
  end
113
104
 
114
105
  def dup
@@ -5,10 +5,7 @@ module Logidze
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- if Rails::VERSION::MAJOR == 4
9
- require "logidze/ignore_log_data/ignored_columns"
10
- attribute :log_data, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb.new
11
- elsif Rails::VERSION::MAJOR == 5
8
+ if Rails::VERSION::MAJOR == 5
12
9
  require "logidze/ignore_log_data/cast_attribute_patch"
13
10
  include CastAttributePatch
14
11
  end
@@ -3,43 +3,28 @@
3
3
  require "active_support"
4
4
 
5
5
  module Logidze
6
- module Deprecations # :nodoc:
7
- def self.show_ts_deprecation_for(meth)
8
- warn(
9
- "[Deprecation] Usage of #{meth}(time) will be removed in the future releases, "\
10
- "use #{meth}(time: ts) instead"
11
- )
12
- end
13
- end
6
+ using RubyNext
14
7
 
15
8
  # Extends model with methods to browse history
16
9
  module Model
17
- require "logidze/history/type" if Rails::VERSION::MAJOR >= 5
10
+ require "logidze/history/type"
18
11
 
19
12
  extend ActiveSupport::Concern
20
13
 
21
14
  included do
22
- if Rails::VERSION::MAJOR < 5
23
- serialize :log_data, Logidze::History
24
- else
25
- attribute :log_data, Logidze::History::Type.new
26
- end
15
+ attribute :log_data, Logidze::History::Type.new
27
16
 
28
17
  delegate :version, to: :log_data, prefix: "log"
29
18
  end
30
19
 
31
20
  module ClassMethods # :nodoc:
32
21
  # Return records reverted to specified time
33
- def at(ts = nil, time: nil, version: nil)
34
- Deprecations.show_ts_deprecation_for(".at") if ts
35
- time ||= ts
36
- all.map { |record| record.at(time: time, version: version) }.compact
22
+ def at(time: nil, version: nil)
23
+ all.to_a.filter_map { |record| record.at(time: time, version: version) }
37
24
  end
38
25
 
39
26
  # Return changes made to records since specified time
40
- def diff_from(ts = nil, time: nil, version: nil)
41
- Deprecations.show_ts_deprecation_for(".diff_from") if ts
42
- time ||= ts
27
+ def diff_from(time: nil, version: nil)
43
28
  all.map { |record| record.diff_from(time: time, version: version) }
44
29
  end
45
30
 
@@ -58,6 +43,28 @@ module Logidze
58
43
  def reset_log_data
59
44
  without_logging { update_all(log_data: nil) }
60
45
  end
46
+
47
+ # Initialize log_data with the current state if it's null
48
+ def create_logidze_snapshot(timestamp: nil, only: nil, except: nil)
49
+ args = ["'null'"]
50
+
51
+ args[0] = "'#{timestamp}'" if timestamp
52
+
53
+ columns = only || except
54
+
55
+ if columns
56
+ args[1] = "'{#{columns.join(",")}}'"
57
+ args[2] = only ? "true" : "false"
58
+ end
59
+
60
+ without_logging do
61
+ where(log_data: nil).update_all(
62
+ <<~SQL
63
+ log_data = logidze_snapshot(to_jsonb(#{quoted_table_name}), #{args.join(", ")})
64
+ SQL
65
+ )
66
+ end
67
+ end
61
68
  end
62
69
 
63
70
  # Use this to convert Ruby time to milliseconds
@@ -69,14 +76,15 @@ module Logidze
69
76
  # If time/version is less then the first version, then return nil.
70
77
  # If time/version is greater then the last version, then return self.
71
78
  # rubocop: disable Metrics/MethodLength
72
- def at(ts = nil, time: nil, version: nil)
73
- Deprecations.show_ts_deprecation_for("#at") if ts
74
-
79
+ def at(time: nil, version: nil)
75
80
  return at_version(version) if version
76
81
 
77
- time ||= ts
78
82
  time = parse_time(time)
79
83
 
84
+ unless log_data
85
+ return Logidze.return_self_if_log_data_is_empty ? self : nil
86
+ end
87
+
80
88
  return nil unless log_data.exists_ts?(time)
81
89
 
82
90
  if log_data.current_ts?(time)
@@ -91,12 +99,11 @@ module Logidze
91
99
  # rubocop: enable Metrics/MethodLength
92
100
 
93
101
  # Revert record to the version at specified time (without saving to DB)
94
- def at!(ts = nil, time: nil, version: nil)
95
- Deprecations.show_ts_deprecation_for("#at!") if ts
96
-
102
+ def at!(time: nil, version: nil)
97
103
  return at_version!(version) if version
98
104
 
99
- time ||= ts
105
+ raise ArgumentError, "#log_data is empty" unless log_data
106
+
100
107
  time = parse_time(time)
101
108
 
102
109
  return self if log_data.current_ts?(time)
@@ -119,6 +126,8 @@ module Logidze
119
126
 
120
127
  # Revert record to the specified version (without saving to DB)
121
128
  def at_version!(version)
129
+ raise ArgumentError, "#log_data is empty" unless log_data
130
+
122
131
  return self if log_data.version == version
123
132
  return false unless log_data.find_by_version(version)
124
133
 
@@ -131,13 +140,11 @@ module Logidze
131
140
  #
132
141
  # post.diff_from(time: 2.days.ago) # or post.diff_from(version: 2)
133
142
  # #=> { "id" => 1, "changes" => { "title" => { "old" => "Hello!", "new" => "World" } } }
134
- def diff_from(ts = nil, version: nil, time: nil)
135
- Deprecations.show_ts_deprecation_for("#diff_from") if ts
136
- time ||= ts
143
+ def diff_from(version: nil, time: nil)
137
144
  time = parse_time(time) if time
138
- changes = log_data.diff_from(time: time, version: version).tap do |v|
145
+ changes = log_data&.diff_from(time: time, version: version)&.tap do |v|
139
146
  deserialize_changes!(v)
140
- end
147
+ end || {}
141
148
 
142
149
  changes.delete_if { |k, _v| deleted_column?(k) }
143
150
 
@@ -206,7 +213,7 @@ module Logidze
206
213
 
207
214
  # Loads log_data field from the database, stores to the attributes hash and returns it
208
215
  def reload_log_data
209
- self.log_data = self.class.where(self.class.primary_key => id).pluck(:log_data).first
216
+ self.log_data = self.class.where(self.class.primary_key => id).pluck("#{self.class.table_name}.log_data").first
210
217
  end
211
218
 
212
219
  # Nullify log_data column for a single record
@@ -214,6 +221,12 @@ module Logidze
214
221
  self.class.without_logging { update_column(:log_data, nil) }
215
222
  end
216
223
 
224
+ def create_logidze_snapshot!(**opts)
225
+ self.class.where(self.class.primary_key => id).create_logidze_snapshot(**opts)
226
+
227
+ reload_log_data
228
+ end
229
+
217
230
  protected
218
231
 
219
232
  def apply_diff(version, diff)