From a860e35341c2e62e2105858abc03916ed7ec4c36 Mon Sep 17 00:00:00 2001 From: drew uhlmann Date: Wed, 27 Sep 2017 09:57:43 -0400 Subject: [PATCH] Migrate existing dialog field association data to use new relationship --- ...ld_associations_to_use_new_relationship.rb | 76 ++++++++++++++++ ...sociations_to_use_new_relationship_spec.rb | 89 +++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 db/migrate/20170927135007_migrate_dialog_field_associations_to_use_new_relationship.rb create mode 100644 spec/migrations/20170927135007_migrate_dialog_field_associations_to_use_new_relationship_spec.rb diff --git a/db/migrate/20170927135007_migrate_dialog_field_associations_to_use_new_relationship.rb b/db/migrate/20170927135007_migrate_dialog_field_associations_to_use_new_relationship.rb new file mode 100644 index 000000000..137cf906b --- /dev/null +++ b/db/migrate/20170927135007_migrate_dialog_field_associations_to_use_new_relationship.rb @@ -0,0 +1,76 @@ +class MigrateDialogFieldAssociationsToUseNewRelationship < ActiveRecord::Migration[5.0] + class Dialog < ActiveRecord::Base + end + + class DialogTab < ActiveRecord::Base + belongs_to :dialog, :class_name => "MigrateDialogFieldAssociationsToUseNewRelationship::Dialog" + end + + class DialogGroup < ActiveRecord::Base + belongs_to :dialog_tab, :class_name => "MigrateDialogFieldAssociationsToUseNewRelationship::DialogTab" + end + + class DialogField < ActiveRecord::Base + self.inheritance_column = :_type_disabled # disable STI + belongs_to :dialog_group, :class_name => "MigrateDialogFieldAssociationsToUseNewRelationship::DialogGroup" + end + + class DialogFieldAssociation < ActiveRecord::Base + end + + def up + say_with_time("Migrating existing dialog field associations to new relationship") do + dialog_with_fields.each do |_dialog, fields| + dialog_triggers = dialog_fields_with_trigger_auto_refresh(fields).sort_by! { |trigger| trigger[:position] } + dialog_responders = dialog_fields_with_auto_refresh(fields) + dialog_triggers.each_with_index do |trigger, index| + specific_responders = dialog_responders.select { |responder| responder_range(trigger, trigger[index + 1]).cover?(responder[:position]) } + set_associations(trigger, specific_responders) + end + end + end + end + + def down + DialogFieldAssociation.delete_all + end + + private + + def absolute_position(dialog_fields) + dialog_fields.collect do |f| + field_position = f.position + dialog_group_position = f.dialog_group.position + dialog_tab_position = f.dialog_group.dialog_tab.position + index = field_position + dialog_group_position * 1000 + dialog_tab_position * 100_000 + {:id => f.id, :position => index} + end + end + + def dialog_fields_with_auto_refresh(dialog_fields) + absolute_position(dialog_fields.select(&:auto_refresh)) + end + + def dialog_fields_with_trigger_auto_refresh(dialog_fields) + absolute_position(dialog_fields.select(&:trigger_auto_refresh)) + end + + def responder_range(trigger_min, trigger_max) + min = trigger_min[:position] + 1 + max = trigger_max.present? ? trigger_max[:position] - 1 : 100_000_000 + (min..max) + end + + def set_associations(trigger, specific_responders) + specific_responders.each { |responder| DialogFieldAssociation.create!(:trigger_id => trigger[:id], :respond_id => responder[:id]) } + end + + def dialog_with_fields + DialogField + .where(:auto_refresh => true) + .or(DialogField.where(:trigger_auto_refresh => true)) + .includes(:dialog_group => {:dialog_tab => :dialog}) + .group_by { |f| f.dialog_group.try(:dialog_tab).try(:dialog) } + .except(nil) + end +end diff --git a/spec/migrations/20170927135007_migrate_dialog_field_associations_to_use_new_relationship_spec.rb b/spec/migrations/20170927135007_migrate_dialog_field_associations_to_use_new_relationship_spec.rb new file mode 100644 index 000000000..1f20a697a --- /dev/null +++ b/spec/migrations/20170927135007_migrate_dialog_field_associations_to_use_new_relationship_spec.rb @@ -0,0 +1,89 @@ +require_migration + +describe MigrateDialogFieldAssociationsToUseNewRelationship do + let(:dialog_field_stub) { migration_stub(:DialogField) } + let(:dialog_group_stub) { migration_stub(:DialogGroup) } + let(:dialog_tab_stub) { migration_stub(:DialogTab) } + let(:dialog_stub) { migration_stub(:Dialog) } + let(:dialog_field_association_stub) { migration_stub(:DialogFieldAssociation) } + let(:dialog_group_id) { dialog_group_stub.first.id } + let(:dialog_tab_id) { dialog_tab_stub.first.id } + let(:dialog_id) { dialog_stub.first.id } + + migration_context :up do + before do + dialog_stub.create! + dialog_tab_stub.create!(:dialog_id => dialog_id, :position => 4) + dialog_group_stub.create!(:dialog_tab_id => dialog_tab_id, :position => 7) + end + + it "does not create a reference when trigger field is after responder field" do + dialog_field_stub.create!(:name => "dialog_field4", :auto_refresh => true, :position => 0, :dialog_group_id => dialog_group_id, :type => "DialogFieldTextbox") + dialog_field_stub.create!(:name => "dialog_field1", :trigger_auto_refresh => true, :position => 4, :dialog_group_id => dialog_group_id, :type => "DialogFieldDropdown") + expect(dialog_field_association_stub.count).to eq(0) + + migrate + + expect(dialog_field_association_stub.count).to eq(0) + end + + it "does not create a circular reference" do + dialog_field_stub.create!(:name => "dialog_field4", :trigger_auto_refresh => true, :auto_refresh => true, :position => 0, :dialog_group_id => dialog_group_id, :type => "DialogFieldRadioButton") + dialog_field_stub.create!(:name => "dialog_field1", :auto_refresh => true, :trigger_auto_refresh => true, :position => 4, :dialog_group_id => dialog_group_id, :type => "DialogFieldTagControl") + expect(dialog_field_association_stub.count).to eq(0) + + migrate + + expect(dialog_field_association_stub.count).to eq(1) + expect(dialog_field_association_stub.first.trigger_id).to eq(dialog_field_stub.first.id) + expect(dialog_field_association_stub.first.respond_id).to eq(dialog_field_stub.second.id) + end + + it "does create a reference when valid one is present" do + dialog_field_stub.create!(:name => "dialog_field4", :auto_refresh => true, :position => 2, :dialog_group_id => dialog_group_id) + dialog_field_stub.create!(:name => "dialog_field1", :trigger_auto_refresh => true, :position => 0, :dialog_group_id => dialog_group_id) + expect(dialog_field_association_stub.count).to eq(0) + + migrate + + expect(dialog_field_association_stub.count).to eq(1) + expect(dialog_field_association_stub.first.trigger_id).to eq(dialog_field_stub.second.id) + expect(dialog_field_association_stub.first.respond_id).to eq(dialog_field_stub.first.id) + end + + it "does not create association when dialog tab is missing" do + dialog_tab_stub.delete_all + dialog_field_stub.create!(:name => "dialog_field4", :auto_refresh => true, :position => 2, :dialog_group_id => dialog_group_stub.first.id) + dialog_field_stub.create!(:name => "dialog_field1", :trigger_auto_refresh => true, :position => 0, :dialog_group_id => dialog_group_stub.first.id) + expect(dialog_field_association_stub.count).to eq(0) + + migrate + + expect(dialog_field_association_stub.count).to eq(0) + end + + it "only creates dialog associations if the fields are on the same dialog" do + dialog_field_stub.create!(:name => "dialog_field4", :trigger_auto_refresh => true, :position => 4, :dialog_group_id => dialog_group_stub.first.id, :type => "DialogFieldDateControl") + + expect(dialog_field_association_stub.count).to eq(0) + + migrate + + expect(dialog_field_association_stub.count).to eq(0) + end + end + + migration_context :down do + it "should delete dialog field associations" do + dialog_field_stub.create!(:name => "dialog_field6") + dialog_field_stub.create!(:name => "dialog_field9") + dialog_field_association_stub.create!(:trigger_id => dialog_field_stub.first.id, :respond_id => dialog_field_stub.second.id) + + expect(dialog_field_association_stub.count).to eq(1) + + migrate + + expect(dialog_field_association_stub.count).to eq(0) + end + end +end