Skip to content

Commit 219d9d0

Browse files
committed
refactor xml_id_path to use array
1 parent 3d54adb commit 219d9d0

File tree

7 files changed

+68
-24
lines changed

7 files changed

+68
-24
lines changed

app/services/proforma_service/convert_exercise_to_task.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def create_task
6767

6868
def proglang
6969
regex = %r{^openhpi/co_execenv_(?<language>[^:]*):(?<version>[^-]*)(?>-.*)?$}
70-
match = regex.match @exercise.execution_environment.docker_image
70+
match = regex.match @exercise&.execution_environment&.docker_image
7171
match ? {name: match[:language], version: match[:version]} : nil
7272
end
7373

@@ -105,18 +105,19 @@ def tests
105105
end
106106

107107
def xml_id_from_file(file)
108+
xml_id_path = file.xml_id_path || []
109+
return xml_id_path if xml_id_path&.any?
110+
108111
type = if file.teacher_defined_assessment?
109112
'test'
110113
elsif file.reference_implementation?
111114
'ms'
112115
else
113116
'file'
114117
end
115-
xml_id_path_parts = file.xml_id_path&.split('/')
116-
xml_id_path = []
117118

118-
xml_id_path << (xml_id_path_parts&.first || "co-#{type}-#{file.id}") unless type == 'file'
119-
xml_id_path << (xml_id_path_parts&.last || file.id)
119+
xml_id_path << "co-#{type}-#{file.id}" unless type == 'file'
120+
xml_id_path << file.id.to_s
120121

121122
xml_id_path
122123
end
@@ -190,7 +191,7 @@ def task_files
190191
end
191192

192193
def task_file(file)
193-
file.update(xml_id_path: xml_id_from_file(file).join('/')) if file.xml_id_path.blank?
194+
file.update(xml_id_path: xml_id_from_file(file)) if file.xml_id_path.blank?
194195

195196
xml_id = xml_id_from_file(file).last
196197
task_file = ProformaXML::TaskFile.new(

app/services/proforma_service/convert_task_to_exercise.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def execute
1717
private
1818

1919
def import_task
20+
old_file_ids = @exercise.files.map(&:id)
2021
@exercise.assign_attributes(
2122
user: @user,
2223
title: @task.title,
@@ -30,11 +31,12 @@ def import_task
3031

3132
files:
3233
)
34+
CodeOcean::File.where(id: (old_file_ids - @exercise.files.map(&:id))).destroy_all
3335
end
3436

3537
def extract_meta_data(meta_data, *path)
3638
current_level = meta_data
37-
path.each {|attribute| current_level = current_level&.dig("CodeOcean:#{attribute}") }
39+
path.each {|attribute| current_level = current_level.is_a?(Hash) ? current_level&.dig("CodeOcean:#{attribute}") : current_level&.find {|entry| entry['@id'] == attribute } } # || current_level
3840
current_level&.dig('$1')
3941
end
4042

@@ -81,6 +83,7 @@ def model_solution_files
8183
model_solution.files.map do |task_file|
8284
codeocean_file_from_task_file(task_file, model_solution).tap do |file|
8385
file.role ||= 'reference_implementation'
86+
file.feedback_message = nil
8487
end
8588
end
8689
end
@@ -90,14 +93,14 @@ def task_files
9093
@task.files.reject {|file| file.id == 'ms-placeholder-file' }.map do |task_file|
9194
codeocean_file_from_task_file(task_file).tap do |file|
9295
file.role ||= 'regular_file'
96+
file.feedback_message = nil
9397
end
9498
end
9599
end
96100

97101
def codeocean_file_from_task_file(file, parent_object = nil)
98102
extension = File.extname(file.filename)
99-
100-
codeocean_file = CodeOcean::File.where(context: @exercise).where('xml_id_path = ? OR xml_id_path LIKE ?', file.id, "%/#{file.id}").first_or_initialize(context: @exercise)
103+
codeocean_file = @exercise.files.where('array_length(xml_id_path, 1) IS NOT NULL AND xml_id_path[array_length(xml_id_path, 1)] = ?', file.id).first_or_initialize
101104
codeocean_file.assign_attributes(
102105
context: @exercise,
103106
file_type: file_type(extension),
@@ -106,7 +109,7 @@ def codeocean_file_from_task_file(file, parent_object = nil)
106109
read_only: file.usage_by_lms != 'edit',
107110
role: extract_meta_data(@task.meta_data&.dig('meta-data'), 'files', "CO-#{file.id}", 'role'),
108111
path: File.dirname(file.filename).in?(['.', '']) ? nil : File.dirname(file.filename),
109-
xml_id_path: (parent_object.nil? ? '' : "#{parent_object.id}/") + file.id.to_s
112+
xml_id_path: (parent_object.nil? ? [file.id] : [parent_object.id, file.id]).map(&:to_s)
110113
)
111114
if file.binary
112115
codeocean_file.native_file = FileIO.new(file.content.dup.force_encoding('UTF-8'), File.basename(file.filename))

db/migrate/20240903204319_add_xml_path_to_files.rb renamed to db/migrate/20241007204319_add_xml_path_to_files.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
class AddXmlPathToFiles < ActiveRecord::Migration[7.1]
44
def change
5-
add_column :files, :xml_id_path, :string, null: true, default: nil
5+
add_column :files, :xml_id_path, :string, array: true, default: [], null: true
66
end
77
end

db/schema.rb

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/services/proforma_service/convert_exercise_to_task_spec.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
execution_environment:,
2323
instructions: 'instruction',
2424
uuid: SecureRandom.uuid,
25-
files: files + tests)
25+
files: files + tests,
26+
unpublished:)
2627
end
28+
let(:unpublished) { false }
2729
let(:files) { [] }
2830
let(:tests) { [] }
2931
let(:execution_environment) { create(:java) }
@@ -77,6 +79,15 @@
7779
end
7880
end
7981

