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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -5
- data/LICENSE.txt +1 -1
- data/README.md +263 -103
- 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 +58 -1
- 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 +49 -13
- 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 +27 -14
- data/lib/logidze/history.rb +1 -10
- data/lib/logidze/ignore_log_data.rb +1 -4
- data/lib/logidze/model.rb +48 -35
- data/lib/logidze/version.rb +1 -1
- metadata +48 -73
- data/.gitattributes +0 -3
- data/.github/ISSUE_TEMPLATE.md +0 -20
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -29
- data/.gitignore +0 -40
- data/.rubocop.yml +0 -55
- data/.travis.yml +0 -46
- data/Gemfile +0 -15
- data/Rakefile +0 -28
- data/assets/pg_log_data_chart.png +0 -0
- data/bench/performance/README.md +0 -109
- data/bench/performance/diff_bench.rb +0 -38
- data/bench/performance/insert_bench.rb +0 -22
- data/bench/performance/memory_profile.rb +0 -56
- data/bench/performance/setup.rb +0 -315
- data/bench/performance/update_bench.rb +0 -38
- 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 -6
- data/gemfiles/rails5.gemfile +0 -6
- data/gemfiles/rails52.gemfile +0 -6
- data/gemfiles/rails6.gemfile +0 -6
- data/gemfiles/railsmaster.gemfile +0 -7
- data/lib/logidze/ignore_log_data/ignored_columns.rb +0 -46
- data/lib/logidze/migration.rb +0 -20
- 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 : | 
| 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,14 +89,13 @@ 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
         | 
| @@ -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,  | 
| 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,  | 
| 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 | 
| 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
    
    | @@ -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 | 
            -
                 | 
| 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 | 
            -
                 | 
| 31 | 
            -
             | 
| 32 | 
            -
                 | 
| 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  | 
| 50 | 
            +
                    ActiveRecord::Base.connection.execute "SET LOCAL #{name} TO #{value};"
         | 
| 43 51 | 
             
                    res = yield
         | 
| 44 | 
            -
                    ActiveRecord::Base.connection.execute "SET LOCAL  | 
| 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
         | 
    
        data/lib/logidze/history.rb
    CHANGED
    
    | @@ -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. | 
| 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 ==  | 
| 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
         | 
    
        data/lib/logidze/model.rb
    CHANGED
    
    | @@ -3,43 +3,28 @@ | |
| 3 3 | 
             
            require "active_support"
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Logidze
         | 
| 6 | 
            -
               | 
| 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" | 
| 10 | 
            +
                require "logidze/history/type"
         | 
| 18 11 |  | 
| 19 12 | 
             
                extend ActiveSupport::Concern
         | 
| 20 13 |  | 
| 21 14 | 
             
                included do
         | 
| 22 | 
            -
                   | 
| 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( | 
| 34 | 
            -
                     | 
| 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( | 
| 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( | 
| 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!( | 
| 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 | 
            -
                   | 
| 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( | 
| 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 | 
| 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( | 
| 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)
         |