### Setup Rails App for Benchmarks Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Installs dependencies and migrates the database for the example Rails application used in performance benchmarks. ```shell bundle install bundle exec rails db:migrate ``` -------------------------------- ### Install Logidze Database Components Source: https://github.com/palkan/logidze/blob/master/_autodocs/configuration.md Use `rails generate logidze:install` for initial setup or to create an update migration. Options include `--update` for migrations and `--fx` or `--no-fx` to control the use of the fx gem. ```bash # Initial setup rails generate logidze:install ``` ```bash # Update to latest version rails generate logidze:install --update ``` ```bash # Use fx gem explicitly rails generate logidze:install --fx ``` -------------------------------- ### Minimal Model Setup with has_logidze Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Use this minimal setup for basic history tracking with all default configurations. ```ruby class Post < ApplicationRecord has_logidze end ``` -------------------------------- ### Install Logidze Gem Source: https://github.com/palkan/logidze/blob/master/_autodocs/README.md Add the gem to your Gemfile, run bundle install, and generate/migrate the necessary database tables. ```ruby # Gemfile gem "logidze", "~> 1.4" # Install bundle install rails generate logidze:install rails db:migrate ``` -------------------------------- ### Setup Database for Benchmarks Source: https://github.com/palkan/logidze/blob/master/bench/triggers/Readme.md Run this command to set up the database required for the benchmarks. This is only necessary if not using Dip. ```sh make setup ``` -------------------------------- ### Install Logidze DB Extensions and Trigger Function Source: https://github.com/palkan/logidze/blob/master/README.md Run the Logidze generator to install the necessary database extensions and create the trigger function. ```bash bundle exec rails generate logidze:install ``` -------------------------------- ### Complete Logidze Implementation Example Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md A full example demonstrating the integration of Logidze in a Rails application, including model configuration, initializer settings, controller logic for updates and history retrieval, and a view for displaying version history. ```ruby # app/models/post.rb class Post < ApplicationRecord # Enable Logidze with custom settings has_logidze ignore_log_data: true, detached: false # Now add your other associations and validations has_many :comments validates :title, presence: true end ``` ```ruby # config/initializers/logidze.rb Logidze.append_on_undo = true Logidze.ignore_log_data_by_default = false Logidze.associations_versioning = true ``` ```ruby # Controller using Logidze class PostsController < ApplicationController def update post = Post.find(params[:id]) Logidze.with_responsible(current_user.id) do post.update!(post_params) end redirect_to post end def history post = Post.find(params[:id]) @versions = post.logidze_versions.take @changes = post.diff_from(time: 7.days.ago) end end ``` ```ruby # View showing history <% @versions.each do |version| %> <% end %>
Version Time Changes By
<%= version.log_version %> <%= Time.at(version.log_data.versions.find { |v| v.version == version.log_version }.time / 1000.0) %> <%= version.title %> <%= User.find(version.log_data.responsible_id) if version.log_data.responsible_id %>
``` -------------------------------- ### Setup PostgreSQL for Testing Source: https://github.com/palkan/logidze/blob/master/README.md Create a PostgreSQL database for testing the Logidze project. Ensure your PostgreSQL server is running and accessible. ```sh createdb -h postgres -U postgres logidze_test ``` -------------------------------- ### Setup PostgreSQL for Benchmarking Source: https://github.com/palkan/logidze/blob/master/README.md Create PostgreSQL databases required for running performance benchmarks for the Logidze project. This includes setting up the hstore extension. ```sh createdb -h postgres -U postgres logidze_bench createdb -h postgres -U postgres logidze_perf_bench psql -d logidze_bench -c 'CREATE EXTENSION IF NOT EXISTS hstore;' ``` -------------------------------- ### LogidzeData Lifecycle Example Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/logidze-data-model.md Demonstrates the lifecycle of a LogidzeData record associated with a Post model, including creation, updates, deletion, and clearing. ```ruby post = Post.create!(title: "Initial") post.logidze_data # => Logidze::LogidzeData with first snapshot post.update!(title: "Updated") post.logidze_data.created_at # Still the original created_at post.logidze_data.updated_at # Updated by trigger post.destroy post.logidze_data # => raises AssociationError (was deleted) # Or via reset_log_data post.reset_log_data post.logidze_data # => nil ``` -------------------------------- ### Custom Model Setup with has_logidze Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Configure has_logidze with custom options like ignoring log data or enabling detached history. ```ruby class Post < ApplicationRecord has_logidze ignore_log_data: true, detached: true end ``` -------------------------------- ### JSON Structure for Metadata Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/meta-tracking.md Illustrates the 'm' (meta) key within version entries, showing empty and populated metadata examples. ```javascript { "v": 3, "h": [ { "v": 1, "ts": 1687000100000, "c": { "title": "Original" }, "m": {} }, { "v": 2, "ts": 1687000200000, "c": { "title": "Updated" }, "m": { "_r": 42, "ip": "192.168.1.1", "request_id": "abc-123", "job": "PostPublishJob" } } ] } ``` -------------------------------- ### Example of Associations Versioning Usage Source: https://github.com/palkan/logidze/wiki/Associations-versioning Demonstrates creating a post and its comment, then retrieving historical states of the post to inspect its associated comments at different times. Note the use of `length` over `size` for associations. ```ruby # 2017-01-19 post = Post.create!(post_params) # 2017-01-22 comment = post.comments.create(body: 'My comment') # 2017-01-24 comment.update!(body: 'New text') # at this time comment wasn't updated yet... old_post = post.at('2017-01-23') # ...so we see its original body, although post itself wasn't changed old_post.comments.first.body #=> 'My comment' # at this time post didn't have comments at all very_old_post = post.at(time: '2017-01-20') very_old_post.comments.length #=> 0 ``` -------------------------------- ### Generate Logidze Install Migration Source: https://github.com/palkan/logidze/blob/master/README.md Run this command to generate a migration for updating the core logidze_logger DB function. The --update flag is required for updates. ```sh bundle exec rails generate logidze:install --update ``` -------------------------------- ### Retrieve and Manipulate Versions List Source: https://github.com/palkan/logidze/blob/master/README.md Demonstrates how to get an enumerator of all versions, take specific versions, find a version by criteria, and retrieve versions in reverse order or including the current state. ```ruby post.logidze_versions # => Enumerator # you can use Enumerator's #take to return all post.logidze_versions.take # or you take a few or call any Enumerable method post.logidze_versions.take(2) post.logidze_versions.find do _1.title == "old title" end # we can also add options post.logidze_versions(reverse: true) # from newer to older post.logidze_versions(include_self: true) # returns self as the last record or the first one when `reverse` is set to true ``` -------------------------------- ### Logidze Failure Return Values Examples Source: https://github.com/palkan/logidze/blob/master/_autodocs/errors.md Demonstrates how Logidze methods return nil or false to indicate various failure conditions like missing versions or inability to undo/redo. The behavior of `at(time:)` with empty log data depends on configuration. ```ruby post = Post.find(1) # Returns nil for missing version post.at(version: 999) # => nil # Returns false when can't undo if post.undo! puts "Undone" else puts "No previous version" end # Returns false for switch failure if post.switch_to!(5) puts "Switched" else puts "Version 5 doesn't exist" end # Handles empty log_data gracefully post.log_data = nil post.at(time: 1.day.ago) # => post (self) or nil depending on config ``` -------------------------------- ### Run Specific Benchmark Source: https://github.com/palkan/logidze/blob/master/bench/triggers/Readme.md Execute a single benchmark for a specific trigger mode. For example, to run the hstore benchmark. ```sh make hstore ``` -------------------------------- ### Get Number of Versions Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Returns the total count of versions available in the history. This is delegated to the size of the versions array. ```ruby post = Post.find(1) post.log_data.size # => 5 ``` -------------------------------- ### Accessing and Managing Versions via History Object Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Illustrates the typical way to interact with versions through the History object. It shows how to access the list of versions, find a specific version by its number, get the current version, and filter versions. ```ruby post = Post.find(1) history = post.log_data # Access versions array history.versions # => [Version@v1, Version@v2, Version@v3] # Find specific version version = history.find_by_version(2) # Get current version current = history.current_version # Iterate and filter history.versions.select { |v| v.responsible_id == 42 } ``` -------------------------------- ### Retrieve Record Versions and History Source: https://github.com/palkan/logidze/blob/master/README.md Demonstrates how to get the current version, log size, and specific record versions by time or version number. Handles cases where log_data is nil. ```ruby post = Post.find(27) # Show current version post.log_version #=> 3 # Show log size (number of versions) post.log_size #=> 3 # Get copy of a record at a given time post.at(time: 2.days.ago) # or revert the record itself to the previous state (without committing to DB) post.at!(time: "2018-04-15 12:00:00") # If no version found post.at(time: "1945-05-09 09:00:00") #=> nil ``` ```ruby post.at(version: 2) ``` ```ruby Post.where(active: true).at(time: 1.month.ago) ``` -------------------------------- ### Log Data JSON Structure Example Source: https://github.com/palkan/logidze/blob/master/_autodocs/types.md Illustrates the structure of the JSONB data stored in the log_data column, detailing version, history, changes, and metadata. ```javascript { "v": 3, // current version number (integer) "h": [ // history array { "v": 1, // version number "ts": 1687000100000, // timestamp in milliseconds "c": { // changes (columns that changed) "title": "Initial Title", "body": "Initial body" }, "m": {} // metadata (empty if no meta) }, { "v": 2, "ts": 1687000200000, "c": { "title": "Updated Title" // only changed column }, "m": { "_r": 42, // responsible ID "ip": "192.168.1.1" // custom metadata } }, { "v": 3, "ts": 1687000300000, "c": { "views": 150 }, "m": { "_r": 1 } } ] } ``` -------------------------------- ### Use `fx` Gem for Schema.rb Source: https://github.com/palkan/logidze/blob/master/_autodocs/configuration.md Integrate the `fx` gem to continue using `schema.rb` while supporting functions and triggers. The `logidze:install` generator will automatically detect and use `fx`. ```ruby gem "fx" # Allows schema.rb with functions and triggers ``` ```bash rails generate logidze:install ``` -------------------------------- ### Diff Hash Format Example Source: https://github.com/palkan/logidze/blob/master/_autodocs/types.md Illustrates the standard format for diff hashes returned by methods like `diff_from`. It includes record ID and a 'changes' hash detailing old and new values for modified attributes. ```ruby { "id" => 1, # Record ID "changes" => { "title" => { "old" => "Previous Title", "new" => "New Title" }, "body" => { "old" => "old body text", "new" => "new body text" } } } ``` -------------------------------- ### Querying Logidze Histories Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/logidze-data-model.md Examples of querying historical data from the Logidze::LogidzeData model. Use these to retrieve histories based on time, specific records, or to analyze history size and count. ```ruby # All histories created in the last hour one_hour_ago = 1.hour.ago Logidze::LogidzeData.where("created_at > ?", one_hour_ago) ``` ```ruby # Histories for specific record Logidze::LogidzeData.where(loggable_type: "Post", loggable_id: 1) ``` ```ruby # Count histories by model Logidze::LogidzeData .group(:loggable_type) .select(:loggable_type, "COUNT(*) as count") ``` ```ruby # Find large histories (by JSON size) Logidze::LogidzeData .select("*, octet_length(log_data) as log_size") .order("log_size DESC") .limit(10) ``` -------------------------------- ### Get all previous versions of a record Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Use `logidze_versions` to get an enumerator of all historical versions of a record. You can then use standard Enumerable methods like `take` to retrieve specific versions. ```ruby post = Post.find(1) # Get all previous versions post.logidze_versions.take # => [Record@v1, Record@v2, Record@v3] # Take first 2 versions post.logidze_versions.take(2) # => [Record@v1, Record@v2] ``` -------------------------------- ### Get Raw Data Hash Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves the raw underlying data hash of the history. ```ruby post = Post.find(1) post.log_data.data # => {"v" => 3, "h" => [...]} ``` -------------------------------- ### Get Loggable ID Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/logidze-data-model.md Retrieves the primary key of the model associated with the log history record. ```ruby logidze_data_record.loggable_id # => 42 ``` -------------------------------- ### Create a Version Instance Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Instantiates a Version object by wrapping a hash representing a single log entry. The input hash should contain keys like 'v', 'ts', 'c', and 'm'. ```Ruby entry = { "v" => 2, "ts" => 1687000200000, "c" => { "title" => "New Title" }, "m" => { "_r" => 42, "ip" => "192.168.1.1" } } version = Logidze::History::Version.new(entry) ``` -------------------------------- ### Get Loggable Type Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/logidze-data-model.md Retrieves the class name of the model associated with the log history record. ```ruby logidze_data_record.loggable_type # => "Post" ``` -------------------------------- ### Version Control with Instance Methods Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Use instance methods to undo/redo changes, switch to a specific version, and manage the log data. ```ruby post = Post.find(1) # Version control post.undo! post.redo! post.switch_to!(2) ``` -------------------------------- ### Get All Versions Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves an array of Version objects representing each change in the history. This is lazy-evaluated and cached. ```ruby post = Post.find(1) post.log_data.versions # => [Version@v1, Version@v2, Version@v3] post.log_data.versions.each { |v| puts "v#{v.version}: #{v.time}" } ``` -------------------------------- ### Get Current Version Number Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Returns the integer representing the current version number of the log data. ```ruby post = Post.find(1) post.log_data.version # => 3 ``` -------------------------------- ### Logidze Migration Generation Options Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Demonstrates various options for generating Logidze migrations, including backfilling, limiting log size, tracking specific columns, detached storage, custom timestamp columns, and updating existing models. ```bash rails generate logidze:model Post --backfill ``` ```bash rails generate logidze:model Post --limit=10 ``` ```bash rails generate logidze:model Post --only=title,body ``` ```bash rails generate logidze:model User --except=password ``` ```bash rails generate logidze:model Post --detached ``` ```bash rails generate logidze:model Post --timestamp_column created_at ``` ```bash rails generate logidze:model Post --update --only=title,body ``` -------------------------------- ### Get Metadata Hash Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves the metadata hash associated with the current version. Returns nil if no metadata is set. ```ruby post = Post.find(1) post.log_data.meta # => {"_r" => 42, "ip" => "192.168.1.1", "user_agent" => "Mozilla/5.0"} ``` -------------------------------- ### Run Database Migrations Source: https://github.com/palkan/logidze/blob/master/README.md Execute database migrations to apply the changes created by the Logidze generator. ```bash bundle exec rails db:migrate ``` -------------------------------- ### Get Responsible ID Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves the ID of the user who made the change for the current version. Returns nil if not set. ```ruby post = Post.find(1) post.log_data.responsible_id # => 42 (user ID) ``` -------------------------------- ### Track Meta Information with Version Source: https://github.com/palkan/logidze/blob/master/README.md Illustrates how to associate meta-information (like IP address) with a specific version by wrapping the save operation within a `Logidze.with_meta` block. Metadata is passed as a hash. ```ruby Logidze.with_meta({ip: request.ip}) do post.save! end ``` -------------------------------- ### Generate Logidze Migration for Detached Storage Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Run this command to generate the necessary migration file for creating the `logidze_data` table when using detached storage. This should be done once per project. ```bash rails generate logidze:migration:logs rails db:migrate ``` -------------------------------- ### Configuration Source: https://github.com/palkan/logidze/blob/master/_autodocs/INDEX.md Details on how to configure Logidze behavior globally or per model. ```APIDOC ## Configuration ### Description Details on how to configure Logidze behavior globally or per model. ### Settings - **`has_logidze ignore_log_data:`** - Type: Boolean - Purpose: Exclude from queries - **`has_logidze detached:`** - Type: Boolean - Purpose: Store separately - **`Logidze.append_on_undo`** - Type: Boolean - Purpose: Undo mode - **`Logidze.associations_versioning`** - Type: Boolean - Purpose: Version associations too - **`Logidze.log_data_placement`** - Type: Symbol - Purpose: Global storage location ``` -------------------------------- ### Get Timestamp of Version Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Retrieves the timestamp (in milliseconds since epoch) when the version was created. This can be converted to a standard Time object. ```Ruby post = Post.find(1) version = post.log_data.versions[0] version.time # => 1687000200000 (milliseconds) # Convert to Time time = Time.at(version.time / 1000.0) # => 2023-06-15 12:00:00 UTC ``` -------------------------------- ### Create Initial Logidze Snapshot for a Record Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Creates an initial snapshot of a record's current state. Use this when `log_data` is initially nil. You can specify a timestamp column or filter columns to include or exclude. ```ruby user = User.find(1) user.create_logidze_snapshot!(timestamp: :created_at) ``` ```ruby user.create_logidze_snapshot!(only: %w[name email]) ``` ```ruby user.create_logidze_snapshot!(except: %w[password token]) ``` -------------------------------- ### Get Version Number Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Retrieves the sequential version number for a specific log entry. This is useful for tracking the order of changes. ```Ruby post = Post.find(1) post.log_data.versions.first.version # => 1 post.log_data.versions.last.version # => 3 ``` -------------------------------- ### Generate Logidze Model with Backfill Option Source: https://github.com/palkan/logidze/blob/master/README.md Generate Logidze model integration with the --backfill option to create initial snapshots for existing data. ```bash bundle exec rails generate logidze:model Post --backfill ``` -------------------------------- ### Get Metadata for Version Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Accesses the metadata hash associated with this version. This can include the responsible ID and any custom data set during the change. ```Ruby post = Post.find(1) # Set metadata Logidze.with_meta({ip: "192.168.1.1", request_id: "abc123"}) do post.update!(title: "Updated") end # Retrieve metadata version = post.log_data.current_version version.meta # => { # "_r" => 42, # responsible ID # "ip" => "192.168.1.1", # "request_id" => "abc123" # } # Metadata not present post.log_data.versions.first.meta # => nil or {} ``` -------------------------------- ### Run Version At Benchmark Script (10 Versions, Single) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Executes the Ruby script to benchmark retrieving a single version at a specific time when records have 10 versions. ```shell bundle exec ruby benchmarks/version_at_bench.rb ``` -------------------------------- ### Get Changed Attributes Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Accesses the hash of attributes that were modified in this specific version. Keys are attribute names, and values are their new values. ```Ruby post = Post.find(1) version = post.log_data.versions[1] # Version 2 version.changes # => {"title" => "Updated Title", "updated_at" => "2023-06-15T12:00:00Z"} ``` -------------------------------- ### Version At Benchmark Results (10 Versions, Single) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of retrieving a single version at a specific time (Logidze AT single, LogidzeDetached AT single, PT AT single) when records have 10 versions. ```text Comparison: Logidze AT single: 468.3 i/s LogidzeDetached AT single: 262.3 i/s - same-ish: difference falls within error PT AT single: 169.6 i/s - 2.76x slower ``` -------------------------------- ### Get Current Version Object Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves the Version object corresponding to the current version number. Returns nil if the current version is not found. ```ruby post = Post.find(1) current = post.log_data.current_version current.version # => 3 current.time # => 1687000200000 current.changes # => {"title" => "Current Title"} ``` -------------------------------- ### Version At Benchmark Results (10 Versions, Many) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of retrieving multiple versions at a specific time (Logidze AT many, LogidzeDetached AT many, PT AT many) when records have 10 versions. Note PaperTrail's N+1 problem. ```text Comparison: Logidze AT many: 283.7 i/s LogidzeDetached AT many: 199.3 i/s - 1.42x slower PT AT many: 27.0 i/s - 10.52x slower ``` -------------------------------- ### Get the number of versions in the log Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md The `log_size` method returns the total number of versions stored in the `log_data` for the record. It returns 0 if `log_data` is nil. ```ruby post = Post.find(1) post.log_size # => 5 (5 versions) ``` -------------------------------- ### Run Specific Benchmark (hstore fallback) Source: https://github.com/palkan/logidze/blob/master/bench/triggers/Readme.md Execute the benchmark for the hstore trigger mode with jsonb fallback. ```sh make hstore_fallback ``` -------------------------------- ### Version At Benchmark Results (100 Versions, Many) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of retrieving multiple versions at a specific time (Logidze AT many, LogidzeDetached AT many, PT AT many) when records have 100 versions. Note PaperTrail's N+1 problem. ```text Comparison: Logidze AT many: 167.9 i/s LogidzeDetached AT many: 130.2 i/s - 1.29x slower PT AT many: 24.2 i/s - 6.94x slower ``` -------------------------------- ### Run Update Benchmark Script (2 Fields) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Executes the Ruby script to benchmark update operations with a 2-field changeset. ```shell bundle exec ruby benchmarks/update_bench.rb ``` -------------------------------- ### Get Diff from Specified Time Source: https://github.com/palkan/logidze/blob/master/README.md Shows how to retrieve the differences in a record's attributes from a specific point in time. Returns an empty hash if log_data is nil. ```ruby post.diff_from(time: 1.hour.ago) #=> { "id" => 27, "changes" => { "title" => { "old" => "Logidze sucks!", "new" => "Logidze rulz!" } } } ``` ```ruby Post.where(created_at: Time.zone.today.all_day).diff_from(time: 1.hour.ago) ``` -------------------------------- ### Get Raw Log Data from a Record Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Retrieves the raw JSON string of the `log_data` attribute before any type casting. This is useful for inspecting the exact logged history. ```ruby post = Post.find(1) post.raw_log_data # => "{\"v\":3,\"h\":[...]}" ``` -------------------------------- ### Version At Benchmark Results (100 Versions, Single) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of retrieving a single version at a specific time (Logidze AT single, LogidzeDetached AT single, PT AT single) when records have 100 versions. ```text Comparison: Logidze AT single: 339.2 i/s LogidzeDetached AT single: 219.6 i/s - same-ish: difference falls within error PT AT single: 150.3 i/s - 2.26x slower ``` -------------------------------- ### Get Next Version Object Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves the Version object for the version immediately following the current one. Returns nil if the current version is the latest version. ```ruby post = Post.find(1) post.log_data.next_version # Returns v(current+1) or nil if already at latest ``` -------------------------------- ### Initialize Logidze::History Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Creates a History wrapper around raw log data. The data hash must contain 'v' for version and 'h' for history. ```ruby history = Logidze::History.new({ "v" => 3, "h" => [ { "v" => 1, "ts" => 1234567890, "c" => { "title" => "v1" }, "m" => {} }, { "v" => 2, "ts" => 1234567900, "c" => { "title" => "v2" }, "m" => {} } ] }) ``` -------------------------------- ### Get Raw Version Data Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Retrieves the complete, underlying hash that represents this version entry. This includes all keys like 'v', 'ts', 'c', and 'm'. ```Ruby version = post.log_data.versions[0] version.data # => { # "v" => 1, # "ts" => 1687000100000, # "c" => {"title" => "Original Title"}, # "m" => {"_r" => 42} # } ``` -------------------------------- ### Create Full Snapshot with Logging Disabled Source: https://github.com/palkan/logidze/blob/master/README.md Use Logidze.without_logging for multiple updates, then Logidze.with_full_snapshot to create a single log entry with the final state. ```ruby record = Model.find(params[:id]) Logidze.without_logging do # perform multiple write operations with record end Logidze.with_full_snapshot do record.touch end ``` -------------------------------- ### Run Diff Benchmark Script (10 Versions) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Executes the Ruby script to benchmark diff calculation when each record has 10 versions. ```shell bundle exec ruby benchmarks/diff_bench.rb ``` -------------------------------- ### Access Associated Loggable Record Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/logidze-data-model.md Fetches the actual record that the log history belongs to using the polymorphic association. Also demonstrates how to get the class of the associated record. ```ruby logidze_data_record = Logidze::LogidzeData.find(1) logidze_data_record.loggable # => Post#42 logidze_data_record.loggable.class # => Post ``` -------------------------------- ### Get Previous Version Object Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Retrieves the Version object for the version immediately preceding the current one. Returns nil if the current version is the first version (v1). ```ruby post = Post.find(1) prev = post.log_data.previous_version # nil if current is v1, otherwise returns v(current-1) ``` -------------------------------- ### Diff Benchmark Results (10 Versions) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of diff calculation performance between Logidze, LogidzeDetached, and PaperTrail (join and direct) when records have 10 versions. ```text Comparison: Logidze DIFF: 189.2 i/s LogidzeDetached DIFF: 124.0 i/s - 1.52x slower PT (join) DIFF: 4.4 i/s - 42.82x slower PT DIFF: 3.1 i/s - 60.69x slower ``` -------------------------------- ### create_logidze_snapshot! (instance method) Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Creates an initial snapshot of the record's current state if `log_data` is nil. This method is called on a specific record instance. ```APIDOC ## create_logidze_snapshot! (instance method) ### Description Creates an initial snapshot of the record's current state if `log_data` is nil. ### Method `record.create_logidze_snapshot!(timestamp: nil, only: nil, except: nil)` ### Parameters #### Path Parameters * **timestamp** (String/Symbol) - Optional - Timestamp column to use for version time (default: current time) * **only** (Array) - Optional - Array of column names to include in snapshot * **except** (Array) - Optional - Array of column names to exclude from snapshot ### Returns The loaded History object from the database. ### Request Example ```ruby user = User.find(1) user.create_logidze_snapshot!(timestamp: :created_at) # With column filtering user.create_logidze_snapshot!(only: %w[name email]) user.create_logidze_snapshot!(except: %w[password token]) ``` ``` -------------------------------- ### Manually Create Logidze Snapshot Source: https://github.com/palkan/logidze/blob/master/README.md Manually create a snapshot of the current table data by updating the log_data column. ```sql UPDATE as t SET log_data = logidze_snapshot(to_jsonb(t)) ``` -------------------------------- ### Get Responsible ID for Change Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Retrieves the ID of the user or agent responsible for this change. It checks the current meta format ('_r') and falls back to the legacy format ('r'). ```Ruby post = Post.find(1) # Set via Logidze.with_responsible Logidze.with_responsible(42) do post.update!(title: "New Title") end post.log_data.current_version.responsible_id # => 42 # Not set if no responsible was recorded post.log_data.versions.first.responsible_id # => nil ``` -------------------------------- ### Diff Benchmark Results (100 Versions) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of diff calculation performance between Logidze, LogidzeDetached, and PaperTrail (direct and join) when records have 100 versions. ```text Comparison: Logidze DIFF: 78.5 i/s LogidzeDetached DIFF: 69.8 i/s - 1.13x slower PT DIFF: 0.4 i/s - 200.20x slower PT (join) DIFF: 0.3 i/s - 227.26x slower ``` -------------------------------- ### Get Record State at Specific Version Number Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Returns a copy of the record at a specific version number. Returns `self` if the version equals the current version, or `nil` if the version does not exist. ```ruby post = Post.find(1) post.at_version(2) # => Record at version 2 ``` -------------------------------- ### Memory Usage Benchmark Results (10 Versions) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Memory usage comparison for Plain records, PT with versions, Logidze records, and LogidzeDetached records when each record has 10 versions. ```text Plain records Total Allocated: 44.38 KB Total Retained: 22.52 KB Retained_memsize memory (per record): 2.35 KB PT with versions Total Allocated: 387.58 KB Total Retained: 223.77 KB Retained_memsize memory (per record): 203.13 KB Logidze records Total Allocated: 76.37 KB Total Retained: 38.46 KB Retained_memsize memory (per record): 3.72 KB LogidzeDetached records Total Allocated: 53.84 KB Total Retained: 24.1 KB Retained_memsize memory (per record): -1768 B ``` -------------------------------- ### Define Model with has_logidze (using global setting) Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Use this snippet to enable Logidze functionality without specifying parameters, allowing the model to inherit the global settings for detached storage. ```ruby class Article < ApplicationRecord # Use global setting has_logidze end ``` -------------------------------- ### Test Logidze Change Tracking Source: https://github.com/palkan/logidze/blob/master/_autodocs/README.md Verify that Logidze correctly tracks changes to model attributes. This example checks if the title attribute is correctly restored to its original value using versioning. ```ruby RSpec.describe Post do it "tracks changes" do post = create(:post, title: "Original") post.update!(title: "Updated") v1 = post.at(version: 1) expect(v1.title).to eq "Original" end it "records responsibility" do user = create(:user) post = create(:post) Logidze.with_responsible(user.id) do post.update!(title: "Updated") end expect(post.log_data.responsible_id).to eq user.id end end ``` -------------------------------- ### Run All Benchmarks Source: https://github.com/palkan/logidze/blob/master/bench/triggers/Readme.md Execute all available benchmarks. The number of transactions can be controlled using the 'T' variable. ```sh make ``` -------------------------------- ### Run Raw Update Benchmark Source: https://github.com/palkan/logidze/blob/master/bench/triggers/Readme.md Execute the benchmark for raw updates without any triggers. ```sh make plain ``` -------------------------------- ### Compute Cumulative Changes to a Specific Point Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/history-class.md Computes the cumulative changes from the initial state to a specified time or version. Can be used with a specific version, timestamp, or to calculate changes from a starting version. ```ruby history = post.log_data # Get all values at version 2 state_v2 = history.changes_to(version: 2) # => {"id" => 1, "title" => "v2 title", "body" => "v2 body"} # Get all values at a timestamp timestamp = (2.days.ago.to_r * 1000).to_i history.changes_to(time: timestamp) # With starting version history.changes_to(version: 3, from: 1) # => Only changes from v2 onwards merged into v3 ``` -------------------------------- ### Run Memory Usage Benchmark Script (10 Versions) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Executes the Ruby script to profile memory usage when each record has 10 versions. ```shell bundle exec ruby benchmarks/memory_profile.rb ``` -------------------------------- ### Get Record State at Specific Time or Version Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Returns a copy of the record at a specific point in time or version number. Use this to inspect historical states without modifying the current record. ```ruby post = Post.find(1) # Get state from 2 days ago past_post = post.at(time: 2.days.ago) # Get specific version v2_post = post.at(version: 2) # Get state at exact timestamp timestamp = Time.parse("2023-06-15 14:30:00") post.at(time: timestamp) # With string post.at(time: "2023-06-15 14:30:00") # With integer (milliseconds since epoch) post.at(time: 1687000200000) ``` -------------------------------- ### Memory Usage Benchmark Results (100 Versions) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Memory usage comparison for Plain records, PT with versions, Logidze records, and LogidzeDetached records when each record has 100 versions. ```text Plain records Total Allocated: 44.3 KB Total Retained: 22.48 KB Retained_memsize memory (per record): 2.23 KB PT with versions Total Allocated: 3.32 MB Total Retained: 1.99 MB Retained_memsize memory (per record): 1.97 MB Logidze records Total Allocated: 281.22 KB Total Retained: 140.89 KB Retained_memsize memory (per record): 14.16 KB LogidzeDetached records Total Allocated: 53.29 KB Total Retained: 23.83 KB Retained_memsize memory (per record): -1608 B ``` -------------------------------- ### Insert Benchmark Results Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of insert performance between Plain INSERT, Logidze INSERT, LogidzeDetached INSERT, and PaperTrail INSERT. ```text Comparison: Plain INSERT: 450.9 i/s Logidze INSERT: 408.2 i/s - same-ish: difference falls within error LogidzeDetached INSERT: 359.8 i/s - same-ish: difference falls within error PaperTrail INSERT: 183.9 i/s - same-ish: difference falls within error ``` -------------------------------- ### Create Logidze Snapshot from Model Instance Source: https://github.com/palkan/logidze/blob/master/README.md Create a logidze snapshot for a specific model instance, with options for timestamp and column filtering. ```ruby Model.create_logidze_snapshot # specify the timestamp column to use for the initial version (by default the current time is used) Model.create_logidze_snapshot(timestamp: :created_at) # filter columns Model.create_logidze_snapshot(only: %w[name]) Model.create_logidze_snapshot(except: %w[password]) # or call a similar method (but with !) on a record my_model = Model.find(params[:id]) my_model.create_logidze_snapshot!(timestamp: :created_at) ``` -------------------------------- ### Get Changes Since a Specific Time or Version Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Calculates and returns the differences (diffs) made to all records in a relation since a specified time or version. The output is an array of hashes, mirroring the format of instance method diffs. ```ruby Post.where(created_at: Time.zone.today.all_day).diff_from(time: 1.hour.ago) ``` ```ruby Post.diff_from(version: 2) ``` -------------------------------- ### Generate Logidze Model with Log Size Limit Source: https://github.com/palkan/logidze/blob/master/README.md Generate Logidze model integration with a specified limit for the size of the log data. ```bash bundle exec rails generate logidze:model Post --limit=10 ``` -------------------------------- ### Get Changes Since a Specific Time or Version Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Returns a hash representing the changes made to the record since a specified time or version. The hash includes the record ID and a nested 'changes' object detailing attribute modifications. ```ruby post = Post.find(1) # Changes since yesterday diff = post.diff_from(time: 1.day.ago) # => { # "id" => 1, # "changes" => { # "title" => { "old" => "Old Title", "new" => "New Title" }, # "views" => { "old" => 100, "new" => 150 } # } # } # Changes since version 2 post.diff_from(version: 2) ``` -------------------------------- ### Update Benchmark Results (5 Fields) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of update performance with a 5-field changeset for Plain UPDATE, Logidze Detached UPDATE, Logidze UPDATE, and PT UPDATE. ```text Comparison: Plain UPDATE #2: 117.8 i/s Logidze Detached UPDATE #2: 116.1 i/s - same-ish: difference falls within error Logidze UPDATE #2: 115.4 i/s - same-ish: difference falls within error PT UPDATE #2: 57.6 i/s - 2.04x slower ``` -------------------------------- ### Undo and Redo Record Changes Source: https://github.com/palkan/logidze/blob/master/README.md Explains how to revert a record to a previous state using `#undo!`, restore to a future state using `#redo!`, or switch to an arbitrary version using `#switch_to!`. These actions store the new state in the database. ```ruby # Revert record to the previous state (and stores this state in DB) post.undo! # You can now user redo! to revert back post.redo! # More generally you can revert record to arbitrary version post.switch_to!(2) ``` -------------------------------- ### Handling PG::NumericValueOutOfRange Error Source: https://github.com/palkan/logidze/blob/master/_autodocs/errors.md Shows an example of a record containing a string that, when interpreted as a large number in scientific notation, overflows PostgreSQL's numeric type, leading to a `PG::NumericValueOutOfRange` error. This typically occurs with the `hstore_to_jsonb_loose` function. ```ruby # Record with problematic data post.some_numeric_field = "557236406134e62000323100" post.save! # PG::NumericValueOutOfRange: "ERROR: value overflows numeric format" ``` -------------------------------- ### Generate Logidze Model with Fx Integration Source: https://github.com/palkan/logidze/blob/master/README.md Generate Logidze model integration, optionally specifying Fx gem usage with --fx or --no-fx flags. ```bash bundle exec rails generate logidze:model Post ``` ```bash bundle exec rails generate logidze:model Post --fx ``` ```bash bundle exec rails generate logidze:model Post --no-fx ``` -------------------------------- ### Create Full Snapshot Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/logidze-module.md Instructs Logidze to create a full snapshot of the record state instead of a diff for the new version. Use this when the entire record state needs to be preserved. ```ruby Logidze.without_logging do record.title = "new title" record.body = "new body" end Logidze.with_full_snapshot do record.touch # Creates a full snapshot version with all attributes end ``` -------------------------------- ### Generate Logidze Model with Limit Source: https://github.com/palkan/logidze/blob/master/_autodocs/README.md Generate a model with log compaction to limit log size and prevent unbounded growth. Old entries are automatically merged when the limit is exceeded. ```bash rails generate logidze:model Post --limit=50 ``` -------------------------------- ### Filtering Versions by Time Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/version-class.md Demonstrates how to filter versions based on their timestamp, specifically finding versions created within the last hour. This is useful for tracking recent changes. ```ruby post = Post.find(1) # Find recent versions one_hour_ago_ms = (1.hour.ago.to_r * 1000).to_i recent = post.log_data.versions.select { |v| v.time > one_hour_ago_ms } ``` -------------------------------- ### Run Insert Benchmark Script Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Executes the Ruby script to benchmark insert operations for Logidze and PaperTrail. ```shell bundle exec ruby benchmarks/insert_bench.rb ``` -------------------------------- ### create_logidze_snapshot (class method) Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/model-methods.md Creates initial snapshots for all records with nil `log_data`. This class method is used for backfilling or initializing history for multiple records. ```APIDOC ## create_logidze_snapshot (class method) ### Description Creates initial snapshots for all records with nil `log_data`. ### Method `Model.create_logidze_snapshot(timestamp: nil, only: nil, except: nil)` ### Parameters #### Path Parameters * **timestamp** (String/Symbol) - Optional - Timestamp column to use (default: current time) * **only** (Array) - Optional - Column names to include * **except** (Array) - Optional - Column names to exclude ### Returns nil ### Request Example ```ruby # Backfill all records Post.create_logidze_snapshot # Use created_at as snapshot time Post.create_logidze_snapshot(timestamp: :created_at) # Include only specific columns Post.create_logidze_snapshot(only: %w[title body]) # Exclude columns Post.create_logidze_snapshot(except: %w[password]) ``` ``` -------------------------------- ### Run Specific Benchmark (keys) Source: https://github.com/palkan/logidze/blob/master/bench/triggers/Readme.md Execute the benchmark for the key iteration trigger mode. ```sh make keys ``` -------------------------------- ### Generate Logidze Model with Custom Path Source: https://github.com/palkan/logidze/blob/master/README.md Generate Logidze model integration, specifying a custom path for the model file if it's not in the default location. ```bash bundle exec rails generate logidze:model Post --path "app/models/custom/post.rb" ``` -------------------------------- ### Querying History with Instance Methods Source: https://github.com/palkan/logidze/blob/master/_autodocs/api-reference/has-logidze-macro.md Access instance methods to query historical data by time or version, calculate differences, and retrieve all versions. ```ruby post = Post.find(1) # Querying history post.at(time: 2.days.ago) post.at(version: 2) post.diff_from(time: 1.day.ago) post.logidze_versions ``` -------------------------------- ### Set Global Defaults for Logidze Source: https://github.com/palkan/logidze/blob/master/_autodocs/INDEX.md Configure global default settings for Logidze in an initializer file. Options include appending on undo, ignoring data by default, and enabling association versioning. ```ruby # config/initializers/logidze.rb Logidze.append_on_undo = true Logidze.ignore_log_data_by_default = true Logidze.associations_versioning = true ``` -------------------------------- ### Update Benchmark Results (2 Fields) Source: https://github.com/palkan/logidze/blob/master/bench/performance/README.md Comparison of update performance with a 2-field changeset for Plain UPDATE, LogidzeDetached UPDATE, Logidze UPDATE, and PT UPDATE. ```text Comparison: Plain UPDATE #1: 204.8 i/s LogidzeDetached UPDATE #1: 188.4 i/s - same-ish: difference falls within error Logidze UPDATE #1: 175.7 i/s - same-ish: difference falls within error PT UPDATE #1: 114.2 i/s - same-ish: difference falls within error ```