mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
c4cb6862ba
This reduces the size of a YAML encoded Active Record object by ~80% depending on the number of columns. There were a number of wasteful things that occurred when we encoded the objects before that have resulted in numerous wins - We were emitting the result of `attributes_before_type_cast` as a hack to work around some laziness issues - The name of an attribute was emitted multiple times, since the attribute objects were in a hash keyed by the name. We now store them in an array instead, and reconstruct the hash using the name - The types were included for every attribute. This would use backrefs if multiple objects were encoded, but really we don't need to include it at all unless it differs from the type at the class level. (The only time that will occur is if the field is the result of a custom select clause) - `original_attribute:` was included over and over and over again since the ivar is almost always `nil`. We've added a custom implementation of `encode_with` on the attribute objects to ensure we don't write the key when the field is `nil`. This isn't without a cost though. Since we're no longer including the types, an object can find itself in an invalid state if the type changes on the class after serialization. This is the same as 4.1 and earlier, but I think it's worth noting. I was worried that I'd introduce some new state bugs as a result of doing this, so I've added an additional test that asserts mutation not being lost as the result of YAML round tripping. Fixes #25145
131 lines
3.6 KiB
Ruby
131 lines
3.6 KiB
Ruby
require 'cases/helper'
|
|
require 'models/topic'
|
|
require 'models/reply'
|
|
require 'models/post'
|
|
require 'models/author'
|
|
|
|
class YamlSerializationTest < ActiveRecord::TestCase
|
|
fixtures :topics, :authors, :posts
|
|
|
|
def test_to_yaml_with_time_with_zone_should_not_raise_exception
|
|
with_timezone_config aware_attributes: true, zone: "Pacific Time (US & Canada)" do
|
|
topic = Topic.new(:written_on => DateTime.now)
|
|
assert_nothing_raised { topic.to_yaml }
|
|
end
|
|
end
|
|
|
|
def test_roundtrip
|
|
topic = Topic.first
|
|
assert topic
|
|
t = YAML.load YAML.dump topic
|
|
assert_equal topic, t
|
|
end
|
|
|
|
def test_roundtrip_serialized_column
|
|
topic = Topic.new(:content => {:omg=>:lol})
|
|
assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content)
|
|
end
|
|
|
|
def test_psych_roundtrip
|
|
topic = Topic.first
|
|
assert topic
|
|
t = Psych.load Psych.dump topic
|
|
assert_equal topic, t
|
|
end
|
|
|
|
def test_psych_roundtrip_new_object
|
|
topic = Topic.new
|
|
assert topic
|
|
t = Psych.load Psych.dump topic
|
|
assert_equal topic.attributes, t.attributes
|
|
end
|
|
|
|
def test_active_record_relation_serialization
|
|
[Topic.all].to_yaml
|
|
end
|
|
|
|
def test_raw_types_are_not_changed_on_round_trip
|
|
topic = Topic.new(parent_id: "123")
|
|
assert_equal "123", topic.parent_id_before_type_cast
|
|
assert_equal "123", YAML.load(YAML.dump(topic)).parent_id_before_type_cast
|
|
end
|
|
|
|
def test_cast_types_are_not_changed_on_round_trip
|
|
topic = Topic.new(parent_id: "123")
|
|
assert_equal 123, topic.parent_id
|
|
assert_equal 123, YAML.load(YAML.dump(topic)).parent_id
|
|
end
|
|
|
|
def test_new_records_remain_new_after_round_trip
|
|
topic = Topic.new
|
|
|
|
assert topic.new_record?, "Sanity check that new records are new"
|
|
assert YAML.load(YAML.dump(topic)).new_record?, "Record should be new after deserialization"
|
|
|
|
topic.save!
|
|
|
|
assert_not topic.new_record?, "Saved records are not new"
|
|
assert_not YAML.load(YAML.dump(topic)).new_record?, "Saved record should not be new after deserialization"
|
|
|
|
topic = Topic.select('title').last
|
|
|
|
assert_not topic.new_record?, "Loaded records without ID are not new"
|
|
assert_not YAML.load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization"
|
|
end
|
|
|
|
def test_types_of_virtual_columns_are_not_changed_on_round_trip
|
|
author = Author.select('authors.*, count(posts.id) as posts_count')
|
|
.joins(:posts)
|
|
.group('authors.id')
|
|
.first
|
|
dumped = YAML.load(YAML.dump(author))
|
|
|
|
assert_equal 5, author.posts_count
|
|
assert_equal 5, dumped.posts_count
|
|
end
|
|
|
|
def test_a_yaml_version_is_provided_for_future_backwards_compat
|
|
coder = {}
|
|
Topic.first.encode_with(coder)
|
|
|
|
assert coder['active_record_yaml_version']
|
|
end
|
|
|
|
def test_deserializing_rails_41_yaml
|
|
topic = YAML.load(yaml_fixture("rails_4_1"))
|
|
|
|
assert topic.new_record?
|
|
assert_equal nil, topic.id
|
|
assert_equal "The First Topic", topic.title
|
|
assert_equal({ omg: :lol }, topic.content)
|
|
end
|
|
|
|
def test_deserializing_rails_4_2_0_yaml
|
|
topic = YAML.load(yaml_fixture("rails_4_2_0"))
|
|
|
|
assert_not topic.new_record?
|
|
assert_equal 1, topic.id
|
|
assert_equal "The First Topic", topic.title
|
|
assert_equal("Have a nice day", topic.content)
|
|
end
|
|
|
|
def test_yaml_encoding_keeps_mutations
|
|
author = Author.first
|
|
author.name = "Sean"
|
|
dumped = YAML.load(YAML.dump(author))
|
|
|
|
assert_equal "Sean", dumped.name
|
|
assert_equal author.name_was, dumped.name_was
|
|
assert_equal author.changes, dumped.changes
|
|
end
|
|
|
|
private
|
|
|
|
def yaml_fixture(file_name)
|
|
path = File.expand_path(
|
|
"../../support/yaml_compatibility_fixtures/#{file_name}.yml",
|
|
__FILE__
|
|
)
|
|
File.read(path)
|
|
end
|
|
end
|