logidze 1.3.1 → 1.4.0
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 +6 -0
 - data/README.md +21 -0
 - data/lib/generators/logidze/install/functions/logidze_logger.sql +80 -8
 - data/lib/generators/logidze/install/functions/logidze_logger_after.sql +1 -1
 - data/lib/generators/logidze/install/install_generator.rb +11 -1
 - data/lib/generators/logidze/migration/USAGE +7 -0
 - data/lib/generators/logidze/migration/logs_generator.rb +24 -0
 - data/lib/generators/logidze/migration/templates/migration.rb.erb +10 -0
 - data/lib/generators/logidze/model/USAGE +4 -0
 - data/lib/generators/logidze/model/model_generator.rb +21 -2
 - data/lib/generators/logidze/model/templates/migration.rb.erb +16 -6
 - data/lib/generators/logidze/model/triggers/logidze.sql +1 -1
 - data/lib/generators/logidze/model/triggers/logidze_after.sql +1 -1
 - data/lib/logidze/detachable.rb +87 -0
 - data/lib/logidze/has_logidze.rb +6 -2
 - data/lib/logidze/history/type.rb +4 -0
 - data/lib/logidze/model.rb +31 -11
 - data/lib/logidze/version.rb +1 -1
 - data/lib/logidze.rb +16 -0
 - metadata +23 -5
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 0cc10c417605b0f92991c201aeb958a44bc6130b64fd3b278780fb780818ccb3
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 3c06efc69a95294bff75de4c8d91c1528c0cc67f5c0b5753097599a4c9e8fe5b
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 62d98e00caed7aa2a6f2ccd2be0ed536d2500ef0f195a391f17569c18cf7ce6051ef846abe45d167633100129d7e26555ae3ab4c728f73c05cfaec56dcd5df96
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 56652129cef0c7ca97b2a87dd7a94ff6711b21a43680ba87a4af7f3b253fea7a171a5d2307bd81b8af2ca46a08f4b999929cfedf8dd46db2a75a72c61a8f551d
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -2,6 +2,12 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            ## master (unreleased)
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
            ## 1.4.0 (2025-05-09) 🎇
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            - Support Rails 7.2 ([@atomaka][])
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            - Add `--detached` option to store `log_data` in a separate `logidze_data` table to avoid table bloat.
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
       5 
11 
     | 
    
         
             
            ## 1.3.1 (2024-10-23)
         
     | 
| 
       6 
12 
     | 
    
         | 
| 
       7 
13 
     | 
    
         
             
            - Fix `rails destroy logidze:model SomeModel` not deleting the `fx` trigger file file. ([@tylerhunt][])
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -37,6 +37,7 @@ Other requirements: 
     | 
|
| 
       37 
