Skip to content

Commit fb9719f

Browse files
committed
1320: refactor convert proforma to task service to use persistence for file-consistency
1 parent 512185c commit fb9719f

File tree

4 files changed

+124
-44
lines changed

4 files changed

+124
-44
lines changed

app/services/proforma_service/convert_proforma_task_to_task.rb

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ def initialize(proforma_task:, user:, task: nil)
77
@proforma_task = proforma_task
88
@user = user
99
@task = task || Task.new
10-
@all_files = @task.all_files
1110
@file_xml_ids = []
1211
end
1312

1413
def execute
15-
import_task
14+
ActiveRecord::Base.transaction do
15+
import_task
16+
end
1617
@task
1718
end
1819

1920
private
2021

2122
def import_task
22-
upsert_files @proforma_task, @task
2323
@task.assign_attributes(
2424
user:,
2525
title: @proforma_task.title,
@@ -33,12 +33,52 @@ def import_task
3333

3434
submission_restrictions: @proforma_task.submission_restrictions,
3535
external_resources: @proforma_task.external_resources,
36-
grading_hints: @proforma_task.grading_hints,
37-
38-
tests:,
39-
model_solutions:
36+
grading_hints: @proforma_task.grading_hints
4037
)
41-
delete_removed_files
38+
Pundit.authorize @user, @task, :create?
39+
40+
@task.save!
41+
42+
# delete not needed files
43+
manage_objects
44+
end
45+
46+
def manage_objects
47+
unreferenced_files.each(&:destroy)
48+
@task.reload
49+
# Move only relocated files to the task, avoiding updates to the updated_at of untouched files.
50+
move_relocated_files_to_task
51+
set_tests
52+
set_model_solutions
53+
54+
@task.reload
55+
upsert_files @proforma_task, @task
56+
delete_removed_objects
57+
@task.save!
58+
end
59+
60+
def move_relocated_files_to_task
61+
@task.files = @task.all_files(cached: false).filter do |file|
62+
# Move files to the task if they belong directly to it or if the parent's xml_id differs from the current parent's xml_id.
63+
parent_id(file.xml_id) == 'task' || (file.fileable.respond_to?(:xml_id) && file.fileable.xml_id) != parent_id(file.xml_id)
64+
end
65+
end
66+
67+
def parent_id(xml_id)
68+
[@proforma_task, *@proforma_task.model_solutions, *@proforma_task.tests].each do |object|
69+
return (object.respond_to?(:id) ? object.id : 'task') if object.files.map(&:id).include?(xml_id)
70+
end
71+
nil
72+
end
73+
74+
def all_proforma_files
75+
[@proforma_task.files + @proforma_task.tests.map(&:files) + @proforma_task.model_solutions.map(&:files)].flatten
76+
end
77+
78+
def unreferenced_files
79+
@task.all_files.reject do |f|
80+
all_proforma_files.map {|pf| pf.id.to_s }.include?(f.xml_id)
81+
end
4282
end
4383

4484
def parent_uuid
@@ -62,7 +102,7 @@ def upsert_files(collection, fileable)
62102
end
63103

64104
def upsert_file_from_proforma_file(proforma_task_file, fileable)
65-
task_file = ch_record_for(@all_files, proforma_task_file.id) || TaskFile.new
105+
task_file = (@file_xml_ids.exclude?(proforma_task_file.id) && ch_record_for(@task.all_files, proforma_task_file.id)) || TaskFile.new
66106
task_file.assign_attributes(file_attributes(proforma_task_file, fileable))
67107
attach_file_content(proforma_task_file, task_file)
68108
task_file
@@ -105,9 +145,10 @@ def file_attributes(proforma_task_file, fileable)
105145
}
106146
end
107147

108-
def tests
109-
@proforma_task.tests.map do |test|
110-
ch_test = ch_record_for(@task.tests, test.id) || Test.new(xml_id: test.id)
148+
def set_tests
149+
@proforma_task.tests.each do |test|
150+
ch_test = find_or_initialize_ch_test test
151+
ch_test.task = @task
111152
upsert_files test, ch_test
112153
ch_test.assign_attributes(
113154
title: test.title,
@@ -117,10 +158,14 @@ def tests
117158
meta_data: test.meta_data,
118159
configuration: test.configuration
119160
)
120-
ch_test
161+
ch_test.save!
121162
end
122163
end
123164

165+
def find_or_initialize_ch_test(test)
166+
ch_record_for(@task.tests, test.id) || Test.new(xml_id: test.id)
167+
end
168+
124169
def object_files(object)
125170
object.files.map {|file| files.delete(file.id) }
126171
end
@@ -136,24 +181,36 @@ def programming_language # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticCo
136181
end
137182
end
138183

