Skip to content

Commit 46e797e

Browse files
committed
Merge pull request #48274 from Shopify/fix-serialized-blob-column-changed-in-place
Fix change_in_place? for binary serialized columns
1 parent da309a5 commit 46e797e

File tree

9 files changed

+52
-43
lines changed

9 files changed

+52
-43
lines changed

activemodel/lib/active_model/type/binary.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ def changed_in_place?(raw_old_value, value)
3131

3232
class Data # :nodoc:
3333
def initialize(value)
34-
@value = value.to_s
34+
value = value.to_s
35+
value = value.b unless value.encoding == Encoding::BINARY
36+
@value = value
3537
end
3638

3739
def to_s

activemodel/test/cases/type/binary_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ def test_type_cast_binary
1111
assert_equal "1", type.cast("1")
1212
assert_equal 1, type.cast(1)
1313
end
14+
15+
def test_serialize_binary_strings
16+
type = Type::Binary.new
17+
assert_equal "Ďe".b, type.serialize("Ďe")
18+
assert_not_equal "Ďe", type.serialize("Ďe")
19+
end
1420
end
1521
end
1622
end

activerecord/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Fix mutation detection for serialized attributes backed by binary columns.
2+
3+
*Jean Boussier*
4+
15
* Fix a bug where using groups and counts with long table names would return incorrect results.
26

37
*Shota Toguchi*, *Yusaku Ono*

activerecord/lib/active_record/type/serialized.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ def default_value?(value)
6363
def encoded(value)
6464
return if default_value?(value)
6565
payload = coder.dump(value)
66-
if payload && binary? && payload.encoding != Encoding::BINARY
67-
payload = payload.dup if payload.frozen?
68-
payload.force_encoding(Encoding::BINARY)
66+
if payload && @subtype.binary?
67+
ActiveModel::Type::Binary::Data.new(payload)
68+
else
69+
payload
6970
end
70-
payload
7171
end
7272
end
7373
end

activerecord/test/cases/core_test.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ def test_inspect_class
1818

1919
def test_inspect_instance
2020
topic = topics(:first)
21-
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "[email protected]", written_on: "#{topic.written_on.to_fs(:inspect)}", bonus_time: "#{topic.bonus_time.to_fs(:inspect)}", last_read: "#{topic.last_read.to_fs(:inspect)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_fs(:inspect)}", updated_at: "#{topic.updated_at.to_fs(:inspect)}">), topic.inspect
21+
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "[email protected]", written_on: "#{topic.written_on.to_fs(:inspect)}", bonus_time: "#{topic.bonus_time.to_fs(:inspect)}", last_read: "#{topic.last_read.to_fs(:inspect)}", content: "Have a nice day", important: nil, binary_content: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_fs(:inspect)}", updated_at: "#{topic.updated_at.to_fs(:inspect)}">), topic.inspect
2222
end
2323

2424
def test_inspect_instance_with_lambda_date_formatter
2525
before = Time::DATE_FORMATS[:inspect]
2626
Time::DATE_FORMATS[:inspect] = ->(date) { "my_format" }
2727
topic = topics(:first)
2828

29-
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "[email protected]", written_on: "my_format", bonus_time: "my_format", last_read: "2004-04-15", content: "Have a nice day", important: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "my_format", updated_at: "my_format">), topic.inspect
29+
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "[email protected]", written_on: "my_format", bonus_time: "my_format", last_read: "2004-04-15", content: "Have a nice day", important: nil, binary_content: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "my_format", updated_at: "my_format">), topic.inspect
3030

3131
ensure
3232
Time::DATE_FORMATS[:inspect] = before
@@ -65,6 +65,7 @@ def test_pretty_print_new
6565
last_read: nil,
6666
content: nil,
6767
important: nil,
68+
binary_content: nil,
6869
approved: true,
6970
replies_count: 0,
7071
unique_replies_count: 0,
@@ -94,6 +95,7 @@ def test_pretty_print_persisted
9495
last_read: Thu, 15 Apr 2004,
9596
content: "Have a nice day",
9697
important: nil,
98+
binary_content: nil,
9799
approved: false,
98100
replies_count: 1,
99101
unique_replies_count: 0,

activerecord/test/cases/reflection_test.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,25 @@ def test_human_name
4444

