Fix deserialization of enums written by PT 4

Fixes #855
This commit is contained in:
Jared Beck 2016-09-02 17:49:08 -04:00
parent 7d60a41e13
commit 7884c143b6
3 changed files with 49 additions and 22 deletions

View File

@ -14,6 +14,8 @@
### Fixed
- [#857](https://github.com/airblade/paper_trail/pull/857) -
Fix deserialization of enums written by PT 4.
- [#798](https://github.com/airblade/paper_trail/issues/798) -
Fix a rare bug with serialization of enums in rails 4.2 only when
using `touch_with_version`.

View File

@ -8,19 +8,41 @@ module PaperTrail
# This implementation depends on the `type_for_attribute` method, which was
# introduced in rails 4.2. In older versions of rails, we shim this method
# with `LegacyActiveRecordShim`.
class CastAttributeSerializer
def initialize(klass)
@klass = klass
end
private
# Returns a hash mapping attributes to hashes that map strings to
# integers. Example:
#
# ```
# { "status" => { "draft"=>0, "published"=>1, "archived"=>2 } }
# ```
#
# ActiveRecord::Enum was added in AR 4.1
# http://edgeguides.rubyonrails.org/4_1_release_notes.html#active-record-enums
def defined_enums
@defined_enums ||= (@klass.respond_to?(:defined_enums) ? @klass.defined_enums : {})
end
end
if ::ActiveRecord::VERSION::MAJOR >= 5
# This implementation uses AR 5's `serialize` and `deserialize`.
class CastAttributeSerializer
def initialize(klass)
@klass = klass
end
def serialize(attr, val)
@klass.type_for_attribute(attr).serialize(val)
end
def deserialize(attr, val)
@klass.type_for_attribute(attr).deserialize(val)
if defined_enums[attr] && val.is_a?(::String)
# Because PT 4 used to save the string version of enums to `object_changes`
val
else
@klass.type_for_attribute(attr).deserialize(val)
end
end
end
else
@ -29,10 +51,6 @@ module PaperTrail
# `type_cast_for_database` in our shim attribute type classes,
# `NoOpAttribute` and `SerializedAttribute`.
class CastAttributeSerializer
def initialize(klass)
@klass = klass
end
def serialize(attr, val)
castable_val = val
if defined_enums[attr]
@ -44,21 +62,18 @@ module PaperTrail
end
def deserialize(attr, val)
val = @klass.type_for_attribute(attr).type_cast_from_database(val)
if defined_enums[attr]
defined_enums[attr].key(val)
else
if defined_enums[attr] && val.is_a?(::String)
# Because PT 4 used to save the string version of enums to `object_changes`
val
else
val = @klass.type_for_attribute(attr).type_cast_from_database(val)
if defined_enums[attr]
defined_enums[attr].key(val)
else
val
end
end
end
private
# ActiveRecord::Enum was added in AR 4.1
# http://edgeguides.rubyonrails.org/4_1_release_notes.html#active-record-enums
def defined_enums
@defined_enums ||= (@klass.respond_to?(:defined_enums) ? @klass.defined_enums : {})
end
end
end
end

View File

@ -5,7 +5,7 @@ require "rails_helper"
describe PostWithStatus, type: :model do
if defined?(ActiveRecord::Enum)
with_versioning do
let(:post) { PostWithStatus.create!(status: "draft") }
let(:post) { described_class.create!(status: "draft") }
it "should stash the enum value properly in versions" do
post.published!
@ -13,6 +13,16 @@ describe PostWithStatus, type: :model do
expect(post.paper_trail.previous_version.published?).to be true
end
it "can read enums in version records written by PT 4" do
post = described_class.create(status: "draft")
post.published!
version = post.versions.last
# Simulate behavior PT 4, which used to save the string version of
# enums to `object_changes`
version.update(object_changes: "---\nid:\n- \n- 1\nstatus:\n- draft\n- published\n")
assert_equal %w(draft published), version.changeset["status"]
end
context "storing enum object_changes" do
subject { post.versions.last }