139-
def model_solutions
140-
@proforma_task.model_solutions.map do |model_solution|
184+
def set_model_solutions
185+
@proforma_task.model_solutions.each do |model_solution|
141186
ch_model_solution = ch_record_for(@task.model_solutions, model_solution.id) || ModelSolution.new(xml_id: model_solution.id)
187+
ch_model_solution.task = @task
142188
upsert_files model_solution, ch_model_solution
143189
ch_model_solution.assign_attributes(
144190
description: model_solution.description,
145191
internal_description: model_solution.internal_description
146192
)
147-
ch_model_solution
193+
ch_model_solution.save!
148194
end
149195
end
150196

151197
def ch_record_for(collection, xml_id)
152198
collection.select {|record| record.xml_id == xml_id }&.first
153199
end
154200

155-
def delete_removed_files
156-
@all_files.reject {|file| @file_xml_ids.include? file.xml_id }.each(&:destroy)
201+
def delete_removed_objects
202+
delete_removed_tests
203+
delete_removed_model_solutions
204+
end
205+
206+
def delete_removed_tests
207+
remaining_test_ids = @proforma_task.tests.map {|t| t.id.to_s }
208+
@task.tests.reject {|test| remaining_test_ids.include? test.xml_id }.each(&:destroy)
209+
end
210+
211+
def delete_removed_model_solutions
212+
remaining_model_solution_ids = @proforma_task.model_solutions.map {|ms| ms.id.to_s }
213+
@task.model_solutions.reject {|model_solution| remaining_model_solution_ids.include? model_solution.xml_id }.each(&:destroy)
157214
end
158215
end
159216
end

app/services/proforma_service/import_task.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ def initialize(proforma_task:, user:)
99
end
1010

1111
def execute
12-
task = ConvertProformaTaskToTask.call(proforma_task: @proforma_task, user: @user, task: base_task)
13-
Pundit.authorize @user, task, :create?
14-
task.save!
15-
task
12+
ConvertProformaTaskToTask.call(proforma_task: @proforma_task, user: @user, task: base_task)
1613
end
1714

1815
private

spec/services/proforma_service/convert_proforma_task_to_task_spec.rb

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
end
2525

2626
describe '#execute' do
27-
subject(:convert_to_task_service) { described_class.call(proforma_task:, user:, task:) }
27+
subject(:convert_to_task_service) { described_class.call(proforma_task:, user:, task:).reload }
2828

2929
let(:proforma_task) do
3030
ProformaXML::Task.new(
@@ -237,8 +237,9 @@
237237
end
238238

239239
it 'creates files with correct attributes' do
240-
expect(convert_to_task_service.tests[1].files[0]).to have_attributes(convert_to_task_service.tests[0].files[0].attributes.merge('xml_id' => 'id-2'))
241-
expect(convert_to_task_service.tests[2].files[0]).to have_attributes(convert_to_task_service.tests[0].files[0].attributes.merge('xml_id' => 'id-3'))
240+
attribute_exceptions = %w[id created_at fileable_id updated_at]
241+
expect(convert_to_task_service.tests[1].files[0]).to have_attributes(convert_to_task_service.tests[0].files[0].attributes.except(*attribute_exceptions).merge('xml_id' => 'id-2'))
242+
expect(convert_to_task_service.tests[2].files[0]).to have_attributes(convert_to_task_service.tests[0].files[0].attributes.except(*attribute_exceptions).merge('xml_id' => 'id-3'))
242243
end
243244

244245
it 'creates separate copies of referenced file' do
@@ -321,10 +322,12 @@
321322
internal_description: 'internal_description',
322323
test_type: 'test_type',
323324
files: test_files,
324-
meta_data: test_meta_data
325+
meta_data: test_meta_data,
326+
configuration:
325327
)
326328
end
327329

330+
let(:configuration) {}
328331
let(:test_meta_data) {}
329332
let(:test_files) { [test_file] }
330333
let(:test_file) do
@@ -402,15 +405,15 @@
402405
end
403406

404407
context 'when test has custom configuration' do
405-
let(:test) { build(:test, :with_unittest) }
408+
let(:configuration) { attributes_for(:test, :with_unittest)[:configuration] }
406409

407410
it 'creates a test with the supplied test configuration' do
408411
expect(convert_to_task_service.tests.first).to have_attributes(configuration: test.configuration)
409412
end
410413
end
411414

412415
context 'when test has multiple custom configuration' do
413-
let(:test) { build(:test, :with_multiple_custom_configurations) }
416+
let(:configuration) { attributes_for(:test, :with_multiple_custom_configurations)[:configuration] }
414417

