Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove serialization of non-existing classes/objects #390

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class FixUnserializableNotificationOptions < ActiveRecord::Migration[5.1]
class Notification < ActiveRecord::Base
belongs_to :notification_type, :class_name => "FixUnserializableNotificationOptions::NotificationType"
end

class NotificationType < ActiveRecord::Base
end

# only used for testing
class CloudVolume < ActiveRecord::Base
self.inheritance_column = :_type_disabled
end

def up
invalid_options_ids = []
removed_records_ids = []
Notification.where.not(:options => nil).each do |n|
begin
# Options could have no longer available activerecord/activemodel classes from rails 5.0.0 -> 5.0.6, which will raise an ArgumentError on deserialization
# Fixing these to use 5.0.7+ smaller serialization format without all the private attributes is too hard, especailly since you can only read their YAML strings
# and make changes as a string, not as a Hash.
opts = YAML.load(n.options)
rescue ArgumentError
invalid_options_ids << n.id
else
begin
opts_subject = opts[:subject]
opts_subject.reload if opts_subject.respond_to?(:reload)
rescue ActiveRecord::RecordNotFound
# Options are readable. Options[:subject] is a AR::Base object but has been deleted.
removed_records_ids << n.id
end
end
end

Notification.where(:id => invalid_options_ids).update_all(:options => {:subject => "Unreadable"}.to_yaml)
Notification.where(:id => removed_records_ids).update_all(:options => {:subject => "Removed"}.to_yaml)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require_migration

describe FixUnserializableNotificationOptions do
let(:notification_stub) { migration_stub(:Notification) }
let(:notification_type_stub) { migration_stub(:NotificationType) }

migration_context :up do
it "fixes options[:subject] classes that don't exist" do
notification1 = notification_stub.create!(:notification_type => notification_type_stub.create!)
notification1.update(
:options =>
<<~OPTIONS
---
:subject: !ruby/object:NonExistingCloudVolume
concise_attributes:
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: id
value_before_type_cast: 3
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: type
value_before_type_cast: NonExistingCloudVolume
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: name
value_before_type_cast: MyCloudVolume
new_record: false
active_record_yaml_version: 2
OPTIONS
)

expect { YAML.load(notification1.reload.options) }.to raise_error(ArgumentError)

migrate

expect(YAML.load(notification1.reload.options)[:subject]).to eq("Unreadable")
end

it "Sets options[:subject] where the record no longer exists" do
notification1 = notification_stub.create!(:notification_type => notification_type_stub.create!)
notification1.update(
:options =>
<<~OPTIONS
---
:subject: !ruby/object:FixUnserializableNotificationOptions::CloudVolume
concise_attributes:
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: id
value_before_type_cast: 3
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: type
value_before_type_cast: ManageIQ::Providers::Openstack::CloudManager::CloudVolume
- !ruby/object:ActiveRecord::Attribute::FromDatabase
name: name
value_before_type_cast: MyCloudVolume
new_record: false
active_record_yaml_version: 2
OPTIONS
)

expect { YAML.load(notification1.reload.options)[:subject].reload }.to raise_error(ActiveRecord::RecordNotFound)

migrate

expect(YAML.load(notification1.reload.options)[:subject]).to eq("Removed")
end
end
end