From d6929e5a091140d118911d3453635baca2fafc7f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 14 Oct 2020 11:57:31 +0200 Subject: [PATCH] Handle binary strings in Active Record serialized columns Serialized attributes stored in BLOB columns will be loaded with the `ASCII-8BIT` (AKA BINARY) encoding. So unless the serialized payload is pure ASCII, they need to have the same internal encoding to be properly compared. Since the serializer have no way to know how the string will be stored, it's up to the column type to properly set the encoding. --- activerecord/lib/active_record/type/serialized.rb | 7 +++++-- .../test/cases/serialized_attribute_test.rb | 15 +++++++++++++++ activerecord/test/models/binary_field.rb | 6 ++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 activerecord/test/models/binary_field.rb diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index a34b2fe702..2e4d2e0bf8 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -61,9 +61,12 @@ module ActiveRecord end def encoded(value) - unless default_value?(value) - coder.dump(value) + return if default_value?(value) + payload = coder.dump(value) + if payload && binary? + payload.force_encoding(Encoding::BINARY) end + payload end end end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index a65a919975..19a6661f9e 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -4,6 +4,7 @@ require "cases/helper" require "models/person" require "models/traffic_light" require "models/post" +require "models/binary_field" class SerializedAttributeTest < ActiveRecord::TestCase fixtures :topics, :posts @@ -317,6 +318,20 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_equal({}, topic.content) end + if current_adapter?(:Mysql2Adapter) + def test_is_not_changed_when_stored_in_mysql_blob + value = %w(Fée) + model = BinaryField.create!(normal_blob: value, normal_text: value) + model.reload + + model.normal_text = value + assert_not_predicate model, :normal_text_changed? + + model.normal_blob = value + assert_not_predicate model, :normal_blob_changed? + end + end + def test_values_cast_from_nil_are_persisted_as_nil # This is required to fulfil the following contract, which must be universally # true in Active Record: diff --git a/activerecord/test/models/binary_field.rb b/activerecord/test/models/binary_field.rb new file mode 100644 index 0000000000..9032ae8075 --- /dev/null +++ b/activerecord/test/models/binary_field.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class BinaryField < ActiveRecord::Base + serialize :normal_blob + serialize :normal_text +end