diff --git a/README.md b/README.md index d17ede9b..a2e99c5a 100644 --- a/README.md +++ b/README.md @@ -727,6 +727,16 @@ Or a block: end ``` +## Using a custom serializer + +By default, PaperTrail stores your changes as a YAML dump. You can override this with the serializer config option: + +```ruby +>> PaperTrail.serializer = MyCustomSerializer +``` + +The serializer needs to be a class that responds to a `load` and `dump` method. + ## Deleting Old Versions Over time your `versions` table will grow to an unwieldy size. Because each version is self-contained (see the Diffing section above for more) you can simply delete any records you don't want any more. For example: diff --git a/lib/paper_trail.rb b/lib/paper_trail.rb index 22453b9f..e52f6278 100644 --- a/lib/paper_trail.rb +++ b/lib/paper_trail.rb @@ -5,6 +5,7 @@ require 'paper_trail/config' require 'paper_trail/controller' require 'paper_trail/has_paper_trail' require 'paper_trail/version' +require 'paper_trail/serializers/yaml' # PaperTrail's module methods can be called in both models and controllers. module PaperTrail @@ -69,6 +70,9 @@ module PaperTrail paper_trail_store[:controller_info] = value end + def self.serializer + PaperTrail.config.serializer + end private diff --git a/lib/paper_trail/config.rb b/lib/paper_trail/config.rb index 2e6ea14d..5f176148 100644 --- a/lib/paper_trail/config.rb +++ b/lib/paper_trail/config.rb @@ -1,12 +1,13 @@ module PaperTrail class Config include Singleton - attr_accessor :enabled, :timestamp_field - + attr_accessor :enabled, :timestamp_field, :serializer + def initialize # Indicates whether PaperTrail is on or off. @enabled = true @timestamp_field = :created_at + @serializer = PaperTrail::Serializers::Yaml end end end diff --git a/lib/paper_trail/has_paper_trail.rb b/lib/paper_trail/has_paper_trail.rb index 880fc1f3..3d6887d0 100644 --- a/lib/paper_trail/has_paper_trail.rb +++ b/lib/paper_trail/has_paper_trail.rb @@ -162,9 +162,10 @@ module PaperTrail } if version_class.column_names.include? 'object_changes' # The double negative (reject, !include?) preserves the hash structure of self.changes. - data[:object_changes] = self.changes.reject do |key, value| + changes = self.changes.reject do |key, value| !notably_changed.include?(key) - end.to_yaml + end + data[:object_changes] = PaperTrail.serializer.dump(changes) end send(self.class.versions_association_name).build merge_metadata(data) end @@ -215,7 +216,7 @@ module PaperTrail end def object_to_string(object) - object.attributes.except(*self.class.skip).to_yaml + PaperTrail.serializer.dump object.attributes.except(*self.class.skip) end def changed_notably? diff --git a/lib/paper_trail/serializers/yaml.rb b/lib/paper_trail/serializers/yaml.rb new file mode 100644 index 00000000..4064912c --- /dev/null +++ b/lib/paper_trail/serializers/yaml.rb @@ -0,0 +1,23 @@ +require 'yaml' + +module PaperTrail + module Serializers + class Yaml + def self.load(string) + new.load(string) + end + + def self.dump(hash) + new.dump(hash) + end + + def load(string) + YAML.load(string) + end + + def dump(hash) + YAML.dump(hash) + end + end + end +end \ No newline at end of file diff --git a/lib/paper_trail/version.rb b/lib/paper_trail/version.rb index 7e4b4cc3..bc762a3e 100644 --- a/lib/paper_trail/version.rb +++ b/lib/paper_trail/version.rb @@ -55,7 +55,7 @@ class Version < ActiveRecord::Base options.reverse_merge! :has_one => false unless object.nil? - attrs = YAML::load object + attrs = PaperTrail.serializer.load object # Normally a polymorphic belongs_to relationship allows us # to get the object we belong to by calling, in this case, @@ -103,7 +103,7 @@ class Version < ActiveRecord::Base def changeset if self.class.column_names.include? 'object_changes' if changes = object_changes - HashWithIndifferentAccess[YAML::load(changes)] + HashWithIndifferentAccess[PaperTrail.serializer.load(changes)] else {} end diff --git a/test/unit/model_test.rb b/test/unit/model_test.rb index b5683b1d..97dc3159 100644 --- a/test/unit/model_test.rb +++ b/test/unit/model_test.rb @@ -49,11 +49,11 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase end should 'have removed the skipped attributes when saving the previous version' do - assert_equal nil, YAML::load(@old_article.object)['file_upload'] + assert_equal nil, PaperTrail.serializer.load(@old_article.object)['file_upload'] end should 'have kept the non-skipped attributes in the previous version' do - assert_equal 'Some text here.', YAML::load(@old_article.object)['content'] + assert_equal 'Some text here.', PaperTrail.serializer.load(@old_article.object)['content'] end end end @@ -184,7 +184,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase end should 'have stored changes' do - assert_equal ({'name' => ['Henry', 'Harry']}), YAML::load(@widget.versions.last.object_changes) + assert_equal ({'name' => ['Henry', 'Harry']}), PaperTrail.serializer.load(@widget.versions.last.object_changes) assert_equal ({'name' => ['Henry', 'Harry']}), @widget.versions.last.changeset end diff --git a/test/unit/serializer_test.rb b/test/unit/serializer_test.rb new file mode 100644 index 00000000..28521528 --- /dev/null +++ b/test/unit/serializer_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class CustomSerializer + require 'json' + def self.dump(object_hash) + JSON.dump object_hash + end + + def self.load(string) + JSON.parse string + end +end + +class SerializerTest < ActiveSupport::TestCase + + context 'YAML Serializer' do + setup do + Fluxor.instance_eval <<-END + has_paper_trail + END + + @fluxor = Fluxor.create :name => 'Some text.' + @fluxor.update_attributes :name => 'Some more text.' + end + + should 'work with the default yaml serializer' do + # Normal behaviour + assert_equal 2, @fluxor.versions.length + assert_nil @fluxor.versions[0].reify + assert_equal 'Some text.', @fluxor.versions[1].reify.name + + + # Check values are stored as YAML. + assert_equal "---\nwidget_id: \nname: Some text.\nid: 1\n", @fluxor.versions[1].object + assert_equal ({"widget_id" => nil,"name" =>"Some text.","id" =>1}), YAML.load(@fluxor.versions[1].object) + + end + end + + context 'Custom Serializer' do + setup do + PaperTrail.config.serializer = CustomSerializer + + Fluxor.instance_eval <<-END + has_paper_trail + END + + @fluxor = Fluxor.create :name => 'Some text.' + @fluxor.update_attributes :name => 'Some more text.' + end + + teardown do + PaperTrail.config.serializer = PaperTrail::Serializers::Yaml + end + + should 'work with custom serializer' do + # Normal behaviour + assert_equal 2, @fluxor.versions.length + assert_nil @fluxor.versions[0].reify + assert_equal 'Some text.', @fluxor.versions[1].reify.name + + # Check values are stored as JSON. + assert_equal "{\"widget_id\":null,\"name\":\"Some text.\",\"id\":1}", @fluxor.versions[1].object + assert_equal ({"widget_id" => nil,"name" =>"Some text.","id" =>1}), JSON.parse(@fluxor.versions[1].object) + + end + end + +end