From 68ecf779dae09627a2fba0775eb399f79ed3f0e9 Mon Sep 17 00:00:00 2001 From: Sean Linsley Date: Tue, 24 Jul 2018 10:04:53 -0500 Subject: [PATCH] make the object column optional --- CHANGELOG.md | 4 +- README.md | 6 +++ lib/paper_trail/events/base.rb | 7 +++ lib/paper_trail/events/destroy.rb | 4 +- lib/paper_trail/events/update.rb | 4 +- .../queries/versions/where_object.rb | 5 +- lib/paper_trail/version_concern.rb | 3 ++ spec/paper_trail/model_spec.rb | 51 +++++++++++++++++++ 8 files changed, 80 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a3893dd..38632868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/). ### Added -- None +- [#1099](https://github.com/paper-trail-gem/paper_trail/issues/1099) - + Ability to save ~50% storage space by making the `object` column optional. + Note that this disables `reify` and `where_object`. ### Fixed diff --git a/README.md b/README.md index 51224501..4f74834e 100644 --- a/README.md +++ b/README.md @@ -1212,6 +1212,12 @@ A valid adapter is a class that contains the following methods: For an example of such an implementation, see [paper_trail-hashdiff](https://github.com/hashwin/paper_trail-hashdiff) +### 6.d. Excluding the Object Column + +The `object` column ends up storing a lot of duplicate data if you have models that have many columns, +and that are updated many times. You can save ~50% of storage space by removing the column from the +versions table. It's important to note that this will disable `reify` and `where_object`. + ## 7. Testing You may want to turn PaperTrail off to speed up your tests. See [Turning diff --git a/lib/paper_trail/events/base.rb b/lib/paper_trail/events/base.rb index 461c2cf4..59845c48 100644 --- a/lib/paper_trail/events/base.rb +++ b/lib/paper_trail/events/base.rb @@ -254,6 +254,13 @@ module PaperTrail @record.class.paper_trail.version_class.column_names.include?("object_changes") end + # Returns a boolean indicating whether to store the original object during save. + # + # @api private + def record_object? + @record.class.paper_trail.version_class.column_names.include?("object") + end + # Returns an object which can be assigned to the `object` attribute of a # nascent version record. If the `object` column is a postgres `json` # column, then a hash can be used in the assignment, otherwise the column diff --git a/lib/paper_trail/events/destroy.rb b/lib/paper_trail/events/destroy.rb index 4becbae5..3b029975 100644 --- a/lib/paper_trail/events/destroy.rb +++ b/lib/paper_trail/events/destroy.rb @@ -16,9 +16,11 @@ module PaperTrail item_id: @record.id, item_type: @record.class.base_class.name, event: @record.paper_trail_event || "destroy", - object: recordable_object(false), whodunnit: PaperTrail.request.whodunnit } + if record_object? + data[:object] = recordable_object(false) + end merge_metadata_into(data) end end diff --git a/lib/paper_trail/events/update.rb b/lib/paper_trail/events/update.rb index 4a3a99c2..20b5b688 100644 --- a/lib/paper_trail/events/update.rb +++ b/lib/paper_trail/events/update.rb @@ -27,12 +27,14 @@ module PaperTrail def data data = { event: @record.paper_trail_event || "update", - object: recordable_object(@is_touch), whodunnit: PaperTrail.request.whodunnit } if @record.respond_to?(:updated_at) data[:created_at] = @record.updated_at end + if record_object? + data[:object] = recordable_object(@is_touch) + end if record_object_changes? data[:object_changes] = recordable_object_changes(@changes) end diff --git a/lib/paper_trail/queries/versions/where_object.rb b/lib/paper_trail/queries/versions/where_object.rb index 2120baca..7b277da8 100644 --- a/lib/paper_trail/queries/versions/where_object.rb +++ b/lib/paper_trail/queries/versions/where_object.rb @@ -18,7 +18,10 @@ module PaperTrail # @api private def execute - case @version_model_class.columns_hash["object"].type + column = @version_model_class.columns_hash["object"] + raise "where_object can't be called without an object column" unless column + + case column.type when :jsonb jsonb when :json diff --git a/lib/paper_trail/version_concern.rb b/lib/paper_trail/version_concern.rb index da8a7bf3..86150e0a 100644 --- a/lib/paper_trail/version_concern.rb +++ b/lib/paper_trail/version_concern.rb @@ -205,6 +205,9 @@ module PaperTrail # - `:preserve` - Attributes undefined in version record are not modified. # def reify(options = {}) + unless self.class.column_names.include? "object" + raise "reify can't be called without an object column" + end return nil if object.nil? ::PaperTrail::Reifier.reify(self, options) end diff --git a/spec/paper_trail/model_spec.rb b/spec/paper_trail/model_spec.rb index 627e190b..bcd49c63 100644 --- a/spec/paper_trail/model_spec.rb +++ b/spec/paper_trail/model_spec.rb @@ -877,4 +877,55 @@ RSpec.describe(::PaperTrail, versioning: true) do expect(widget.versions.empty?).to(eq(true)) end end + + context "without the object column" do + # rubocop:disable RSpec/BeforeAfterAll + before :all do + ActiveRecord::Migration.remove_column :versions, :object + PaperTrail::Version.reset_column_information + end + after :all do + ActiveRecord::Migration.add_column :versions, :object, :text + PaperTrail::Version.reset_column_information + end + # rubocop:enable RSpec/BeforeAfterAll + + it "versions are created" do + song = Song.create length: 4 + version = song.versions.last.attributes + expect(version).not_to include "object" + expect(version["event"]).to eq "create" + expect(version["object_changes"]).to start_with("---") + + song.update length: 5 + version = song.versions.last.attributes + expect(version).not_to include "object" + expect(version["event"]).to eq "update" + expect(version["object_changes"]).to start_with("---") + + song.destroy + version = song.versions.last.attributes + expect(version).not_to include "object" + expect(version["event"]).to eq "destroy" + expect(version["object_changes"]).to eq nil + end + + it "reify doesn't work" do + song = Song.create length: 4 + song.update length: 5 + + expect do + song.versions.first.reify + end.to raise_error "reify can't be called without an object column" + end + + it "where_object doesn't work" do + song = Song.create length: 4 + song.update length: 5 + + expect do + song.versions.where_object length: 4 + end.to raise_error "where_object can't be called without an object column" + end + end end