diff --git a/app/models/transformation_mapping.rb b/app/models/transformation_mapping.rb new file mode 100644 index 00000000000..9ddd730c5d1 --- /dev/null +++ b/app/models/transformation_mapping.rb @@ -0,0 +1,67 @@ +class TransformationMapping < ApplicationRecord + has_many :transformation_mapping_items, :dependent => :destroy + + validates :name, :presence => true, :uniqueness => true + + virtual_column :mappings, :type => :string + + # forward compatible: next release the table will be polymorphic + virtual_column :type, :type => :string + + def self.inheritance_column + "none" + end + + def type + "MigrationInfrastructureMapping" + end + + def type=(type) + end + + # Assumption: source and destination types are the same. Works for infrastructure mapping + # group mapping items first by destination_type, then by destination_id + # simplify records with IDs. + # Sample result (Hash) + # + # EmsCluster: + # - destination: "2": + # sources: ["100", "200"] + # - destination: "3" + # sources: ["301", "302"] + # Network: + # - destination: "2" + # sources: ["202", "204"] + # Storage: + # - destination: "4" + # sources: ["33", "32"] + def mappings + dst_type_groups = transformation_mapping_items.group_by(&:destination_type) + dst_type_groups.each_with_object({}) do |(dst_type, items), results| + dst_groups = items.group_by { |item| item.destination_id.to_s } + results[dst_type] = dst_groups.each_with_object([]) do |(dst_id, item_array), dst_id_groups| + dst_id_groups << {"destination" => dst_id, "sources" => item_array.collect { |item| item.source_id.to_s }} + end + end + end + + def mappings=(options) + options.each do |dst_type, item_map_array| + item_map_array.each do |item_map| + destination_id = item_map['destination'] + item_map['sources'].each do |source_id| + transformation_mapping_items << TransformationMappingItem.new( + :source_id => source_id, + :source_type => dst_type, + :destination_id => destination_id, + :destination_type => dst_type.camelize + ) + end + end + end + end + + def map(source) + transformation_mapping_items.find_by(:source => source).try(:destination) + end +end diff --git a/app/models/transformation_mapping_item.rb b/app/models/transformation_mapping_item.rb new file mode 100644 index 00000000000..1fb195d3af0 --- /dev/null +++ b/app/models/transformation_mapping_item.rb @@ -0,0 +1,7 @@ +class TransformationMappingItem < ApplicationRecord + belongs_to :transformation_mapping + belongs_to :source, :polymorphic => true + belongs_to :destination, :polymorphic => true + + validates :source_id, :uniqueness => {:scope => [:transformation_mapping_id, :source_type]} +end diff --git a/spec/factories/transformation_mapping.rb b/spec/factories/transformation_mapping.rb new file mode 100644 index 00000000000..1605c01a9d4 --- /dev/null +++ b/spec/factories/transformation_mapping.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :transformation_mapping do + sequence(:name) { |n| "Transformation Mapping #{seq_padded_for_sorting(n)}" } + end +end diff --git a/spec/models/transformation_mapping_spec.rb b/spec/models/transformation_mapping_spec.rb new file mode 100644 index 00000000000..3262de53ccc --- /dev/null +++ b/spec/models/transformation_mapping_spec.rb @@ -0,0 +1,43 @@ +describe TransformationMapping do + let(:src1) { Array.new(4) { FactoryGirl.create(:ems_cluster) } } + let(:dst1) { Array.new(2) { FactoryGirl.create(:ems_cluster) } } + let(:src2) { Array.new(2) { FactoryGirl.create(:network) } } + let(:dst2) { Array.new(2) { FactoryGirl.create(:network) } } + let(:mappings) do + { + "EmsCluster" => [ + { "sources" => [src1[0].id.to_s, src1[1].id.to_s], "destination" => dst1[0].id.to_s }, + { "sources" => [src1[2].id.to_s, src1[3].id.to_s], "destination" => dst1[1].id.to_s } + ], + "Network" => [ + { "sources" => [src2[0].id.to_s], "destination" => dst2[0].id.to_s }, + { "sources" => [src2[1].id.to_s], "destination" => dst2[1].id.to_s } + ] + } + end + + context "CRUD" do + it 'creates a complete mapping with item from a hash representation' do + tm = FactoryGirl.create(:transformation_mapping, :mappings => mappings) + expect(tm.map(src1[0])).to eq(dst1[0]) + expect(tm.map(src1[1])).to eq(dst1[0]) + expect(tm.map(src1[2])).to eq(dst1[1]) + expect(tm.map(src1[3])).to eq(dst1[1]) + expect(tm.map(src2[0])).to eq(dst2[0]) + expect(tm.map(src2[1])).to eq(dst2[1]) + end + + it 'reads a complete mapping in a hash representation' do + tm = FactoryGirl.create(:transformation_mapping) + tm.transformation_mapping_items << TransformationMappingItem.new(:source => src1[0], :destination => dst1[0]) + tm.transformation_mapping_items << TransformationMappingItem.new(:source => src1[1], :destination => dst1[0]) + tm.transformation_mapping_items << TransformationMappingItem.new(:source => src1[2], :destination => dst1[1]) + tm.transformation_mapping_items << TransformationMappingItem.new(:source => src1[3], :destination => dst1[1]) + tm.transformation_mapping_items << TransformationMappingItem.new(:source => src2[0], :destination => dst2[0]) + tm.transformation_mapping_items << TransformationMappingItem.new(:source => src2[1], :destination => dst2[1]) + tm.save + + expect(tm.mappings).to eq(mappings) + end + end +end