415418
it 'creates a test with the supplied test configurations' do
416419
expect(convert_to_task_service.tests.first).to have_attributes(configuration: test.configuration)
@@ -420,7 +423,11 @@
420423
context 'when proforma_task has multiple tests' do
421424
let(:tests) { [test, test2] }
422425
let(:test2) do
423-
ProformaXML::Test.new(files: test_files2)
426+
ProformaXML::Test.new(
427+
id: 'test_file_id',
428+
title: 'wild-title',
429+
files: test_files2
430+
)
424431
end
425432
let(:test_files2) { [test_file2] }
426433
let(:test_file2) do
@@ -605,51 +612,67 @@
605612
)
606613
end
607614

615+
it 'does not update updated_at of unchanged files' do
616+
task_file_updated_at = task_files.first.updated_at
617+
test_file_updated_at = test_files.first.updated_at
618+
ms_file_update_at = model_solution_files.first.updated_at
619+
620+
expect(convert_to_task_service).to have_attributes(
621+
files: have(1).item.and(include(have_attributes(
622+
updated_at: task_file_updated_at
623+
))),
624+
model_solutions: have(1).item.and(include(have_attributes(
625+
files: have(1).item.and(include(have_attributes(updated_at: ms_file_update_at)))
626+
))),
627+
tests: have(1).item.and(include(have_attributes(
628+
id: tests.first.id,
629+
files: have(1).item.and(include(have_attributes(updated_at: test_file_updated_at)))
630+
)))
631+
)
632+
end
633+
608634
context 'when files have been deleted' do
609635
context 'when task files have been deleted' do
610-
before { task.files = [] }
636+
before { proforma_task.files = [] }
611637

612638
it 'imports task files correctly' do
613639
expect(convert_to_task_service.files).to be_empty
614640
end
615641

616642
it 'saves the task correctly' do
617-
convert_to_task_service.save
618-
task.reload
619-
expect(task.files).to be_empty
643+
convert_to_task_service
644+
expect(task.reload.files).to be_empty
620645
end
621646
end
622647

623648
context 'when test files have been deleted' do
624-
before { task.tests.first.files = [] }
649+
before { proforma_task.tests.first.files = [] }
625650

626651
it 'imports test files correctly' do
627652
expect(convert_to_task_service.tests.first.files).to be_empty
628653
end
629654

630655
it 'saves the task correctly' do
631-
convert_to_task_service.save
632-
task.reload
656+
convert_to_task_service
633657
expect(task.tests.first.files).to be_empty
634658
end
635659
end
636660

637661
context 'when model solution files have been deleted' do
638-
before { task.model_solutions.first.files = [] }
662+
before { proforma_task.model_solutions.first.files = [] }
639663

640664
it 'imports model solution files correctly' do
641665
expect(convert_to_task_service.model_solutions.first.files).to be_empty
642666
end
643667

644668
it 'saves the task correctly' do
645-
convert_to_task_service.save
646-
task.reload
669+
convert_to_task_service
647670
expect(task.model_solutions.first.files).to be_empty
648671
end
649672
end
650673
end
651674

652-
context 'when files have been move around' do
675+
context 'when files have been moved around' do
653676
before do
654677
task_file = proforma_task.files.first
655678
test_file = proforma_task.tests.first.files.first
@@ -691,15 +714,15 @@
691714
files: have(1).item.and(include(have_attributes(id: task_files.first.id)))
692715
))),
693716
tests: have(1).item.and(include(have_attributes(
694-
id: 987_654_325,
717+
xml_id: '987654325',
695718
files: have(1).item.and(include(have_attributes(id: model_solution_files.first.id)))
696719
)))
697720
)
698721
end
699722

700723
context 'when imported task is persisted' do
701724
before do
702-
convert_to_task_service.save
725+
convert_to_task_service.save!
703726
task.reload
704727
end
705728

@@ -735,7 +758,7 @@
735758
files: have(1).item.and(include(have_attributes(id: task_files.first.id)))
736759
))),
737760
tests: have(1).item.and(include(have_attributes(
738-
id: 987_654_325,
761+
xml_id: '987654325',
739762
files: have(1).item.and(include(have_attributes(id: model_solution_files.first.id)))
740763
)))
741764
)

spec/services/proforma_service/import_spec.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
before do
5454
zip_file.write(exporter)
5555
zip_file.rewind
56+
task.reload
5657
end
5758

5859
it { is_expected.to be_an_equal_task_as task }
@@ -75,7 +76,7 @@
7576
it { is_expected.to be_valid }
7677

7778
it 'sets the correct user as owner of the task' do
78-
expect(imported_task.user).to be user
79+
expect(imported_task.user).to eq user
7980
end
8081

8182
it 'sets the uuid' do
@@ -199,6 +200,8 @@
199200
user:)
200201
end
201202

203+
before { task2.reload }
204+
202205
it 'imports the tasks from zip containing multiple zips' do
203206
expect(imported_task).to all be_an(Task)
204207
end

0 commit comments

Comments
 (0)