37 
     | 
    
         
             
              - [Logs timestamps](#logs-timestamps)
         
     | 
| 
       38 
38 
     | 
    
         
             
              - [Undoing a Generated Invocation](#undoing-a-generated-invocation)
         
     | 
| 
       39 
39 
     | 
    
         
             
              - [Using with partitioned tables](#using-with-partitioned-tables)
         
     | 
| 
      
 40 
     | 
    
         
            +
              - [Storing history data in a separate table](#storing-history-data-in-a-separate-table)
         
     | 
| 
       40 
41 
     | 
    
         
             
            - [Usage](#usage)
         
     | 
| 
       41 
42 
     | 
    
         
             
              - [Basic API](#basic-api)
         
     | 
| 
       42 
43 
     | 
    
         
             
              - [Track meta information](#track-meta-information)
         
     | 
| 
         @@ -203,6 +204,25 @@ bundle exec rails generate logidze:model Post --after-trigger 
     | 
|
| 
       203 
204 
     | 
    
         | 
| 
       204 
205 
     | 
    
         
             
            **IMPORTANT:** Using Logidze for partitioned tables in PostgreSQL 10 is not supported.
         
     | 
| 
       205 
206 
     | 
    
         | 
| 
      
 207 
     | 
    
         
            +
            ### Storing history data in a separate table
         
     | 
| 
      
 208 
     | 
    
         
            +
             
     | 
| 
      
 209 
     | 
    
         
            +
            By default, Logidze stores history data in the `log_data` column in the origin record table, which might lead to table bloat.
         
     | 
| 
      
 210 
     | 
    
         
            +
            If it concerns you, you may configure Logidze to store history data in a separate table by providing `--detached` option to the migration:
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
            ```sh
         
     | 
| 
      
 213 
     | 
    
         
            +
            bundle exec rails logidze:model Post --detached
         
     | 
| 
      
 214 
     | 
    
         
            +
            ```
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
            You can also configure Logidze to always store history data in a separate table for all models:
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 219 
     | 
    
         
            +
            # config/initializers/logidze.rb
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
            Logidze.log_data_placement = :detached
         
     | 
| 
      
 222 
     | 
    
         
            +
            ```
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            **IMPORTANT:** Using `--detached` mode for storing historic data slightly decreases performance. Check [bench results] for the details.
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
       206 
226 
     | 
    
         
             
            ## Usage
         
     | 
| 
       207 
227 
     | 
    
         | 
| 
       208 
228 
     | 
    
         
             
            ### Basic API
         
     | 
| 
         @@ -645,3 +665,4 @@ Bug reports and pull requests are welcome on GitHub at [https://github.com/palka 
     | 
|
| 
       645 
665 
     | 
    
         
             
            The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
         
     | 
| 
       646 
666 
     | 
    
         | 
| 
       647 
667 
     | 
    
         
             
            [fx]: https://github.com/teoljungberg/fx
         
     | 
| 
      
 668 
     | 
    
         
            +
            [bench results]: https://github.com/palkan/logidze/blob/feat-log-data-separate-storage/bench/performance/README.md
         
     | 
| 
         @@ -1,5 +1,5 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
         
     | 
| 
       2 
     | 
    
         
            -
              -- version:  
     | 
| 
      
 2 
     | 
    
         
            +
              -- version: 5
         
     | 
| 
       3 
3 
     | 
    
         
             
              DECLARE
         
     | 
| 
       4 
4 
     | 
    
         
             
                changes jsonb;
         
     | 
| 
       5 
5 
     | 
    
         
             
                version jsonb;
         
     | 
| 
         @@ -15,6 +15,15 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       15 
15 
     | 
    
         
             
                item record;
         
     | 
| 
       16 
16 
     | 
    
         
             
                columns text[];
         
     | 
| 
       17 
17 
     | 
    
         
             
                include_columns boolean;
         
     | 
| 
      
 18 
     | 
    
         
            +
                detached_log_data jsonb;
         
     | 
| 
      
 19 
     | 
    
         
            +
                -- We use `detached_loggable_type` for:
         
     | 
| 
      
 20 
     | 
    
         
            +
                -- 1. Checking if current implementation is `--detached` (`log_data` is stored in a separated table)
         
     | 
| 
      
 21 
     | 
    
         
            +
                -- 2. If implementation is `--detached` then we use detached_loggable_type to determine
         
     | 
| 
      
 22 
     | 
    
         
            +
                --    to which table current `log_data` record belongs
         
     | 
| 
      
 23 
     | 
    
         
            +
                detached_loggable_type text;
         
     | 
| 
      
 24 
     | 
    
         
            +
                log_data_table_name text;
         
     | 
| 
      
 25 
     | 
    
         
            +
                log_data_is_empty boolean;
         
     | 
| 
      
 26 
     | 
    
         
            +
                log_data_ts_key_data text;
         
     | 
| 
       18 
27 
     | 
    
         
             
                ts timestamp with time zone;
         
     | 
| 
       19 
28 
     | 
    
         
             
                ts_column text;
         
     | 
| 
       20 
29 
     | 
    
         
             
                err_sqlstate text;
         
     | 
| 
         @@ -30,8 +39,30 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       30 
39 
     | 
    
         
             
                ts_column := NULLIF(TG_ARGV[1], 'null');
         
     | 
| 
       31 
40 
     | 
    
         
             
                columns := NULLIF(TG_ARGV[2], 'null');
         
     | 
| 
       32 
41 
     | 
    
         
             
                include_columns := NULLIF(TG_ARGV[3], 'null');
         
     | 
| 
      
 42 
     | 
    
         
            +
                detached_loggable_type := NULLIF(TG_ARGV[5], 'null');
         
     | 
| 
      
 43 
     | 
    
         
            +
                log_data_table_name := NULLIF(TG_ARGV[6], 'null');
         
     | 
| 
       33 
44 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
      
 45 
     | 
    
         
            +
                -- getting previous log_data if it exists for detached `log_data` storage variant
         
     | 
| 
      
 46 
     | 
    
         
            +
                IF detached_loggable_type IS NOT NULL
         
     | 
| 
      
 47 
     | 
    
         
            +
                THEN
         
     | 
| 
      
 48 
     | 
    
         
            +
                  EXECUTE format(
         
     | 
| 
      
 49 
     | 
    
         
            +
                    'SELECT ldtn.log_data ' ||
         
     | 
| 
      
 50 
     | 
    
         
            +
                    'FROM %I ldtn ' ||
         
     | 
| 
      
 51 
     | 
    
         
            +
                    'WHERE ldtn.loggable_type = $1 ' ||
         
     | 
| 
      
 52 
     | 
    
         
            +
                      'AND ldtn.loggable_id = $2 '  ||
         
     | 
| 
      
 53 
     | 
    
         
            +
                    'LIMIT 1',
         
     | 
| 
      
 54 
     | 
    
         
            +
                    log_data_table_name
         
     | 
| 
      
 55 
     | 
    
         
            +
                  ) USING detached_loggable_type, NEW.id INTO detached_log_data;
         
     | 
| 
      
 56 
     | 
    
         
            +
                END IF;
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                IF detached_loggable_type IS NULL
         
     | 
| 
      
 59 
     | 
    
         
            +
                THEN
         
     | 
| 
      
 60 
     | 
    
         
            +
                    log_data_is_empty = NEW.log_data is NULL OR NEW.log_data = '{}'::jsonb;
         
     | 
| 
      
 61 
     | 
    
         
            +
                ELSE
         
     | 
| 
      
 62 
     | 
    
         
            +
                    log_data_is_empty = detached_log_data IS NULL OR detached_log_data = '{}'::jsonb;
         
     | 
| 
      
 63 
     | 
    
         
            +
                END IF;
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                IF log_data_is_empty
         
     | 
| 
       35 
66 
     | 
    
         
             
                THEN
         
     | 
| 
       36 
67 
     | 
    
         
             
                  IF columns IS NOT NULL THEN
         
     | 
| 
       37 
68 
     | 
    
         
             
                    log_data = logidze_snapshot(to_jsonb(NEW.*), ts_column, columns, include_columns);
         
     | 
| 
         @@ -40,7 +71,16 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       40 
71 
     | 
    
         
             
                  END IF;
         
     | 
| 
       41 
72 
     | 
    
         | 
| 
       42 
73 
     | 
    
         
             
                  IF log_data#>>'{h, -1, c}' != '{}' THEN
         
     | 
| 
       43 
     | 
    
         
            -
                     
     | 
| 
      
 74 
     | 
    
         
            +
                    IF detached_loggable_type IS NULL
         
     | 
| 
      
 75 
     | 
    
         
            +
                    THEN
         
     | 
| 
      
 76 
     | 
    
         
            +
                      NEW.log_data := log_data;
         
     | 
| 
      
 77 
     | 
    
         
            +
                    ELSE
         
     | 
| 
      
 78 
     | 
    
         
            +
                      EXECUTE format(
         
     | 
| 
      
 79 
     | 
    
         
            +
                        'INSERT INTO %I(log_data, loggable_type, loggable_id) ' ||
         
     | 
| 
      
 80 
     | 
    
         
            +
                        'VALUES ($1, $2, $3);',
         
     | 
| 
      
 81 
     | 
    
         
            +
                        log_data_table_name
         
     | 
| 
      
 82 
     | 
    
         
            +
                      ) USING log_data, detached_loggable_type, NEW.id;
         
     | 
| 
      
 83 
     | 
    
         
            +
                    END IF;
         
     | 
| 
       44 
84 
     | 
    
         
             
                  END IF;
         
     | 
| 
       45 
85 
     | 
    
         | 
| 
       46 
86 
     | 
    
         
             
                ELSE
         
     | 
| 
         @@ -52,7 +92,12 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       52 
92 
     | 
    
         
             
                  history_limit := NULLIF(TG_ARGV[0], 'null');
         
     | 
| 
       53 
93 
     | 
    
         
             
                  debounce_time := NULLIF(TG_ARGV[4], 'null');
         
     | 
| 
       54 
94 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                   
     | 
| 
      
 95 
     | 
    
         
            +
                  IF detached_loggable_type IS NULL
         
     | 
| 
      
 96 
     | 
    
         
            +
                  THEN
         
     | 
| 
      
 97 
     | 
    
         
            +
                      log_data := NEW.log_data;
         
     | 
| 
      
 98 
     | 
    
         
            +
                  ELSE
         
     | 
| 
      
 99 
     | 
    
         
            +
                      log_data := detached_log_data;
         
     | 
| 
      
 100 
     | 
    
         
            +
                  END IF;
         
     | 
| 
       56 
101 
     | 
    
         | 
| 
       57 
102 
     | 
    
         
             
                  current_version := (log_data->>'v')::int;
         
     | 
| 
       58 
103 
     | 
    
         | 
| 
         @@ -65,8 +110,16 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       65 
110 
     | 
    
         
             
                    END IF;
         
     | 
| 
       66 
111 
     | 
    
         
             
                  ELSEIF TG_OP = 'INSERT' THEN
         
     | 
| 
       67 
112 
     | 
    
         
             
                    ts := (to_jsonb(NEW.*) ->> ts_column)::timestamp with time zone;
         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                    IF detached_loggable_type IS NULL
         
     | 
| 
      
 115 
     | 
    
         
            +
                    THEN
         
     | 
| 
      
 116 
     | 
    
         
            +
                      log_data_ts_key_data = NEW.log_data #>> '{h,-1,ts}';
         
     | 
| 
      
 117 
     | 
    
         
            +
                    ELSE
         
     | 
| 
      
 118 
     | 
    
         
            +
                      log_data_ts_key_data = detached_log_data #>> '{h,-1,ts}';
         
     | 
| 
      
 119 
     | 
    
         
            +
                    END IF;
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                    IF ts IS NULL OR (extract(epoch from ts) * 1000)::bigint = log_data_ts_key_data::bigint THEN
         
     | 
| 
      
 122 
     | 
    
         
            +
                        ts := statement_timestamp();
         
     | 
| 
       70 
123 
     | 
    
         
             
                    END IF;
         
     | 
| 
       71 
124 
     | 
    
         
             
                  END IF;
         
     | 
| 
       72 
125 
     | 
    
         | 
| 
         @@ -123,7 +176,12 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       123 
176 
     | 
    
         
             
                    END;
         
     | 
| 
       124 
177 
     | 
    
         
             
                  END IF;
         
     | 
| 
       125 
178 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                   
     | 
| 
      
 179 
     | 
    
         
            +
                  -- We store `log_data` in a separate table for the `detached` mode
         
     | 
| 
      
 180 
     | 
    
         
            +
                  -- So we remove `log_data` only when we store historic data in the record's origin table
         
     | 
| 
      
 181 
     | 
    
         
            +
                  IF detached_loggable_type IS NULL
         
     | 
| 
      
 182 
     | 
    
         
            +
                  THEN
         
     | 
| 
      
 183 
     | 
    
         
            +
                      changes = changes - 'log_data';
         
     | 
| 
      
 184 
     | 
    
         
            +
                  END IF;
         
     | 
| 
       127 
185 
     | 
    
         | 
| 
       128 
186 
     | 
    
         
             
                  IF columns IS NOT NULL THEN
         
     | 
| 
       129 
187 
     | 
    
         
             
                    changes = logidze_filter_keys(changes, columns, include_columns);
         
     | 
| 
         @@ -170,7 +228,21 @@ CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$ 
     | 
|
| 
       170 
228 
     | 
    
         
             
                    log_data := logidze_compact_history(log_data, size - history_limit + 1);
         
     | 
| 
       171 
229 
     | 
    
         
             
                  END IF;
         
     | 
| 
       172 
230 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
                   
     | 
| 
      
 231 
     | 
    
         
            +
                  IF detached_loggable_type IS NULL
         
     | 
| 
      
 232 
     | 
    
         
            +
                  THEN
         
     | 
| 
      
 233 
     | 
    
         
            +
                    NEW.log_data := log_data;
         
     | 
| 
      
 234 
     | 
    
         
            +
                  ELSE
         
     | 
| 
      
 235 
     | 
    
         
            +
                    detached_log_data = log_data;
         
     | 
| 
      
 236 
     | 
    
         
            +
                    EXECUTE format(
         
     | 
| 
      
 237 
     | 
    
         
            +
                      'UPDATE %I ' ||
         
     | 
| 
      
 238 
     | 
    
         
            +
                      'SET log_data = $1 ' ||
         
     | 
| 
      
 239 
     | 
    
         
            +
                      'WHERE %I.loggable_type = $2 ' ||
         
     | 
| 
      
 240 
     | 
    
         
            +
                      'AND %I.loggable_id = $3',
         
     | 
| 
      
 241 
     | 
    
         
            +
                      log_data_table_name,
         
     | 
| 
      
 242 
     | 
    
         
            +
                      log_data_table_name,
         
     | 
| 
      
 243 
     | 
    
         
            +
                      log_data_table_name
         
     | 
| 
      
 244 
     | 
    
         
            +
                    ) USING detached_log_data, detached_loggable_type, NEW.id;
         
     | 
| 
      
 245 
     | 
    
         
            +
                  END IF;
         
     | 
| 
       174 
246 
     | 
    
         
             
                END IF;
         
     | 
| 
       175 
247 
     | 
    
         | 
| 
       176 
248 
     | 
    
         
             
                RETURN NEW; -- result
         
     | 
| 
         @@ -87,7 +87,17 @@ module Logidze 
     | 
|
| 
       87 
87 
     | 
    
         
             
                      source.sub!(/^CREATE OR REPLACE FUNCTION logidze_logger.*$/, "")
         
     | 
| 
       88 
88 
     | 
    
         
             
                      source.sub!(/^  -- version.*$/, "")
         
     | 
| 
       89 
89 
     | 
    
         
             
                      source.gsub!("RETURN NEW; -- pass", "RETURN NULL;")
         
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                      return_condition = <<~SQL
         
     | 
| 
      
 92 
     | 
    
         
            +
                        IF detached_loggable_type IS NULL
         
     | 
| 
      
 93 
     | 
    
         
            +
                            THEN
         
     | 
| 
      
 94 
     | 
    
         
            +
                              EXECUTE format('UPDATE %I.%I SET "log_data" = $1 WHERE ctid = %L', TG_TABLE_SCHEMA, TG_TABLE_NAME, NEW.CTID) USING NEW.log_data;
         
     | 
| 
      
 95 
     | 
    
         
            +
                            END IF;
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                            RETURN NULL;
         
     | 
| 
      
 98 
     | 
    
         
            +
                      SQL
         
     | 
| 
      
 99 
     | 
    
         
            +
                      source.gsub!("RETURN NEW; -- result", return_condition)
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
       91 
101 
     | 
    
         
             
                      source
         
     | 
| 
       92 
102 
     | 
    
         
             
                    end
         
     | 
| 
       93 
103 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -0,0 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rails/generators"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "rails/generators/active_record"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Logidze
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Generators
         
     | 
| 
      
 8 
     | 
    
         
            +
                module Migration
         
     | 
| 
      
 9 
     | 
    
         
            +
                  class LogsGenerator < Rails::Generators::Base
         
     | 
| 
      
 10 
     | 
    
         
            +
                    include Rails::Generators::Migration
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    source_root File.expand_path("templates", __dir__)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                    def generate_migration
         
     | 
| 
      
 15 
     | 
    
         
            +
                      migration_template "migration.rb.erb", "db/migrate/create_logidze_data.rb"
         
     | 
| 
      
 16 
     | 
    
         
            +
                    end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                    def self.next_migration_number(dir)
         
     | 
| 
      
 19 
     | 
    
         
            +
                      ::ActiveRecord::Generators::Base.next_migration_number(dir)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class <%= @migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
         
     | 
| 
      
 4 
     | 
    
         
            +
              def change
         
     | 
| 
      
 5 
     | 
    
         
            +
                create_table :logidze_data do |t|
         
     | 
| 
      
 6 
     | 
    
         
            +
                  t.jsonb :log_data
         
     | 
| 
      
 7 
     | 
    
         
            +
                  t.belongs_to :loggable, polymorphic: true, index: {name: "index_logidze_loggable", unique: true}
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -11,3 +11,7 @@ Examples: 
     | 
|
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                This will generate the migration to update existing trigger (drop and create).
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
      
 14 
     | 
    
         
            +
              rails generate logidze:Model User --detached
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                This will generate migration to add trigger for the <User> model.
         
     | 
| 
      
 17 
     | 
    
         
            +
                This will also add `has_logidze detached: true` to the model.
         
     | 
| 
         @@ -14,6 +14,9 @@ module Logidze 
     | 
|
| 
       14 
14 
     | 
    
         
             
                  source_root File.expand_path("templates", __dir__)
         
     | 
| 
       15 
15 
     | 
    
         
             
                  source_paths << File.expand_path("triggers", __dir__)
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
      
 17 
     | 
    
         
            +
                  class_option :detached, type: :boolean, optional: true,
         
     | 
| 
      
 18 
     | 
    
         
            +
                    desc: "Store history data in a separate table"
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
       17 
20 
     | 
    
         
             
                  class_option :limit, type: :numeric, optional: true, desc: "Specify history size limit"
         
     | 
| 
       18 
21 
     | 
    
         | 
| 
       19 
22 
     | 
    
         
             
                  class_option :debounce_time, type: :numeric, optional: true,
         
     | 
| 
         @@ -60,8 +63,11 @@ module Logidze 
     | 
|
| 
       60 
63 
     | 
    
         
             
                    return if update?
         
     | 
| 
       61 
64 
     | 
    
         | 
| 
       62 
65 
     | 
    
         
             
                    indents = "  " * (class_name.scan("::").count + 1)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    macros_name = detached? ? "has_logidze detached: true\n" : "has_logidze\n"
         
     | 
| 
       63 
67 
     | 
    
         | 
| 
       64 
     | 
    
         
            -
                     
     | 
| 
      
 68 
     | 
    
         
            +
                    if File.readlines("#{destination_root}/#{model_file_path}").grep(/has_logidze/).empty?
         
     | 
| 
      
 69 
     | 
    
         
            +
                      inject_into_class(model_file_path, class_name.demodulize, indents + macros_name)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
       65 
71 
     | 
    
         
             
                  end
         
     | 
| 
       66 
72 
     | 
    
         | 
| 
       67 
73 
     | 
    
         
             
                  no_tasks do
         
     | 
| 
         @@ -80,6 +86,10 @@ module Logidze 
     | 
|
| 
       80 
86 
     | 
    
         
             
                      "#{config.table_name_prefix}#{table_name}#{config.table_name_suffix}"
         
     | 
| 
       81 
87 
     | 
    
         
             
                    end
         
     | 
| 
       82 
88 
     | 
    
         | 
| 
      
 89 
     | 
    
         
            +
                    def detached_loggable_type
         
     | 
| 
      
 90 
     | 
    
         
            +
                      escape_pgsql_string(class_name) if detached?
         
     | 
| 
      
 91 
     | 
    
         
            +
                    end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
       83 
93 
     | 
    
         
             
                    def limit
         
     | 
| 
       84 
94 
     | 
    
         
             
                      options[:limit]
         
     | 
| 
       85 
95 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -88,6 +98,10 @@ module Logidze 
     | 
|
| 
       88 
98 
     | 
    
         
             
                      options[:backfill]
         
     | 
| 
       89 
99 
     | 
    
         
             
                    end
         
     | 
| 
       90 
100 
     | 
    
         | 
| 
      
 101 
     | 
    
         
            +
                    def detached?
         
     | 
| 
      
 102 
     | 
    
         
            +
                      options[:detached] || Logidze.detached_log_placement?
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
       91 
105 
     | 
    
         
             
                    def only_trigger?
         
     | 
| 
       92 
106 
     | 
    
         
             
                      options[:only_trigger]
         
     | 
| 
       93 
107 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -116,6 +130,10 @@ module Logidze 
     | 
|
| 
       116 
130 
     | 
    
         
             
                      escape_pgsql_string(value)
         
     | 
| 
       117 
131 
     | 
    
         
             
                    end
         
     | 
| 
       118 
132 
     | 
    
         | 
| 
      
 133 
     | 
    
         
            +
                    def quoted_log_data_table_name
         
     | 
| 
      
 134 
     | 
    
         
            +
                      Logidze::LogidzeData.quoted_table_name if detached?
         
     | 
| 
      
 135 
     | 
    
         
            +
                    end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
       119 
137 
     | 
    
         
             
                    def debounce_time
         
     | 
| 
       120 
138 
     | 
    
         
             
                      options[:debounce_time]
         
     | 
| 
       121 
139 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -149,7 +167,8 @@ module Logidze 
     | 
|
| 
       149 
167 
     | 
    
         
             
                    end
         
     | 
| 
       150 
168 
     | 
    
         | 
| 
       151 
169 
     | 
    
         
             
                    def logidze_logger_parameters
         
     | 
| 
       152 
     | 
    
         
            -
                      format_pgsql_args(limit, timestamp_column, filtered_columns, include_columns, debounce_time 
     | 
| 
      
 170 
     | 
    
         
            +
                      format_pgsql_args(limit, timestamp_column, filtered_columns, include_columns, debounce_time,
         
     | 
| 
      
 171 
     | 
    
         
            +
                        detached_loggable_type, quoted_log_data_table_name)
         
     | 
| 
       153 
172 
     | 
    
         
             
                    end
         
     | 
| 
       154 
173 
     | 
    
         | 
| 
       155 
174 
     | 
    
         
             
                    def logidze_snapshot_parameters
         
     | 
| 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            class <%= @migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
         
     | 
| 
       2 
2 
     | 
    
         
             
              def change
         
     | 
| 
       3 
     | 
    
         
            -
            <%- unless update? || only_trigger? -%>
         
     | 
| 
      
 3 
     | 
    
         
            +
            <%- unless update? || only_trigger? || detached? -%>
         
     | 
| 
       4 
4 
     | 
    
         
             
                add_column :<%= table_name %>, :log_data, :jsonb
         
     | 
| 
       5 
5 
     | 
    
         
             
            <%- end -%>
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
         @@ -58,13 +58,23 @@ class <%= @migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::M 
     | 
|
| 
       58 
58 
     | 
    
         
             
                end
         
     | 
| 
       59 
59 
     | 
    
         
             
            <%- end -%>
         
     | 
| 
       60 
60 
     | 
    
         
             
            <%- if backfill? -%>
         
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
61 
     | 
    
         
             
                reversible do |dir|
         
     | 
| 
       63 
62 
     | 
    
         
             
                  dir.up do
         
     | 
| 
       64 
     | 
    
         
            -
                     
     | 
| 
       65 
     | 
    
         
            -
                       
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 63 
     | 
    
         
            +
                    <%- if detached?  %>
         
     | 
| 
      
 64 
     | 
    
         
            +
                      execute <<~SQL
         
     | 
| 
      
 65 
     | 
    
         
            +
                        INSERT INTO <%= quoted_log_data_table_name %> (log_data, loggable_type, loggable_id)
         
     | 
| 
      
 66 
     | 
    
         
            +
                        SELECT logidze_snapshot(<%= logidze_snapshot_parameters %>), <%= detached_loggable_type %>, t.id
         
     | 
| 
      
 67 
     | 
    
         
            +
                        FROM "<%= full_table_name %>" t
         
     | 
| 
      
 68 
     | 
    
         
            +
                        ON CONFLICT (loggable_type, loggable_id)
         
     | 
| 
      
 69 
     | 
    
         
            +
                        DO UPDATE
         
     | 
| 
      
 70 
     | 
    
         
            +
                        SET log_data = EXCLUDED.log_data;
         
     | 
| 
      
 71 
     | 
    
         
            +
                      SQL
         
     | 
| 
      
 72 
     | 
    
         
            +
                    <%- else %>
         
     | 
| 
      
 73 
     | 
    
         
            +
                      execute <<~SQL
         
     | 
| 
      
 74 
     | 
    
         
            +
                        UPDATE "<%= full_table_name %>" as t
         
     | 
| 
      
 75 
     | 
    
         
            +
                        SET log_data = logidze_snapshot(<%= logidze_snapshot_parameters %>);
         
     | 
| 
      
 76 
     | 
    
         
            +
                      SQL
         
     | 
| 
      
 77 
     | 
    
         
            +
                    <%- end %>
         
     | 
| 
       68 
78 
     | 
    
         
             
                  end
         
     | 
| 
       69 
79 
     | 
    
         
             
                end
         
     | 
| 
       70 
80 
     | 
    
         
             
            <%- end -%>
         
     | 
| 
         @@ -2,5 +2,5 @@ CREATE TRIGGER <%= %Q("logidze_on_#{full_table_name}") %> 
     | 
|
| 
       2 
2 
     | 
    
         
             
            BEFORE UPDATE OR INSERT ON <%= %Q("#{full_table_name}") %> FOR EACH ROW
         
     | 
| 
       3 
3 
     | 
    
         
             
            WHEN (coalesce(current_setting('logidze.disabled', true), '') <> 'on')
         
     | 
| 
       4 
4 
     | 
    
         
             
            -- Parameters: history_size_limit (integer), timestamp_column (text), filtered_columns (text[]),
         
     | 
| 
       5 
     | 
    
         
            -
            -- include_columns (boolean), debounce_time_ms (integer)
         
     | 
| 
      
 5 
     | 
    
         
            +
            -- include_columns (boolean), debounce_time_ms (integer), detached_loggable_type(text), log_data_table_name(text)
         
     | 
| 
       6 
6 
     | 
    
         
             
            EXECUTE PROCEDURE logidze_logger(<%= logidze_logger_parameters %>);
         
     | 
| 
         @@ -2,5 +2,5 @@ CREATE TRIGGER <%= %Q("logidze_on_#{full_table_name}") %> 
     | 
|
| 
       2 
2 
     | 
    
         
             
            AFTER UPDATE OR INSERT ON <%= %Q("#{full_table_name}") %> FOR EACH ROW
         
     | 
| 
       3 
3 
     | 
    
         
             
            WHEN (coalesce(current_setting('logidze.disabled', true), '') <> 'on' AND pg_trigger_depth() < 1)
         
     | 
| 
       4 
4 
     | 
    
         
             
            -- Parameters: history_size_limit (integer), timestamp_column (text), filtered_columns (text[]),
         
     | 
| 
       5 
     | 
    
         
            -
            -- include_columns (boolean), debounce_time_ms (integer)
         
     | 
| 
      
 5 
     | 
    
         
            +
            -- include_columns (boolean), debounce_time_ms (integer), detached_loggable_type(text), log_data_table_name(text)
         
     | 
| 
       6 
6 
     | 
    
         
             
            EXECUTE PROCEDURE logidze_logger_after(<%= logidze_logger_parameters %>);
         
     | 
| 
         @@ -0,0 +1,87 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Logidze
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Detachable
         
     | 
| 
      
 5 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                included do
         
     | 
| 
      
 8 
     | 
    
         
            +
                  has_one :logidze_data, as: :loggable, class_name: "::Logidze::LogidzeData", dependent: :destroy, autosave: true
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  delegate :log_data, to: :logidze_data, allow_nil: true
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                module ClassMethods # :nodoc:
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # Nullify log_data column for a association
         
     | 
| 
      
 15 
     | 
    
         
            +
                  #
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # @return [Integer] number of deleted +Logidze::LogidzeData+ records
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def reset_log_data
         
     | 
| 
      
 18 
     | 
    
         
            +
                    Logidze::LogidzeData.where(loggable_id: ids, loggable_type: name).delete_all
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  # Initialize log_data with the current state if it's null
         
     | 
| 
      
 22 
     | 
    
         
            +
                  def create_logidze_snapshot(timestamp: nil, only: nil, except: nil, sql_filter: nil)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    ActiveRecord::Base.connection.execute <<~SQL.squish
         
     | 
| 
      
 24 
     | 
    
         
            +
                      INSERT INTO #{Logidze::LogidzeData.quoted_table_name} (log_data, loggable_type, loggable_id)
         
     | 
| 
      
 25 
     | 
    
         
            +
                      SELECT logidze_snapshot(
         
     | 
| 
      
 26 
     | 
    
         
            +
                          to_jsonb(#{quoted_table_name}),
         
     | 
| 
      
 27 
     | 
    
         
            +
                          #{snapshot_query_args(timestamp: timestamp, only: only, except: except)}
         
     | 
| 
      
 28 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 29 
     | 
    
         
            +
                        '#{name}',
         
     | 
| 
      
 30 
     | 
    
         
            +
                        #{quoted_table_name}.id
         
     | 
| 
      
 31 
     | 
    
         
            +
                      FROM #{quoted_table_name}
         
     | 
| 
      
 32 
     | 
    
         
            +
                      #{sql_filter}
         
     | 
| 
      
 33 
     | 
    
         
            +
                      ON CONFLICT (loggable_type, loggable_id)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      DO UPDATE
         
     | 
| 
      
 35 
     | 
    
         
            +
                      SET log_data = EXCLUDED.log_data;
         
     | 
| 
      
 36 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  private
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def initial_scope
         
     | 
| 
      
 42 
     | 
    
         
            +
                    includes(:logidze_data)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                # Loads log_data field from the database, stores to the attributes hash and returns it
         
     | 
| 
      
 47 
     | 
    
         
            +
                def reload_log_data
         
     | 
| 
      
 48 
     | 
    
         
            +
                  reload_logidze_data.log_data
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                # Nullify log_data column for a single record
         
     | 
| 
      
 52 
     | 
    
         
            +
                def reset_log_data
         
     | 
| 
      
 53 
     | 
    
         
            +
                  tap { logidze_data.delete }.reload_logidze_data
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                # Initialize log_data with the current state if it's null for a single record
         
     | 
| 
      
 57 
     | 
    
         
            +
                def create_logidze_snapshot!(timestamp: nil, only: nil, except: nil)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  id_filter = "WHERE #{self.class.quoted_table_name}.id = #{id}"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  self.class.create_logidze_snapshot(timestamp: timestamp, only: only, except: except, sql_filter: id_filter)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  reload_log_data
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def raw_log_data
         
     | 
| 
      
 65 
     | 
    
         
            +
                  logidze_data&.read_attribute_before_type_cast(:log_data)
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def log_data=(v)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  logidze_data&.assign_attributes(log_data: v) || build_logidze_data(log_data: v)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  v # rubocop:disable Lint/Void
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def dup
         
     | 
| 
      
 74 
     | 
    
         
            +
                  super.tap { _1.logidze_data = logidze_data.dup }
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                protected
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                # rubocop: disable Lint/ShadowedArgument
         
     | 
| 
      
 80 
     | 
    
         
            +
                def build_dup(log_entry, requested_ts = log_entry.time, object_at: nil)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  object_at = dup
         
     | 
| 
      
 82 
     | 
    
         
            +
                  object_at.logidze_data = logidze_data.dup
         
     | 
| 
      
 83 
     | 
    
         
            +
                  super
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
                # rubocop: enable Lint/ShadowedArgument
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/logidze/has_logidze.rb
    CHANGED
    
    | 
         @@ -9,10 +9,14 @@ module Logidze 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
                module ClassMethods # :nodoc:
         
     | 
| 
       11 
11 
     | 
    
         
             
                  # Include methods to work with history.
         
     | 
| 
       12 
     | 
    
         
            -
                   
     | 
| 
       13 
     | 
    
         
            -
                  def has_logidze(ignore_log_data: Logidze.ignore_log_data_by_default)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  def has_logidze(ignore_log_data: Logidze.ignore_log_data_by_default, detached: Logidze.detached_log_placement?)
         
     | 
| 
       14 
13 
     | 
    
         
             
                    include Logidze::IgnoreLogData
         
     | 
| 
       15 
14 
     | 
    
         
             
                    include Logidze::Model
         
     | 
| 
      
 15 
     | 
    
         
            +
                    if detached && !Logidze.inline_log_placement?
         
     | 
| 
      
 16 
     | 
    
         
            +
                      # Adds needed behavior to models and alters behavior of some methods from +Logidze::Model+ to
         
     | 
| 
      
 17 
     | 
    
         
            +
                      # work with detached table for `log_data`
         
     | 
| 
      
 18 
     | 
    
         
            +
                      include Logidze::Detachable
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
       16 
20 
     | 
    
         | 
| 
       17 
21 
     | 
    
         
             
                    @ignore_log_data = ignore_log_data
         
     | 
| 
       18 
22 
     | 
    
         | 
    
        data/lib/logidze/history/type.rb
    CHANGED
    
    
    
        data/lib/logidze/model.rb
    CHANGED
    
    | 
         @@ -18,12 +18,12 @@ module Logidze 
     | 
|
| 
       18 
18 
     | 
    
         
             
                module ClassMethods # :nodoc:
         
     | 
| 
       19 
19 
     | 
    
         
             
                  # Return records reverted to specified time
         
     | 
| 
       20 
20 
     | 
    
         
             
                  def at(time: nil, version: nil)
         
     | 
| 
       21 
     | 
    
         
            -
                    all.to_a.filter_map { |record| record.at(time: time, version: version) }
         
     | 
| 
      
 21 
     | 
    
         
            +
                    initial_scope.all.to_a.filter_map { |record| record.at(time: time, version: version) }
         
     | 
| 
       22 
22 
     | 
    
         
             
                  end
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
                  # Return changes made to records since specified time
         
     | 
| 
       25 
25 
     | 
    
         
             
                  def diff_from(time: nil, version: nil)
         
     | 
| 
       26 
     | 
    
         
            -
                    all.map { |record| record.diff_from(time: time, version: version) }
         
     | 
| 
      
 26 
     | 
    
         
            +
                    initial_scope.all.map { |record| record.diff_from(time: time, version: version) }
         
     | 
| 
       27 
27 
     | 
    
         
             
                  end
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         
             
                  # Alias for Logidze.without_logging
         
     | 
| 
         @@ -44,6 +44,26 @@ module Logidze 
     | 
|
| 
       44 
44 
     | 
    
         | 
| 
       45 
45 
     | 
    
         
             
                  # Initialize log_data with the current state if it's null
         
     | 
| 
       46 
46 
     | 
    
         
             
                  def create_logidze_snapshot(timestamp: nil, only: nil, except: nil)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    without_logging do
         
     | 
| 
      
 48 
     | 
    
         
            +
                      where(log_data: nil).update_all(
         
     | 
| 
      
 49 
     | 
    
         
            +
                        <<~SQL.squish
         
     | 
| 
      
 50 
     | 
    
         
            +
                          log_data = logidze_snapshot(
         
     | 
| 
      
 51 
     | 
    
         
            +
                            to_jsonb(#{quoted_table_name}),
         
     | 
| 
      
 52 
     | 
    
         
            +
                            #{snapshot_query_args(timestamp: timestamp, only: only, except: except)}
         
     | 
| 
      
 53 
     | 
    
         
            +
                          )
         
     | 
| 
      
 54 
     | 
    
         
            +
                        SQL
         
     | 
| 
      
 55 
     | 
    
         
            +
                      )
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  private
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def initial_scope
         
     | 
| 
      
 62 
     | 
    
         
            +
                    all
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  # Computes args for creating initializing snapshots in +.create_logidze_snapshot+ and +#create_logidze_snapshot!+
         
     | 
| 
      
 66 
     | 
    
         
            +
                  def snapshot_query_args(timestamp: nil, only: nil, except: nil)
         
     | 
| 
       47 
67 
     | 
    
         
             
                    args = ["'null'"]
         
     | 
| 
       48 
68 
     | 
    
         | 
| 
       49 
69 
     | 
    
         
             
                    args[0] = "'#{timestamp}'" if timestamp
         
     | 
| 
         @@ -55,13 +75,7 @@ module Logidze 
     | 
|
| 
       55 
75 
     | 
    
         
             
                      args[2] = only ? "true" : "false"
         
     | 
| 
       56 
76 
     | 
    
         
             
                    end
         
     | 
| 
       57 
77 
     | 
    
         | 
| 
       58 
     | 
    
         
            -
                     
     | 
| 
       59 
     | 
    
         
            -
                      where(log_data: nil).update_all(
         
     | 
| 
       60 
     | 
    
         
            -
                        <<~SQL
         
     | 
| 
       61 
     | 
    
         
            -
                          log_data = logidze_snapshot(to_jsonb(#{quoted_table_name}), #{args.join(", ")})
         
     | 
| 
       62 
     | 
    
         
            -
                        SQL
         
     | 
| 
       63 
     | 
    
         
            -
                      )
         
     | 
| 
       64 
     | 
    
         
            -
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                    args.join(", ")
         
     | 
| 
       65 
79 
     | 
    
         
             
                  end
         
     | 
| 
       66 
80 
     | 
    
         
             
                end
         
     | 
| 
       67 
81 
     | 
    
         | 
| 
         @@ -244,6 +258,10 @@ module Logidze 
     | 
|
| 
       244 
258 
     | 
    
         
             
                  reload_log_data
         
     | 
| 
       245 
259 
     | 
    
         
             
                end
         
     | 
| 
       246 
260 
     | 
    
         | 
| 
      
 261 
     | 
    
         
            +
                def raw_log_data
         
     | 
| 
      
 262 
     | 
    
         
            +
                  read_attribute_before_type_cast(:log_data)
         
     | 
| 
      
 263 
     | 
    
         
            +
                end
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
       247 
265 
     | 
    
         
             
                protected
         
     | 
| 
       248 
266 
     | 
    
         | 
| 
       249 
267 
     | 
    
         
             
                def apply_diff(version, diff)
         
     | 
| 
         @@ -261,14 +279,16 @@ module Logidze 
     | 
|
| 
       261 
279 
     | 
    
         
             
                  write_attribute column, deserialize_value(column, value)
         
     | 
| 
       262 
280 
     | 
    
         
             
                end
         
     | 
| 
       263 
281 
     | 
    
         | 
| 
       264 
     | 
    
         
            -
                 
     | 
| 
       265 
     | 
    
         
            -
             
     | 
| 
      
 282 
     | 
    
         
            +
                # rubocop: disable Lint/ShadowedArgument
         
     | 
| 
      
 283 
     | 
    
         
            +
                def build_dup(log_entry, requested_ts = log_entry.time, object_at: nil)
         
     | 
| 
      
 284 
     | 
    
         
            +
                  object_at ||= dup
         
     | 
| 
       266 
285 
     | 
    
         
             
                  object_at.apply_diff(log_entry.version, log_data.changes_to(version: log_entry.version))
         
     | 
| 
       267 
286 
     | 
    
         
             
                  object_at.id = id
         
     | 
| 
       268 
287 
     | 
    
         
             
                  object_at.logidze_requested_ts = requested_ts
         
     | 
| 
       269 
288 
     | 
    
         | 
| 
       270 
289 
     | 
    
         
             
                  object_at
         
     | 
| 
       271 
290 
     | 
    
         
             
                end
         
     | 
| 
      
 291 
     | 
    
         
            +
                # rubocop: enable Lint/ShadowedArgument
         
     | 
| 
       272 
292 
     | 
    
         | 
| 
       273 
293 
     | 
    
         
             
                def deserialize_value(column, value)
         
     | 
| 
       274 
294 
     | 
    
         
             
                  @attributes[column].type.deserialize(value)
         
     | 
    
        data/lib/logidze/version.rb
    CHANGED
    
    
    
        data/lib/logidze.rb
    CHANGED
    
    | 
         @@ -11,6 +11,7 @@ module Logidze 
     | 
|
| 
       11 
11 
     | 
    
         
             
              require "logidze/ignore_log_data"
         
     | 
| 
       12 
12 
     | 
    
         
             
              require "logidze/has_logidze"
         
     | 
| 
       13 
13 
     | 
    
         
             
              require "logidze/meta"
         
     | 
| 
      
 14 
     | 
    
         
            +
              require "logidze/detachable"
         
     | 
| 
       14 
15 
     | 
    
         | 
| 
       15 
16 
     | 
    
         
             
              extend Logidze::Meta
         
     | 
| 
       16 
17 
     | 
    
         | 
| 
         @@ -29,6 +30,12 @@ module Logidze 
     | 
|
| 
       29 
30 
     | 
    
         
             
                attr_accessor :sort_triggers_by_name
         
     | 
| 
       30 
31 
     | 
    
         
             
                # Determines what Logidze should do when upgrade is needed (:raise | :warn | :ignore)
         
     | 
| 
       31 
32 
     | 
    
         
             
                attr_reader :on_pending_upgrade
         
     | 
| 
      
 33 
     | 
    
         
            +
                # Determines where to store +log_data+:
         
     | 
| 
      
 34 
     | 
    
         
            +
                # - +:inline+ - force Logidze to store it in the origin table in the +log_data+ column
         
     | 
| 
      
 35 
     | 
    
         
            +
                # - +:detached+ - force Logidze to  store it in the +logidze_data+ table in the +log_data+ column
         
     | 
| 
      
 36 
     | 
    
         
            +
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                # By default we do not set +log_data_placement+ value and rely on `has_logidze` macros
         
     | 
| 
      
 38 
     | 
    
         
            +
                attr_accessor :log_data_placement
         
     | 
| 
       32 
39 
     | 
    
         | 
| 
       33 
40 
     | 
    
         
             
                # Temporary disable DB triggers.
         
     | 
| 
       34 
41 
     | 
    
         
             
                #
         
     | 
| 
         @@ -53,6 +60,14 @@ module Logidze 
     | 
|
| 
       53 
60 
     | 
    
         
             
                  @on_pending_upgrade = mode
         
     | 
| 
       54 
61 
     | 
    
         
             
                end
         
     | 
| 
       55 
62 
     | 
    
         | 
| 
      
 63 
     | 
    
         
            +
                def detached_log_placement?
         
     | 
| 
      
 64 
     | 
    
         
            +
                  @log_data_placement == :detached
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def inline_log_placement?
         
     | 
| 
      
 68 
     | 
    
         
            +
                  @log_data_placement == :inline
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
       56 
71 
     | 
    
         
             
                private
         
     | 
| 
       57 
72 
     | 
    
         | 
| 
       58 
73 
     | 
    
         
             
                def with_logidze_setting(name, value)
         
     | 
| 
         @@ -71,4 +86,5 @@ module Logidze 
     | 
|
| 
       71 
86 
     | 
    
         
             
              self.return_self_if_log_data_is_empty = true
         
     | 
| 
       72 
87 
     | 
    
         
             
              self.on_pending_upgrade = :ignore
         
     | 
| 
       73 
88 
     | 
    
         
             
              self.sort_triggers_by_name = false
         
     | 
| 
      
 89 
     | 
    
         
            +
              self.log_data_placement = nil
         
     | 
| 
       74 
90 
     | 
    
         
             
            end
         
     | 
    
        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.4.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - palkan
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire: 
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2025-05-09 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: railties
         
     | 
| 
         @@ -136,6 +136,20 @@ dependencies: 
     | 
|
| 
       136 
136 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       137 
137 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       138 
138 
     | 
    
         
             
                    version: '0.8'
         
     | 
| 
      
 139 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 140 
     | 
    
         
            +
              name: concurrent-ruby
         
     | 
| 
      
 141 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 142 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 143 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 144 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 145 
     | 
    
         
            +
                    version: 1.3.4
         
     | 
| 
      
 146 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 147 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 148 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 149 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 150 
     | 
    
         
            +
                - - '='
         
     | 
| 
      
 151 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 152 
     | 
    
         
            +
                    version: 1.3.4
         
     | 
| 
       139 
153 
     | 
    
         
             
            description: PostgreSQL JSONB-based model changes tracking
         
     | 
| 
       140 
154 
     | 
    
         
             
            email:
         
     | 
| 
       141 
155 
     | 
    
         
             
            - dementiev.vm@gmail.com
         
     | 
| 
         @@ -160,12 +174,16 @@ files: 
     | 
|
| 
       160 
174 
     | 
    
         
             
            - lib/generators/logidze/install/templates/hstore.rb.erb
         
     | 
| 
       161 
175 
     | 
    
         
             
            - lib/generators/logidze/install/templates/migration.rb.erb
         
     | 
| 
       162 
176 
     | 
    
         
             
            - lib/generators/logidze/install/templates/migration_fx.rb.erb
         
     | 
| 
      
 177 
     | 
    
         
            +
            - lib/generators/logidze/migration/USAGE
         
     | 
| 
      
 178 
     | 
    
         
            +
            - lib/generators/logidze/migration/logs_generator.rb
         
     | 
| 
      
 179 
     | 
    
         
            +
            - lib/generators/logidze/migration/templates/migration.rb.erb
         
     | 
| 
       163 
180 
     | 
    
         
             
            - lib/generators/logidze/model/USAGE
         
     | 
| 
       164 
181 
     | 
    
         
             
            - lib/generators/logidze/model/model_generator.rb
         
     | 
| 
       165 
182 
     | 
    
         
             
            - lib/generators/logidze/model/templates/migration.rb.erb
         
     | 
| 
       166 
183 
     | 
    
         
             
            - lib/generators/logidze/model/triggers/logidze.sql
         
     | 
| 
       167 
184 
     | 
    
         
             
            - lib/generators/logidze/model/triggers/logidze_after.sql
         
     | 
| 
       168 
185 
     | 
    
         
             
            - lib/logidze.rb
         
     | 
| 
      
 186 
     | 
    
         
            +
            - lib/logidze/detachable.rb
         
     | 
| 
       169 
187 
     | 
    
         
             
            - lib/logidze/engine.rb
         
     | 
| 
       170 
188 
     | 
    
         
             
            - lib/logidze/has_logidze.rb
         
     | 
| 
       171 
189 
     | 
    
         
             
            - lib/logidze/history.rb
         
     | 
| 
         @@ -188,7 +206,7 @@ metadata: 
     | 
|
| 
       188 
206 
     | 
    
         
             
              documentation_uri: http://github.com/palkan/logidze
         
     | 
| 
       189 
207 
     | 
    
         
             
              homepage_uri: http://github.com/palkan/logidze
         
     | 
| 
       190 
208 
     | 
    
         
             
              source_code_uri: http://github.com/palkan/logidze
         
     | 
| 
       191 
     | 
    
         
            -
            post_install_message: 
     | 
| 
      
 209 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
       192 
210 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       193 
211 
     | 
    
         
             
            require_paths:
         
     | 
| 
       194 
212 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -204,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       204 
222 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       205 
223 
     | 
    
         
             
            requirements: []
         
     | 
| 
       206 
224 
     | 
    
         
             
            rubygems_version: 3.4.19
         
     | 
| 
       207 
     | 
    
         
            -
            signing_key: 
     | 
| 
      
 225 
     | 
    
         
            +
            signing_key:
         
     | 
| 
       208 
226 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       209 
227 
     | 
    
         
             
            summary: PostgreSQL JSONB-based model changes tracking
         
     | 
| 
       210 
228 
     | 
    
         
             
            test_files: []
         
     |