82+
context 'when exercise has no execution_environment' do
83+
let(:execution_environment) { nil }
84+
let(:unpublished) { true }
85+
86+
it 'creates a task with the correct proglang attribute' do
87+
expect(task).to have_attributes(proglang: nil)
88+
end
89+
end
90+
8091
context 'when exercise has a mainfile' do
8192
let(:files) { [file] }
8293
let(:file) { create(:file) }
@@ -111,13 +122,13 @@
111122
end
112123

113124
it 'sets xml_id_path to default' do
114-
expect { convert_to_task.execute }.to change(file.reload, :xml_id_path).from(nil).to(file.id.to_s)
125+
expect { convert_to_task.execute }.to change(file.reload, :xml_id_path).from([]).to([file.id.to_s])
115126
end
116127

117128
context 'when xml_id_path is set for file' do
118-
let(:file) { create(:file, xml_id_path: 'foobar') }
129+
let(:file) { create(:file, xml_id_path: ['foobar']) }
119130

120-
it 'does not change xml_path_id' do
131+
it 'does not change xml_id_path' do
121132
expect { convert_to_task.execute }.not_to change(file.reload, :xml_id_path)
122133
end
123134
end
@@ -224,7 +235,7 @@
224235
end
225236

226237
context 'when reference_implementations belong to the same proforma model_solution' do
227-
let(:files) { build_list(:file, 2, role: 'reference_implementation') {|file, i| file.xml_id_path = "proforma_ms/xml_id_#{i}" } }
238+
let(:files) { build_list(:file, 2, role: 'reference_implementation') {|file, i| file.xml_id_path = ['proforma_ms', "xml_id_#{i}"] } }
228239

229240
it 'creates a task with one model-solution' do
230241
expect(task.model_solutions).to have(1).items
@@ -294,7 +305,7 @@
294305
end
295306

296307
context 'when test_files belong to the same proforma test' do
297-
let(:tests) { build_list(:test_file, 2) {|file, i| file.xml_id_path = "proforma_test/xml_id_#{i}" } }
308+
let(:tests) { build_list(:test_file, 2) {|file, i| file.xml_id_path = ['proforma_test', "xml_id_#{i}"] } }
298309

299310
it 'creates a test with two file' do
300311
expect(task.tests.first).to have_attributes(

spec/services/proforma_service/convert_task_to_exercise_spec.rb

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,21 +558,50 @@
558558
context 'when the files with correct xml_id_paths already exist' do
559559
let(:exercise) do
560560
create(:dummy,
561+
unpublished: true,
561562
files: co_files,
562563
title: 'exercise-title',
563564
description: 'exercise-description')
564565
end
565566
let(:co_files) do
566-
[create(:file, xml_id_path: 'id', role: 'regular_file'),
567-
create(:file, xml_id_path: 'ms-id/ms-file-id', role: 'reference_implementation'),
568-
create(:test_file, xml_id_path: 'test-id/test-file-id')]
567+
[create(:file, xml_id_path: ['id'], role: 'regular_file'),
568+
create(:file, xml_id_path: %w[ms-id ms-file-id], role: 'reference_implementation'),
569+
create(:test_file, xml_id_path: %w[test-id test-file-id])]
569570
end
570571

571572
it 'reuses existing file' do
572573
convert_to_exercise_service
573-
574+
exercise.save!
574575
expect(exercise.reload.files).to match_array(co_files)
575576
end
577+
578+
context 'when files are move around' do
579+
let(:files) { [test_file] }
580+
let(:test_files) { [ms_file] }
581+
let(:ms_files) { [file] }
582+
583+
it 'reuses existing file' do
584+
convert_to_exercise_service
585+
exercise.save!
586+
expect(exercise.reload.files).to match_array(co_files)
587+
end
588+
end
589+
590+
context 'when files are removed' do
591+
let(:files) { [] }
592+
let(:test_files) { [] }
593+
let(:ms_files) { [] }
594+
595+
it 'removes files from exercise' do
596+
convert_to_exercise_service
597+
exercise.save!
598+
expect(exercise.reload.files).to be_empty
599+
end
600+
601+
it 'destroys removed files' do
602+
expect { convert_to_exercise_service }.to change(CodeOcean::File, :count).by(-3)
603+
end
604+
end
576605
end
577606
end
578607
end

spec/services/proforma_service/import_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
end
128128

129129
context 'when exercise has multiple tests' do
130-
let(:tests) { build_list(:test_file, 2) }
130+
let(:tests) { [build(:test_file, xml_id_path: %w[test file1]), build(:test_file, xml_id_path: %w[test file2])] }
131131

132132
it { is_expected.to be_an_equal_exercise_as exercise }
133133
end

0 commit comments

Comments
 (0)