4545
def test_read_attribute_names
4646
assert_equal(
47-
%w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort,
47+
%w( id title author_name author_email_address bonus_time written_on last_read content important binary_content group approved replies_count unique_replies_count parent_id parent_title type created_at updated_at ).sort,
4848
@first.attribute_names.sort
4949
)
5050
end
5151

5252
def test_columns
53-
assert_equal 18, Topic.columns.length
53+
assert_equal 19, Topic.columns.length
5454
end
5555

5656
def test_columns_are_returned_in_the_order_they_were_declared
5757
column_names = Topic.columns.map(&:name)
58-
assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
58+
assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important binary_content approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names
5959
end
6060

6161
def test_content_columns
6262
content_columns = Topic.content_columns
6363
content_column_names = content_columns.map(&:name)
64-
assert_equal 13, content_columns.length
65-
assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort
64+
assert_equal 14, content_columns.length
65+
assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important binary_content group approved parent_title created_at updated_at).sort, content_column_names.sort
6666
end
6767

6868
def test_column_string_type_and_limit

activerecord/test/cases/serialized_attribute_test.rb

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
require "models/person"
55
require "models/traffic_light"
66
require "models/post"
7-
require "models/binary_field"
87

98
class SerializedAttributeTest < ActiveRecord::TestCase
109
def setup
@@ -358,36 +357,37 @@ def test_newly_emptied_serialized_hash_is_changed
358357
assert_equal({}, topic.content)
359358
end
360359

361-
if current_adapter?(:Mysql2Adapter)
362-
def test_is_not_changed_when_stored_in_mysql_blob
363-
value = %w(Fée)
364-
model = BinaryField.create!(normal_blob: value, normal_text: value)
365-
model.reload
360+
def test_is_not_changed_when_stored_blob
361+
Topic.serialize(:binary_content, type: Array)
362+
Topic.serialize(:content, type: Array)
366363

367-
model.normal_text = value
368-
assert_not_predicate model, :normal_text_changed?
364+
value = %w(Fée)
365+
model = Topic.create!(binary_content: value, content: value)
366+
model.reload
369367

370-
model.normal_blob = value
371-
assert_not_predicate model, :normal_blob_changed?
372-
end
368+
model.binary_content = value
369+
assert_not_predicate model, :binary_content_changed?
373370

374-
class FrozenBinaryField < BinaryField
375-
class FrozenCoder < ActiveRecord::Coders::YAMLColumn
376-
def dump(obj)
377-
super&.freeze
378-
end
379-
end
380-
serialize :normal_blob, FrozenCoder.new(:normal_blob, Array)
371+
model.content = value
372+
assert_not_predicate model, :content_changed?
373+
end
374+
375+
class FrozenCoder < ActiveRecord::Coders::YAMLColumn
376+
def dump(obj)
377+
super&.freeze
381378
end
379+
end
382380

383-
def test_is_not_changed_when_stored_in_mysql_blob_frozen_payload
384-
value = %w(Fée)
385-
model = FrozenBinaryField.create!(normal_blob: value, normal_text: value)
386-
model.reload
381+
def test_is_not_changed_when_stored_in_blob_frozen_payload
382+
Topic.serialize(:binary_content, coder: FrozenCoder.new(:binary_content, Array))
383+
Topic.serialize(:content, coder: FrozenCoder.new(:content, Array))
387384

388-
model.normal_blob = value
389-
assert_not_predicate model, :normal_blob_changed?
390-
end
385+
value = %w(Fée)
386+
model = Topic.create!(binary_content: value, content: value)
387+
model.reload
388+
389+
model.content = value
390+
assert_not_predicate model, :content_changed?
391391
end
392392

393393
def test_values_cast_from_nil_are_persisted_as_nil

activerecord/test/models/binary_field.rb

Lines changed: 0 additions & 6 deletions
This file was deleted.

activerecord/test/schema/schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@
11031103
t.text :content
11041104
t.text :important
11051105
end
1106+
t.blob :binary_content
11061107
t.boolean :approved, default: true
11071108
t.integer :replies_count, default: 0
11081109
t.integer :unique_replies_count, default: 0

0 commit comments

Comments
 (0)