### 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
| Version |
Time |
Changes |
By |
<% @versions.each do |version| %>
| <%= 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 %> |
<% end %>
```
--------------------------------
### 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
```