{"id":13484321,"url":"https://github.com/palkan/logidze","last_synced_at":"2025-05-13T15:10:42.998Z","repository":{"id":38554993,"uuid":"56171308","full_name":"palkan/logidze","owner":"palkan","description":"Database changes log for Rails","archived":false,"fork":false,"pushed_at":"2025-05-09T17:54:58.000Z","size":712,"stargazers_count":1642,"open_issues_count":12,"forks_count":81,"subscribers_count":19,"default_branch":"master","last_synced_at":"2025-05-09T18:52:39.465Z","etag":null,"topics":["activerecord","hacktoberfest","postgresql","rails","versioning"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/palkan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-04-13T17:12:54.000Z","updated_at":"2025-05-09T17:54:51.000Z","dependencies_parsed_at":"2024-01-08T17:14:39.474Z","dependency_job_id":"44de5496-5731-4e27-93a3-c48da28a8513","html_url":"https://github.com/palkan/logidze","commit_stats":{"total_commits":340,"total_committers":38,"mean_commits":8.947368421052632,"dds":"0.35882352941176465","last_synced_commit":"09a91813fbc26c36d21a1836b2534e791875fa76"},"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Flogidze","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Flogidze/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Flogidze/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/palkan%2Flogidze/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/palkan","download_url":"https://codeload.github.com/palkan/logidze/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253969248,"owners_count":21992263,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","hacktoberfest","postgresql","rails","versioning"],"created_at":"2024-07-31T17:01:22.494Z","updated_at":"2025-05-13T15:10:37.976Z","avatar_url":"https://github.com/palkan.png","language":"Ruby","readme":"[![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com)\n[![Gem Version](https://badge.fury.io/rb/logidze.svg)](https://rubygems.org/gems/logidze)\n[![Build](https://github.com/palkan/logidze/workflows/Build/badge.svg)](https://github.com/palkan/logidze/actions)\n[![Open Source Helpers](https://www.codetriage.com/palkan/logidze/badges/users.svg)](https://www.codetriage.com/palkan/logidze)\n\n# Logidze\n\nLogidze provides tools for logging DB records changes when using PostgreSQL. Just like [audited](https://github.com/collectiveidea/audited) and [paper_trail](https://github.com/airblade/paper_trail) do (but [faster](bench/performance)).\n\nLogidze allows you to create a DB-level log (using triggers) and gives you an API to browse this log.\nThe log is stored with the record itself in JSONB column. No additional tables required.\n\n🤔 [How is Logidze pronounced?](https://github.com/palkan/logidze/issues/73)\n\nOther requirements:\n\n- Ruby ~\u003e 2.7\n- Rails \u003e= 6.0 (for Rails 4.2 use version \u003c=0.12.0, for Rails 5.x use version \u003c= 1.2.3)\n- PostgreSQL \u003e= 10.0\n\n\u003ca href=\"https://evilmartians.com/\"\u003e\n\u003cimg src=\"https://evilmartians.com/badges/sponsored-by-evil-martians.svg\" alt=\"Sponsored by Evil Martians\" width=\"236\" height=\"54\"\u003e\u003c/a\u003e\n\n## Links\n\n- [Logidze 1.0: Active Record, Postgres, Rails, and time travel](https://evilmartians.com/chronicles/logidze-1-0-active-record-postgresql-rails-and-time-travel?utm_source=logidze)\n- [Logidze: for all those tired of versioning data](https://evilmartians.com/chronicles/introducing-logidze?utm_source=logidze)\n\n## Table of contents\n\n- [Installation \u0026 Configuration](#installation)\n  - [Using with schema.rb](#using-with-schemarb)\n  - [Configuring models](#configuring-models)\n  - [Backfill data](#backfill-data)\n  - [Log size limits](#log-size-limits)\n  - [Tracking only selected columns](#tracking-only-selected-columns)\n  - [Logs timestamps](#logs-timestamps)\n  - [Undoing a Generated Invocation](#undoing-a-generated-invocation)\n  - [Using with partitioned tables](#using-with-partitioned-tables)\n  - [Storing history data in a separate table](#storing-history-data-in-a-separate-table)\n- [Usage](#usage)\n  - [Basic API](#basic-api)\n  - [Track meta information](#track-meta-information)\n  - [Track responsibility](#track-responsibility)\n  - [Disable logging temporary](#disable-logging-temporary)\n  - [Reset log](#reset-log)\n  - [Creating full snapshot instead of diffs](#full-snapshots)\n  - [Associations versioning](#associations-versioning)\n- [Dealing with large logs](#dealing-with-large-logs)\n- [Handling records deletion](#handling-records-deletion)\n- [Handling PG exceptions](#handling-pg-exceptions)\n- [Upgrading](#upgrading)\n- [Log format](#log-format)\n- [Troubleshooting 🚨](#troubleshooting)\n- [Development](#development)\n\n## Installation\n\nAdd Logidze to your application's Gemfile:\n\n```ruby\ngem \"logidze\", \"~\u003e 1.1\"\n```\n\nInstall required DB extensions and create trigger function:\n\n```sh\nbundle exec rails generate logidze:install\n```\n\nThis creates a migration for adding trigger function and enabling the hstore extension.\n\nRun migrations:\n\n```sh\nbundle exec rails db:migrate\n```\n\n**NOTE:** Logidze uses DB functions and triggers, hence you need to use SQL format for a schema dump:\n\n```ruby\n# application.rb\nconfig.active_record.schema_format = :sql\n```\n\n### Using with schema.rb\n\nLogidze seamlessly integrates with [fx][] gem to make it possible to continue using `schema.rb` for the database schema dump.\n\nAdd `fx` gem to your Gemfile and run the same Logidze generators: `rails g logidze:install` or `rails g logidze:model`.\n\nIf for some reason Logidze couldn't detect the presence of Fx in your bundle, you can enforce it by passing `--fx` option to generators.\n\nOn the other hand, if you have `fx` gem but don't want Logidze to use it—pass `--no-fx` option.\n\n### Configuring models\n\nRun the following migration to enable changes tracking for an Active Record model and adding a `log_data::jsonb` column to the table:\n\n```sh\nbundle exec rails generate logidze:model Post\nbundle exec rails db:migrate\n```\n\nThis also adds `has_logidze` line to your model, which adds methods for working with logs.\n\nBy default, Logidze tries to infer the path to the model file from the model name and may fail, for example, if you have unconventional project structure. In that case, you should specify the path explicitly:\n\n```sh\nbundle exec rails generate logidze:model Post --path \"app/models/custom/post.rb\"\n```\n\n### Backfill data\n\nTo backfill table data (i.e., create initial snapshots) add `backfill` option to the generator:\n\n```sh\nbundle exec rails generate logidze:model Post --backfill\n```\n\nNow your migration should contain and `UPDATE ...` statement to populate the `log_data` column with the current state.\n\nOtherwise a full snapshot will be created the first time the record is updated.\n\nYou can create a snapshot manually by performing the following query:\n\n```sql\nUPDATE \u003cmy_table\u003e as t\nSET log_data = logidze_snapshot(to_jsonb(t))\n```\n\nOr by using the following methods:\n\n```ruby\nModel.create_logidze_snapshot\n\n# specify the timestamp column to use for the initial version (by default the current time is used)\nModel.create_logidze_snapshot(timestamp: :created_at)\n\n# filter columns\nModel.create_logidze_snapshot(only: %w[name])\nModel.create_logidze_snapshot(except: %w[password])\n\n# or call a similar method (but with !) on a record\n\nmy_model = Model.find(params[:id])\nmy_model.create_logidze_snapshot!(timestamp: :created_at)\n```\n\nA snapshot is only created if `log_data` is null.\n\n### Log size limits\n\nYou can provide the `limit` option to `generate` to limit the size of the log (by default it's unlimited):\n\n```sh\nbundle exec rails generate logidze:model Post --limit=10\n```\n\n### Tracking only selected columns\n\nYou can log only particular columns changes. There are mutually exclusive `except` and `only` options for this:\n\n```sh\n# track all columns, except `created_at` and `active`\nbundle exec  rails generate logidze:model Post --except=created_at,active\n# track only `title` and `body` columns\nbundle exec rails generate logidze:model Post --only=title,body\n```\n\n### Logs timestamps\n\nBy default, Logidze tries to get a timestamp for a version from record's `updated_at` field whenever appropriate. If\nyour model does not have that column, Logidze will gracefully fall back to `statement_timestamp()`.\n\nTo change the column name or disable this feature completely, you can use the `timestamp_column` option:\n\n```sh\n# will try to get the timestamp value from `time` column\nbundle exec rails generate logidze:model Post --timestamp_column time\n# will always set version timestamp to `statement_timestamp()`\nbundle exec rails generate logidze:model Post --timestamp_column nil # \"null\" and \"false\" will also work\n```\n\n### Undoing a Generated Invocation\n\nIf you would like to re-do your `rails generate` anew, as with other generators you can use `rails destroy` to revert it, which will delete the migration file and undo the injection of `has_logidze` into the model file:\n\n```sh\nbundle exec rails destroy logidze:model Post\n```\n\n**IMPORTANT**: If you use non-UTC time zone for Active Record (`config.active_record.default_timezone`), you MUST always infer log timestamps from a timestamp column (e.g., when back-filling data); otherwise, you may end up with inconsistent logs ([#199](https://github.com/palkan/logidze/issues/199)). In general, we recommend using UTC as the database time unless there is a very strong reason not to.\n\n### Using with partitioned tables\n\nLogidze supports partitioned tables for PostgreSQL 13+ without any additional configuration. For PostgreSQL 11/12, you should use _after_ triggers. To do that, provide the `--after-trigger` option to the migration:\n\n```sh\nbundle exec rails generate logidze:model Post --after-trigger\n```\n\n**NOTE:** Record changes are written as a full snapshot if the partition has changed during the update.\n\n**IMPORTANT:** Using Logidze for partitioned tables in PostgreSQL 10 is not supported.\n\n### Storing history data in a separate table\n\nBy default, Logidze stores history data in the `log_data` column in the origin record table, which might lead to table bloat.\nIf it concerns you, you may configure Logidze to store history data in a separate table by providing `--detached` option to the migration:\n\n```sh\nbundle exec rails logidze:model Post --detached\n```\n\nYou can also configure Logidze to always store history data in a separate table for all models:\n\n```ruby\n# config/initializers/logidze.rb\n\nLogidze.log_data_placement = :detached\n```\n\n**IMPORTANT:** Using `--detached` mode for storing historic data slightly decreases performance. Check [bench results] for the details.\n\n## Usage\n\n### Basic API\n\nYour model now has `log_data` column, which stores changes log.\n\nTo retrieve record version at a given time use `#at` or `#at!` methods:\n\n```ruby\npost = Post.find(27)\n\n# Show current version\npost.log_version #=\u003e 3\n\n# Show log size (number of versions)\npost.log_size #=\u003e 3\n\n# Get copy of a record at a given time\npost.at(time: 2.days.ago)\n\n# or revert the record itself to the previous state (without committing to DB)\npost.at!(time: \"2018-04-15 12:00:00\")\n\n# If no version found\npost.at(time: \"1945-05-09 09:00:00\") #=\u003e nil\n```\n\nYou can also get revision by version number:\n\n```ruby\npost.at(version: 2)\n```\n\n**NOTE:** If `log_data` is nil, `#at(time:)` returns self and `#at(version:)` returns `nil`.\nYou can opt-in to return `nil` for time-based `#at` as well by setting `Logidze.return_self_if_log_data_is_empty = false`.\n\nIt is also possible to get version for relations:\n\n```ruby\nPost.where(active: true).at(time: 1.month.ago)\n```\n\nYou can also get diff from specified time:\n\n```ruby\npost.diff_from(time: 1.hour.ago)\n#=\u003e { \"id\" =\u003e 27, \"changes\" =\u003e { \"title\" =\u003e { \"old\" =\u003e \"Logidze sucks!\", \"new\" =\u003e \"Logidze rulz!\" } } }\n\n# the same for relations\nPost.where(created_at: Time.zone.today.all_day).diff_from(time: 1.hour.ago)\n```\n\n**NOTE:** If `log_data` is nil, `#diff_from` returns an empty Hash as `\"changes\"`.\n\nAlso, it is possible to retrieve list of model's `versions`:\n\n```ruby\npost.logidze_versions # =\u003e Enumerator\n\n# you can use Enumerator's #take to return all\npost.logidze_versions.take\n\n# or you take a few or call any Enumerable method\npost.logidze_versions.take(2)\npost.logidze_versions.find do\n  _1.title == \"old title\"\nend\n\n# we can also add options\npost.logidze_versions(reverse: true) # from newer to older\npost.logidze_versions(include_self: true) # returns self as the last record or the first one when `reverse` is set to true\n```\n\nThere are also `#undo!` and `#redo!` options (and more general `#switch_to!`):\n\n```ruby\n# Revert record to the previous state (and stores this state in DB)\npost.undo!\n\n# You can now user redo! to revert back\npost.redo!\n\n# More generally you can revert record to arbitrary version\npost.switch_to!(2)\n```\n\nYou can initiate reloading of `log_data` from the DB:\n\n```ruby\npost.reload_log_data # =\u003e returns the latest log data value\n```\n\nTypically, if you update record after `#undo!` or `#switch_to!` you lose all \"future\" versions and `#redo!` is no\nlonger possible. However, you can provide an `append: true` option to `#undo!` or `#switch_to!`, which will\ncreate a new version with old data. Caveat: when switching to a newer version, `append` will have no effect.\n\n```ruby\npost = Post.create!(title: \"first post\") # v1\npost.update!(title: \"new title\") # v2\npost.undo!(append: true) # v3 (with same attributes as v1)\n```\n\nNote that `redo!` will not work after `undo!(append: true)` because the latter will create a new version\ninstead of rolling back to an old one.\nAlternatively, you can configure Logidze always to default to `append: true`.\n\n```ruby\nLogidze.append_on_undo = true\n```\n\n### Track meta information\n\nYou can store any meta information you want inside your version (it could be IP address, user agent, etc.). To add it you should wrap your code with a block:\n\n```ruby\nLogidze.with_meta({ip: request.ip}) do\n  post.save!\nend\n```\n\n**NOTE:** You should pass metadata as a Hash; passing keyword arguments doesn't work in Ruby 3.0+.\n\nMeta expects a hash to be passed so you won't need to encode and decode JSON manually.\n\nBy default `.with_meta` wraps the block into a DB transaction. That could lead to an unexpected behavior, especially, when using `.with_meta` within an around_action. To avoid wrapping the block into a DB transaction use `transactional: false` option.\n\n```ruby\nLogidze.with_meta({ip: request.ip}, transactional: false) do\n  post.save!\nend\n```\n\n**Important:** If you use connection pooling (e.g., PgBouncer), using `.with_meta` without a transaction may lead to unexpected results (since meta is set for a connection). Without a transaction, we cannot guarantee that the same connection will be used for queries (including metadata cleanup).\n\n**Important**: In Rails, `after_commit` callbacks are executed after transaction is committed, and, thus, after `with_meta` block is executed—the meta wouldn't be added to changes captured in the `after_commit` phase. One particular scenario is having associations with `touch: true` (_touch_ updates are executed after commit).\n\n### Track responsibility\n\nA special application of meta information is storing the author of the change, which is called _Responsible ID_. There is more likely that you would like to store the `current_user.id` that way.\n\nTo provide `responsible_id` you should wrap your code in a block:\n\n```ruby\nLogidze.with_responsible(user.id) do\n  post.save!\nend\n```\n\nAnd then to retrieve `responsible_id`:\n\n```ruby\npost.log_data.responsible_id\n```\n\nLogidze does not require `responsible_id` to be `SomeModel` ID. It can be anything. Thus Logidze does not provide methods for retrieving the corresponding object. However, you can easily write it yourself:\n\n```ruby\nclass Post \u003c ActiveRecord::Base\n  has_logidze\n\n  def whodunnit\n    id = log_data.responsible_id\n    User.find(id) if id.present?\n  end\nend\n```\n\nAnd in your controller:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  around_action :use_logidze_responsible, only: %i[create update]\n\n  def use_logidze_responsible(\u0026block)\n    Logidze.with_responsible(current_user\u0026.id, \u0026block)\n  end\nend\n```\n\nBy default `.with_responsible` wraps the block into a DB transaction. That could lead to an unexpected behavior, especially, when using `.with_responsible` within an around_action. To avoid wrapping the block into a DB transaction use `transactional: false` option.\n\n```ruby\nLogidze.with_responsible(user.id, transactional: false) do\n  post.save!\nend\n```\n\n### Disable logging temporary\n\nIf you want to make update without logging (e.g., mass update), you can turn it off the following way:\n\n```ruby\nLogidze.without_logging { Post.update_all(seen: true) }\n\n# or\n\nPost.without_logging { Post.update_all(seen: true) }\n```\n\n### Reset log\n\nReset the history for a record (or records):\n\n```ruby\n# for a single record\nrecord.reset_log_data\n\n# for relation\nUser.where(active: true).reset_log_data\n```\n\n### Full snapshots\n\nYou can instruct Logidze to create a full snapshot instead of a diff for a particular log entry.\n\nIt could be useful in combination with `.without_logging`: first, you perform multiple updates without logging, then\nyou want to create a log entry with the current state. To do that, you should use the `Logidze.with_full_snapshot` method:\n\n```ruby\nrecord = Model.find(params[:id])\n\nLogidze.without_logging do\n  # perform multiple write operations with record\nend\n\nLogidze.with_full_snapshot do\n  record.touch\nend\n```\n\n### Associations versioning\n\nLogidze also supports associations versioning. This feature is disabled by default (due to the number of edge cases). You can learn more\nin the [wiki](https://github.com/palkan/logidze/wiki/Associations-versioning).\n\n## Dealing with large logs\n\nBy default, Active Record _selects_ all the table columns when no explicit `select` statement specified.\n\nThat could slow down queries execution if you have field values which exceed the size of the data block (typically 8KB). PostgreSQL turns on its [TOAST](https://wiki.postgresql.org/wiki/TOAST) mechanism), which requires reading from multiple physical locations for fetching the row's data.\n\nIf you do not use compaction (`generate logidze:model ... --limit N`) for `log_data`, you're likely to face this problem.\n\nLogidze provides a way to avoid loading `log_data` by default (and load it on demand):\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  # Add `ignore_log_data` option to macros\n  has_logidze ignore_log_data: true\nend\n```\n\nIf you want Logidze to behave this way by default, configure the global option:\n\n```ruby\n# config/initializers/logidze.rb\nLogidze.ignore_log_data_by_default = true\n\n# or\n\n# config/application.rb\nconfig.logidze.ignore_log_data_by_default = true\n```\n\nHowever, you can override it by explicitly passing `ignore_log_data: false` to the `ignore_log_data`.\nYou can also enforce loading `log_data` in-place by using the `.with_log_data` scope, e.g. `User.all.with_log_data` loads all\nthe _users_ with `log_data` included.\n\nThe chart below shows the difference in PG query time before and after turning `ignore_log_data` on. (Special thanks to [@aderyabin](https://github.com/aderyabin) for sharing it.)\n\n![](./assets/pg_log_data_chart.png)\n\nIf you try to call `#log_data` on the model loaded in such way, you'll get `nil`. If you want to fetch log data (e.g., during the console debugging)–use **`user.reload_log_data`**, which forces loading this column from the DB.\n\n## Handling records deletion\n\nUnlike, for example, PaperTrail, Logidze is designed to **only track changes**. If the record has been deleted, **everything is lost**.\n\nIf you want to keep changes history after records deletion as well, consider using specialized tools for soft-delete, such as, [Discard](https://github.com/jhawthorn/discard) or [Paranoia](https://github.com/rubysherpas/paranoia).\n\nSee also the discussion: [#61](https://github.com/palkan/logidze/issues/61).\n\n## Handling PG exceptions\n\nBy default, Logidze raises an exception which causes the entire transaction to fail.\nTo change this behavior, it's now possible to override `logidze_capture_exception(error_data jsonb)` function.\n\nFor example, you may want to raise a warning instead of an exception and complete the transaction without updating log_data.\n\nRelated issues: [#193](https://github.com/palkan/logidze/issues/193)\n\n## Upgrading\n\nWe try to make an upgrade process as simple as possible. For now, the only required action is to create and run a migration:\n\n```sh\nbundle exec rails generate logidze:install --update\n```\n\nThis updates core `logdize_logger` DB function. No need to update tables or triggers.\n\n**NOTE:** When using `fx`, you can omit the `--update` flag. The migration containing only the updated functions would be created.\n\nIf you want to update Logidze settings for the model, run migration with `--update` flag:\n\n```sh\nbundle exec rails generate logidze:model Post --update --only=title,body,rating\n```\n\nYou can also use the `--name` option to specify the migration name to avoid duplicate migration names:\n\n```sh\n$ bundle exec rails generate logidze:model Post --update --only=title,body,rating --name add_only_filter_to_posts_log_data\n\n    create db/migrate/20202309142344_add_only_filter_to_posts_log_data.rb\n```\n\n### Pending upgrade check [Experimental]\n\nLogidze can check for a pending upgrade. Use `Logidze.on_pending_upgrade = :warn` to be notified by warning, or `Logidze.on_pending_upgrade = :raise` if you want Logidze to raise an error.\n\n### Upgrading from 0.x to 1.0 (edge)\n\n#### Schema and migrations\n\nMost SQL function definitions have changed without backward compatibility.\nPerform the following steps to upgrade:\n\n1. Re-install Logidze: `bundle exec rails generate logidze:install --update`.\n\n1. Re-install Logidze triggers **for all models**: `bundle exec rails generate logidze:model \u003cmodel\u003e --update`.\n\n   **NOTE:** If you had previously specified whitelist/blacklist attributes, you will need to include the `--only`/`--except` [option](#tracking-only-selected-columns) as appropriate. You can easily copy these column lists from the previous logidze migration for the model.\n\n1. Remove the `include Logidze::Migration` line from the old migration files (if any)—this module has been removed.\n\nRewrite legacy logidze migrations to not use the `#current_setting(name)` and `#current_setting_missing_supported?` methods, or copy them from the latest [0.x release](https://github.com/palkan/logidze/blob/0-stable/lib/logidze/migration.rb).\n\n#### API changes\n\nThe deprecated `time` positional argument has been removed from `#at` and `#diff_from` methods. Now you need to use keyword arguments, i.e., `model.at(some_tome) -\u003e model.at(time: some_time)`.\n\n## Log format\n\nThe `log_data` column has the following format:\n\n```js\n{\n  \"v\": 2, // current record version,\n  \"h\": // list of changes\n    [\n      {\n        \"v\": 1,  // change number\n        \"ts\": 1460805759352, // change timestamp in milliseconds\n        \"c\": {\n            \"attr\": \"new value\",  // updated fields with new values\n            \"attr2\": \"new value\"\n            },\n        \"r\": 42, // Resposibility ID (if provided), not in use since 0.7.0\n        \"m\": {\n          \"_r\": 42 // Resposibility ID (if provided), in use since 0.7.0\n          // any other meta information provided, please see Track meta information section for the details\n        }\n      }\n    ]\n}\n```\n\nIf you specify the limit in the trigger definition, then log size will not exceed the specified size. When a new change occurs, and there is no more room for it, the two oldest changes will be merged.\n\n## Ordering of Triggers in schema.rb\n\nBy default, when generating `schema.rb`, Rails will order the triggers based on the id's of their respective tables. This can lead to unnecessary changes being made when utilizing `rails db:prepare`, since the ordering of the tables will now be based off the alphabetical ordering (see [#250](https://github.com/palkan/logidze/issues/250) for more details). To force the ordering to be consistent with `rails db:prepare`, Logidze can be configured to order the triggers alphabetically.\n\n```ruby\n# config/initializers/logidze.rb\n\nLogidze.sort_triggers_by_name = true\n```\n\n## Troubleshooting\n\n### `log_data` is nil when using Rails fixtures\n\nRails fixtures are populated with triggers disabled. Thus, `log_data` is null initially for all records.\nYou can use `#create_logidze_snapshot` manually to build initial snapshots.\n\n### How to make this work with Apartment 🤔\n\nFirst, read [Apartment docs](https://github.com/influitive/apartment#installing-extensions-into-persistent-schemas) on installing PostgreSQL extensions. You need to use the described approach to install Hstore (and drop the migration provided by Logidze during installation).\n\nSecondly, set `config.use_sql = true` in the Apartment configuration.\n\nFinally, when using `fx` along with `schema.rb`, you might face a problem with duplicate trigger definitions (for different schemas).\nHere is a patch to fix this: [dump_triggers.rake](etc/dump_triggers.rake).\n\nRelated issues: [#50](https://github.com/palkan/logidze/issues/50).\n\n### `PG::UntranslatableCharacter: ERROR`\n\nThat could happen when your row data contain null bytes. You should sanitize the data before writing to the database.\nFrom the [PostgreSQL docs](https://www.postgresql.org/docs/current/datatype-json.html): `jsonb type also rejects \\u0000 (because that cannot be represented in PostgreSQL's text type)`.\n\nRelated issues: [#155](https://github.com/palkan/logidze/issues/155).\n\n### `pg_restore` fails to restore a dump\n\nFirst, when restoring data dumps you should consider using `--disable-triggers` option (unless you have a strong reason to invoke the triggers).\n\nWhen restoring data dumps for a particular PostgreSQL schema (e.g., when using Apartment), you may encounter the issue with non-existent Logidze functions. That happens because `pg_dump` adds `SELECT pg_catalog.set_config('search_path', '', false);`, and, thus, breaks our existing triggers/functions, because they live either in \"public\" or in a tenant's namespace (see [this thread](https://postgrespro.com/list/thread-id/2448092)).\n\n### `PG::NumericValueOutOfRange: ERROR: value overflows numeric format`\n\nDue to the usage of `hstore_to_jsonb_loose` under the hood, there could be a situation when you have a string representing a number in the scientific notation (e.g., \"557236406134e62000323100\"). Postgres would try to convert it to a number (a pretty big one, for sure) and fail with the exception.\n\nRelated issues: [#69](https://github.com/palkan/logidze/issues/69).\n\n## Development\n\nThis project requires a PostgreSQL instance running with the following setup:\n\n```sh\n# For testing\ncreatedb -h postgres -U postgres logidze_test\n\n# For benchmarks\ncreatedb -h postgres -U postgres logidze_bench\ncreatedb -h postgres -U postgres logidze_perf_bench\npsql -d logidze_bench -c 'CREATE EXTENSION IF NOT EXISTS hstore;'\n```\n\nThis project is compatible with [Reusable Docker environment](https://evilmartians.com/chronicles/reusable-development-containers-with-docker-compose-and-dip) setup.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at [https://github.com/palkan/logidze](https://github.com/palkan/logidze).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n[fx]: https://github.com/teoljungberg/fx\n[bench results]: https://github.com/palkan/logidze/blob/feat-log-data-separate-storage/bench/performance/README.md\n","funding_links":[],"categories":["Auditing","Ruby","1. language","ORM/ODM Extensions","Gems"],"sub_categories":["1.1 ruby","Soft Deletes and Versioning"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpalkan%2Flogidze","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpalkan%2Flogidze","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpalkan%2Flogidze/lists"}