From 52418c077c89d71bbb9cf626343b2a7ba15cb326 Mon Sep 17 00:00:00 2001 From: Ben Atkins Date: Wed, 16 Oct 2013 15:28:16 -0400 Subject: [PATCH] Adding support for PostgreSQL's JSON column type --- CHANGELOG.md | 2 ++ lib/paper_trail/has_paper_trail.rb | 35 +++++++++++++++++++----------- lib/paper_trail/version.rb | 19 +++++++++++++--- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c8d74e..f15dbdce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## 3.0.0 (Unreleased) + - [#287](https://github.com/airblade/paper_trail/issues/287) - Support for + [PostgreSQL's JSON Type](http://www.postgresql.org/docs/9.2/static/datatype-json.html) for storing `object` and `object_changes`. - [#281](https://github.com/airblade/paper_trail/issues/281) - `Rails::Controller` helper will return `false` for the `paper_trail_enabled_for_controller` method if `PaperTrail.enabled? == false`. - [#280](https://github.com/airblade/paper_trail/pull/280) - Don't track virtual timestamp attributes. diff --git a/lib/paper_trail/has_paper_trail.rb b/lib/paper_trail/has_paper_trail.rb index bc296bdf..ec4312e0 100644 --- a/lib/paper_trail/has_paper_trail.rb +++ b/lib/paper_trail/has_paper_trail.rb @@ -204,7 +204,8 @@ module PaperTrail } if changed_notably? and version_class.column_names.include?('object_changes') - data[:object_changes] = PaperTrail.serializer.dump(changes_for_paper_trail) + data[:object_changes] = version_class.object_changes_col_is_json? ? changes_for_paper_trail : + PaperTrail.serializer.dump(changes_for_paper_trail) end send(self.class.versions_association_name).create! merge_metadata(data) end @@ -212,13 +213,15 @@ module PaperTrail def record_update if paper_trail_switched_on? && changed_notably? + object_attrs = object_attrs_for_paper_trail(item_before_change) data = { :event => paper_trail_event || 'update', - :object => object_to_string(item_before_change), + :object => version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs), :whodunnit => PaperTrail.whodunnit } - if version_class.column_names.include? 'object_changes' - data[:object_changes] = PaperTrail.serializer.dump(changes_for_paper_trail) + if version_class.column_names.include?('object_changes') + data[:object_changes] = version_class.object_changes_col_is_json? ? changes_for_paper_trail : + PaperTrail.serializer.dump(changes_for_paper_trail) end send(self.class.versions_association_name).build merge_metadata(data) end @@ -228,17 +231,22 @@ module PaperTrail self.changes.delete_if do |key, value| !notably_changed.include?(key) end.tap do |changes| - self.class.serialize_attribute_changes(changes) # Use serialized value for attributes when necessary + # don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases + self.class.serialize_attribute_changes(changes) unless version_class.object_changes_col_is_json? end end def record_destroy if paper_trail_switched_on? and not new_record? - version_class.create merge_metadata(:item_id => self.id, - :item_type => self.class.base_class.name, - :event => paper_trail_event || 'destroy', - :object => object_to_string(item_before_change), - :whodunnit => PaperTrail.whodunnit) + object_attrs = object_attrs_for_paper_trail(item_before_change) + data = { + :item_id => self.id, + :item_type => self.class.base_class.name, + :event => paper_trail_event || 'destroy', + :object => version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs), + :whodunnit => PaperTrail.whodunnit + } + version_class.create merge_metadata(data) send(self.class.versions_association_name).send :load_target end end @@ -276,11 +284,12 @@ module PaperTrail end end - def object_to_string(object) + # returns hash of object attributes (with appropriate attributes serialized), ommitting attributes to be skipped + def object_attrs_for_paper_trail(object) _attrs = object.attributes.except(*self.paper_trail_options[:skip]).tap do |attributes| - self.class.serialize_attributes_for_paper_trail attributes + # don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases + self.class.serialize_attributes_for_paper_trail attributes unless version_class.object_changes_col_is_json? end - PaperTrail.serializer.dump(_attrs) end def changed_notably? diff --git a/lib/paper_trail/version.rb b/lib/paper_trail/version.rb index b4f21f5d..7ee92c65 100644 --- a/lib/paper_trail/version.rb +++ b/lib/paper_trail/version.rb @@ -41,6 +41,16 @@ module PaperTrail order("#{PaperTrail.timestamp_field} ASC") } + # Returns whether the `object` column is using the `json` type supported by PostgreSQL + def self.object_col_is_json? + @object_col_is_json ||= columns_hash['object'].type == :json + end + + # Returns whether the `object_changes` column is using the `json` type supported by PostgreSQL + def self.object_changes_col_is_json? + @object_changes_col_is_json ||= columns_hash['object_changes'].type == :json + end + # Restore the item from this version. # # This will automatically restore all :has_one associations as they were "at the time", @@ -58,7 +68,7 @@ module PaperTrail options.reverse_merge! :has_one => false unless object.nil? - attrs = PaperTrail.serializer.load object + attrs = self.class.object_col_is_json? ? object : PaperTrail.serializer.load(object) # Normally a polymorphic belongs_to relationship allows us # to get the object we belong to by calling, in this case, @@ -111,9 +121,12 @@ module PaperTrail def changeset return nil unless self.class.column_names.include? 'object_changes' - @changeset ||= HashWithIndifferentAccess.new(PaperTrail.serializer.load(object_changes)).tap do |changes| + _changes = self.class.object_changes_col_is_json? ? object_changes : PaperTrail.serializer.load(object_changes) + @changeset ||= HashWithIndifferentAccess.new(_changes).tap do |changes| item_type.constantize.unserialize_attribute_changes(changes) - end rescue {} + end + rescue + {} end # Returns who put the item into the state stored in this version.