diff options
| author | Robert | 2017-10-04 14:46:42 +0200 |
|---|---|---|
| committer | Robert | 2017-10-04 17:15:10 +0200 |
| commit | 14eb03b2e2485985e6956375a82edbcf71f01bb4 (patch) | |
| tree | 6eae75fd60f7231970e92ad0580a2825f56cae26 | |
| parent | 92a4ed553ab430e7af47ff7421f0a1fcdd9fb56b (diff) | |
| download | chouette-core-14eb03b2e2485985e6956375a82edbcf71f01bb4.tar.bz2 | |
Fixes: #4629@4.5h; Implemented copy
| -rw-r--r-- | app/models/compliance_check_block.rb | 2 | ||||
| -rw-r--r-- | app/models/compliance_check_set.rb | 3 | ||||
| -rw-r--r-- | app/workers/compliance_control_set_copy_worker.rb | 8 | ||||
| -rw-r--r-- | lib/af83/compliance_control_set_copier.rb | 128 | ||||
| -rw-r--r-- | spec/lib/af83/cloning/compliance_control_set_copier_spec.rb | 88 | ||||
| -rw-r--r-- | spec/models/compliance_check_block_spec.rb | 1 | ||||
| -rw-r--r-- | spec/models/compliance_check_set_spec.rb | 3 | ||||
| -rw-r--r-- | spec/models/compliance_control_set_spec.rb | 1 |
8 files changed, 234 insertions, 0 deletions
diff --git a/app/models/compliance_check_block.rb b/app/models/compliance_check_block.rb index 035c03ed9..ee60a8bb1 100644 --- a/app/models/compliance_check_block.rb +++ b/app/models/compliance_check_block.rb @@ -1,3 +1,5 @@ class ComplianceCheckBlock < ActiveRecord::Base belongs_to :compliance_check_set + + has_many :compliance_checks end diff --git a/app/models/compliance_check_set.rb b/app/models/compliance_check_set.rb index e4146e0e2..536afc705 100644 --- a/app/models/compliance_check_set.rb +++ b/app/models/compliance_check_set.rb @@ -6,6 +6,9 @@ class ComplianceCheckSet < ActiveRecord::Base belongs_to :workbench belongs_to :parent, polymorphic: true + has_many :compliance_check_blocks + has_many :compliance_checks + has_many :compliance_check_resources has_many :compliance_check_messages diff --git a/app/workers/compliance_control_set_copy_worker.rb b/app/workers/compliance_control_set_copy_worker.rb new file mode 100644 index 000000000..4cbd4e0bc --- /dev/null +++ b/app/workers/compliance_control_set_copy_worker.rb @@ -0,0 +1,8 @@ +class ComplianceControlSetCopyWorker + include Sidekiq::Worker + + def perform(cc_set_id, referential_id) + AF83.ComplianceControlSetCopier.new.copy(cc_set_id, referential_id) + end + +end diff --git a/lib/af83/compliance_control_set_copier.rb b/lib/af83/compliance_control_set_copier.rb new file mode 100644 index 000000000..840e21e9e --- /dev/null +++ b/lib/af83/compliance_control_set_copier.rb @@ -0,0 +1,128 @@ +module AF83 + # We use a class instead of a singleton object because we will use + # a cache to avoid copying instancs of ComplianceControl twice as they + # might be reachable from CCSet **and** its descendent CCBlock. + # More generally spoken, we copy a DAG, not a tree. + class ComplianceControlSetCopier + + # Naming Convention: As we are in a domain with quite long names we + # abbreviate compliance_control to cc and + # compliance_check to cck iff used as prefixes. + + attr_reader :cc_set_id, :referential_id + + def copy cc_set_id, referential_id + @cc_set_id = cc_set_id + @referential_id = referential_id + check_organisation_coherence! + copy_dag + end + + + private + + + # Workers + # ------- + def check_organisation_coherence! + return true if cc_set.organisation_id == referential.organisation_id + raise ArgumentError, "Incoherent organisation of referential" + end + + def copy_dag + # Assure cck_set's existance, just in case cc_set is _empty_. + cck_set + make_cck_blocks + make_ccks_from_ccs + end + + def make_all_cck_block_children cc_block, cck_block + cc_block + .compliance_controls + .each{ |compliance_control| make_compliance_check(compliance_control, cck_block.id) } + end + def make_cck_block cc_block + cck_block = + cck_set.compliance_check_blocks.create( + name: name_with_refid(cc_block.name)) + + make_all_cck_block_children cc_block, cck_block + end + def make_cck_blocks + cc_set.compliance_control_blocks.each(&method(:make_cck_block)) + end + + def make_ccks_from_ccs + cc_set.compliance_controls.each(&method(:make_compliance_check)) + end + + def make_compliance_check(compliance_control, cck_block_id = nil) + already_there = get_from_cache compliance_control.id + # We do not want to impose traversal order of the DAG, that would + # make the code more resistant to change... + # So we check if we need to update the compliance_check_block_id + # of a control found in the cache. + # N.B. By traversing the indirect descendents from a set first, + # or IOW, traversing the control_blocks before the controls, + # this check could go away and we could return from the + # method in case of a cache hit. + if already_there + # Purely defensive: + if already_there.compliance_check_block_id.nil? && cck_block_id + already_there.update compliance_check_block_id: cck_block_id + end + return + end + make_compliance_check!(compliance_control, cck_block_id) + end + def make_compliance_check!(compliance_control, cck_block_id) + add_to_cache( + compliance_control.id, + cck_set.compliance_checks.create( + criticity: compliance_control.criticity, + name: name_with_refid(compliance_control.name), + code: compliance_control.code, + origin_code: compliance_control.origin_code, + compliance_check_block_id: cck_block_id + )) + end + + def name_with_refid name + [name, referential.name].join('-') + end + + # Lazy Values + # ----------- + def cc_set + @__cc_set__ ||= ComplianceControlSet.find(cc_set_id) + end + def cck_set + @__cck_set__ ||= ComplianceCheckSet.create!( + compliance_control_set_id: cc_set_id, + referential_id: referential_id, + workbench_id: referential.workbench_id, + name: name_with_refid(cc_set.name), + status: 'new' + ) + end + def referential + @__referential__ ||= Referential.find(referential_id) + end + + # Copy Cache + # ---------- + def add_to_cache key, obj + # Right now we map key -> obj, in case memory consumption becomes too important + # we can map key -> obj.id and fetch the object from the database in get_from_cache + # (time vs. space tradeoff) + copy_cache.merge!(key => obj) + end + def get_from_cache key + copy_cache[key].tap do | ele | + end + end + def copy_cache + @__copy_cache__ ||= Hash.new + end + end +end diff --git a/spec/lib/af83/cloning/compliance_control_set_copier_spec.rb b/spec/lib/af83/cloning/compliance_control_set_copier_spec.rb new file mode 100644 index 000000000..aacd9d7c9 --- /dev/null +++ b/spec/lib/af83/cloning/compliance_control_set_copier_spec.rb @@ -0,0 +1,88 @@ +RSpec.describe AF83::ComplianceControlSetCopier do + + subject{ described_class.new } + + let( :cc_set ){ create :compliance_control_set } + + context 'Copying empty set' do + context 'incorrect organisation' do + # Assuring the organisation missmatch + before { referential.organisation_id = cc_set.organisation_id.succ } + it 'fails' do + expect{ subject.copy(cc_set.id, referential.id) }.to raise_error(ArgumentError) + end + it 'does not create any objects in the database' do + expect{ subject.copy(cc_set.id, referential.id) rescue nil }.to_not change{ComplianceCheckSet.count} + expect{ subject.copy(cc_set.id, referential.id) rescue nil }.to_not change{ComplianceCheckBlock.count} + expect{ subject.copy(cc_set.id, referential.id) rescue nil }.to_not change{ComplianceCheck.count} + end + end + + context 'correct organisation' do + let(:ref){ create :referential, organisation_id: cc_set.organisation_id } + + let(:cc_blox){ + 3.times.map{ |_| create :compliance_control_block, compliance_control_set: cc_set } + } + let!(:direct_ccs){ + 3.times.map{ |n| create :compliance_control, compliance_control_set: cc_set, name: "direct #{n.succ}" } + } + # Needed to check we do not dulicate a node (compliance_control) twice + let!(:indirect_ccs){ + # Create 1 child for each block and also associate first of the direct ccs to the first block + # seconf of the direct css to the second block + cc_blox.take(2).zip(direct_ccs.take(2)).each do | cc_block, cc | + cc.update compliance_control_block_id: cc_block.id + end + cc_blox.each_with_index.map{ | cc_block, n | + create(:compliance_control, compliance_control_set: cc_set, compliance_control_block: cc_block, name: "indirect #{n.succ}") + } + } + + let( :cck_set ){ ComplianceCheckSet.last } + let( :cck_blox ){ cck_set.compliance_check_blocks } + let( :ccks ){ cck_set.compliance_checks } + + it 'correctly creates a cck_set for a complete DAG' do + # Slowness of tests constrain us to create a minimum of objects in the DB, + # hence only one example :( + counts = object_counts + subject.copy(cc_set.id, ref.id) + + # Did not change the original objects + # Correct numbers + expect( ComplianceControlSet.count).to eq(counts.cc_set_count) + expect( ComplianceControlBlock.count).to eq(counts.cc_block_count) + expect( ComplianceControl.count).to eq(counts.cc_count) + + expect( ComplianceCheckSet.count ).to eq(counts.cck_set_count + 1) + expect( cck_blox.count ).to eq(counts.cck_block_count + cc_blox.size) + expect( ccks.count ).to eq(counts.cck_count + direct_ccs.size + indirect_ccs.size) + + # Correcly associated + expect( cck_blox.map(&:compliance_checks).map(&:size) ) + .to eq([2, 2, 1]) + expect( ComplianceCheck.where(name: mk_name('direct 1')).first.compliance_check_block_id ) + .to eq( cck_blox.first.id ) + expect( ComplianceCheck.where(name: mk_name('direct 3')).first.compliance_check_block_id ).to be_nil + end + + end + + + def object_counts + OpenStruct.new \ + cc_set_count: ComplianceControlSet.count, + cc_block_count: ComplianceControlBlock.count, + cc_count: ComplianceControl.count, + cck_set_count: ComplianceCheckSet.count, + cck_block_count: ComplianceCheckBlock.count, + cck_count: ComplianceCheck.count + end + + def mk_name name + [name, ref.name].join('-') + end + end + +end diff --git a/spec/models/compliance_check_block_spec.rb b/spec/models/compliance_check_block_spec.rb index f581d5085..a3d98d459 100644 --- a/spec/models/compliance_check_block_spec.rb +++ b/spec/models/compliance_check_block_spec.rb @@ -6,4 +6,5 @@ RSpec.describe ComplianceCheckBlock, type: :model do end it { should belong_to :compliance_check_set } + it { should have_many :compliance_checks } end diff --git a/spec/models/compliance_check_set_spec.rb b/spec/models/compliance_check_set_spec.rb index 6e53c9def..8afea5b3e 100644 --- a/spec/models/compliance_check_set_spec.rb +++ b/spec/models/compliance_check_set_spec.rb @@ -9,4 +9,7 @@ RSpec.describe ComplianceCheckSet, type: :model do it { should belong_to :workbench } it { should belong_to :compliance_control_set } it { should belong_to :parent } + + it { should have_many :compliance_checks } + it { should have_many :compliance_check_blocks } end diff --git a/spec/models/compliance_control_set_spec.rb b/spec/models/compliance_control_set_spec.rb index edc684bbc..04d1c418c 100644 --- a/spec/models/compliance_control_set_spec.rb +++ b/spec/models/compliance_control_set_spec.rb @@ -7,6 +7,7 @@ RSpec.describe ComplianceControlSet, type: :model do it { should belong_to :organisation } it { should have_many(:compliance_controls).dependent(:destroy) } + it { should have_many(:compliance_control_blocks).dependent(:destroy) } it { should validate_presence_of :name } end |
