Skip to content

Commit

Permalink
refactor import to reuse task_files based on xml_id
Browse files Browse the repository at this point in the history
  • Loading branch information
kkoehn committed Sep 17, 2024
1 parent aaae548 commit 2688d42
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 49 deletions.
6 changes: 4 additions & 2 deletions app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@ def overall_rating
average_rating[:overall_rating]
end

def all_files
(files + tests.map(&:files) + model_solutions.map(&:files)).flatten
def all_files(cached: true)
return @all_files if cached && defined?(@all_files)

@all_files = (files + tests.map(&:files) + model_solutions.map(&:files)).flatten
end

def label_names=(label_names)
Expand Down
4 changes: 2 additions & 2 deletions app/models/task_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TaskFile < ApplicationRecord
validates :xml_id, presence: true
validates :visible, inclusion: {in: %w[yes no delayed]}
validates :used_by_grader, inclusion: {in: [true, false]}
validate :unique_xml_id, if: -> { !fileable.nil? }
validate :unique_xml_id, if: -> { !fileable.nil? && xml_id_changed? }

attr_accessor :use_attached_file, :file_marked_for_deletion

Expand Down Expand Up @@ -52,7 +52,7 @@ def remove_attachment

def unique_xml_id
task = fileable.is_a?(Task) ? fileable : fileable.task
xml_ids = (task.all_files - [self]).map(&:xml_id)
xml_ids = (task.all_files(cached: false) - [self]).map(&:xml_id)
errors.add(:xml_id, :not_unique) if xml_ids.include? xml_id
end
end
63 changes: 41 additions & 22 deletions app/services/proforma_service/convert_proforma_task_to_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ def execute

def import_task
@task.assign_attributes(
user: @user,
user:,
title: @proforma_task.title,
description:,
internal_description: @proforma_task.internal_description,
programming_language:,
uuid:,
parent_uuid: @proforma_task.parent_uuid,
parent_uuid:,
language: @proforma_task.language,
meta_data: @proforma_task.meta_data,

Expand All @@ -39,6 +39,14 @@ def import_task
)
end

def parent_uuid
@proforma_task.parent_uuid || @task.parent_uuid
end

def user
@task.user || @user
end

def uuid
@task.uuid || @proforma_task.uuid
end
Expand All @@ -52,15 +60,24 @@ def files
end

def file_from_proforma_file(proforma_task_file)
task_file = TaskFile.new(file_attributes(proforma_task_file))
task_file = @task&.all_files&.select {|file| file.xml_id == proforma_task_file.id }&.first || TaskFile.new
task_file.assign_attributes(file_attributes(proforma_task_file))
attach_file_content(proforma_task_file, task_file)
task_file
end

def attach_file_content(proforma_task_file, task_file)
if proforma_task_file.binary
task_file.attachment.attach(io: StringIO.new(proforma_task_file.content), filename: proforma_task_file.filename,
content_type: proforma_task_file.mimetype)
task_file.attachment.attach(
io: StringIO.new(proforma_task_file.content),
filename: proforma_task_file.filename,
content_type: proforma_task_file.mimetype
)
task_file.use_attached_file = 'true'
else
task_file.content = proforma_task_file.content
task_file.use_attached_file = 'false'
end
task_file
end

def xml_file_id(xml_id)
Expand All @@ -87,16 +104,17 @@ def file_attributes(proforma_task_file)

def tests
@proforma_task.tests.map do |test|
Test.new(
xml_id: test.id,
title: test.title,
description: test.description,
internal_description: test.internal_description,
test_type: test.test_type,
meta_data: test.meta_data,
configuration: test.configuration,
files: test.files.map {|task_file| file_from_proforma_file(task_file) }
)
@task.tests.find_or_initialize_by(xml_id: test.id).tap do |ch_test|
ch_test.assign_attributes(
title: test.title,
description: test.description,
internal_description: test.internal_description,
test_type: test.test_type,
meta_data: test.meta_data,
configuration: test.configuration,
files: test.files.map {|task_file| file_from_proforma_file(task_file) }
)
end
end
end

Expand All @@ -117,12 +135,13 @@ def programming_language # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticCo

def model_solutions
@proforma_task.model_solutions.map do |model_solution|
ModelSolution.new(
xml_id: model_solution.id,
description: model_solution.description,
internal_description: model_solution.internal_description,
files: model_solution.files.map {|task_file| file_from_proforma_file(task_file) }
)
@task.model_solutions.find_or_initialize_by(xml_id: model_solution.id).tap do |ch_model_solution|
ch_model_solution.assign_attributes(
description: model_solution.description,
internal_description: model_solution.internal_description,
files: model_solution.files.map {|task_file| file_from_proforma_file(task_file) }
)
end
end
end
end
Expand Down
46 changes: 46 additions & 0 deletions spec/models/task_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,50 @@
end
end
end

describe '#all_files' do
subject(:all_files) { task.all_files }

let(:task) { create(:task, files:, model_solutions:, tests:) }
let(:files) { build_list(:task_file, 1) }
let(:model_solutions) do
build_list(:model_solution, 2) do |model_solution|
model_solution.files = build_list(:task_file, 3)
end
end
let(:tests) do
build_list(:test, 4) do |test|
test.files = build_list(:task_file, 5)
end
end

it { is_expected.to have_attributes(size: 27) }

context 'with specific task_files' do
let(:model_solutions) { build_list(:model_solution, 1, files: model_solution_files) }
let(:model_solution_files) { build_list(:task_file, 3) }
let(:tests) { build_list(:test, 1, files: test_files) }
let(:test_files) { build_list(:task_file, 4) }

it { is_expected.to match_array(files + model_solution_files + test_files) }
end

context 'when all_files is called again' do
before do
all_files
task.tests.destroy_all
task.model_solutions.destroy_all
end

it 'returns the cached result' do
expect(all_files).to have_attributes(size: 27)
end

context 'with cache: false' do
it 'returns the correct result' do
expect(task.all_files(cached: false)).to have_attributes(size: 1)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -576,5 +576,31 @@
end
end
end

context 'when proforma_task has been exported from task' do
let(:proforma_task) { ProformaService::ConvertTaskToProformaTask.call(task:) }
let(:task) { create(:task, files: build_list(:task_file, 1, :exportable), tests:, model_solutions:, title: 'title') }
let(:tests) { build_list(:test, 1, files: build_list(:task_file, 1, :exportable)) }
let(:model_solutions) { build_list(:model_solution, 1, files: build_list(:task_file, 1, :exportable)) }

it { is_expected.to be_an_equal_task_as task }

it 'reuses existing objects' do
expect(convert_to_task_service).to have_attributes(
id: task.id,
files: have(1).item.and(include(have_attributes(
id: task.files.first.id
))),
model_solutions: have(1).item.and(include(have_attributes(
id: model_solutions.first.id,
files: include(have_attributes(id: model_solutions.first.files.first.id))
))),
tests: have(1).item.and(include(have_attributes(
id: tests.first.id,
files: include(have_attributes(id: tests.first.files.first.id))
)))
)
end
end
end
end
50 changes: 27 additions & 23 deletions spec/services/proforma_service/import_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@

RSpec.describe ProformaService::Import do
describe '.new' do
subject(:import_service) { described_class.new(zip:, user:) }
subject(:imported_task) { described_class.new(zip:, user:) }

let(:zip) { Tempfile.new('proforma_test_zip_file') }
let(:user) { build(:user) }

it 'assigns zip' do
expect(import_service.instance_variable_get(:@zip)).to be zip
expect(imported_task.instance_variable_get(:@zip)).to be zip
end

it 'assigns user' do
expect(import_service.instance_variable_get(:@user)).to be user
expect(imported_task.instance_variable_get(:@user)).to be user
end
end

describe '#execute' do
subject(:import_service) { described_class.call(zip: zip_file, user: import_user) }
subject(:imported_task) { described_class.call(zip: zip_file, user: import_user) }

let(:user) { build(:user) }
let(:import_user) { user }
Expand Down Expand Up @@ -57,12 +57,16 @@

it { is_expected.to be_an_equal_task_as task }

it 'sets the correct user as owner of the task' do
expect(import_service.user).to be user
it 'sets the uuid' do
expect(imported_task.uuid).not_to be_blank
end

it 'sets the uuid' do
expect(import_service.uuid).not_to be_blank
context 'when task is imported by a different user, but is admin' do
let(:import_user) { build(:admin) }

it 'does not overwrite existing owner' do
expect(imported_task.user).to eql user
end
end

context 'when no task exists' do
Expand All @@ -71,18 +75,18 @@
it { is_expected.to be_valid }

it 'sets the correct user as owner of the task' do
expect(import_service.user).to be user
expect(imported_task.user).to be user
end

it 'sets the uuid' do
expect(import_service.uuid).not_to be_blank
expect(imported_task.uuid).not_to be_blank
end

context 'when task has a uuid' do
let(:uuid) { SecureRandom.uuid }

it 'sets the uuid' do
expect(import_service.uuid).to eql uuid
expect(imported_task.uuid).to eql uuid
end
end
end
Expand All @@ -91,31 +95,31 @@
let(:meta_data) { attributes_for(:task, :with_meta_data)[:meta_data] }

it 'sets the meta_data' do
expect(import_service.meta_data).to eql meta_data
expect(imported_task.meta_data).to eql meta_data
end
end

context 'when task has submission_restrictions' do
let(:submission_restrictions) { attributes_for(:task, :with_submission_restrictions)[:submission_restrictions] }

it 'sets the submission_restrictions' do
expect(import_service.submission_restrictions).to eql submission_restrictions
expect(imported_task.submission_restrictions).to eql submission_restrictions
end
end

context 'when task has external_resources' do
let(:external_resources) { attributes_for(:task, :with_external_resources)[:external_resources] }

it 'sets the external_resources' do
expect(import_service.external_resources).to eql external_resources
expect(imported_task.external_resources).to eql external_resources
end
end

context 'when task has grading_hints' do
let(:grading_hints) { attributes_for(:task, :with_grading_hints)[:grading_hints] }

it 'sets the grading_hints' do
expect(import_service.grading_hints).to eql grading_hints
expect(imported_task.grading_hints).to eql grading_hints
end
end

Expand Down Expand Up @@ -163,7 +167,7 @@
let(:test_meta_data) { attributes_for(:test, :with_meta_data)[:meta_data] }

it 'sets the meta_data' do
expect(import_service.tests.first.meta_data).to eql test_meta_data
expect(imported_task.tests.first.meta_data).to eql test_meta_data
end
end

Expand All @@ -173,7 +177,7 @@
end

it 'sets the configuration' do
expect(import_service.tests.first.configuration).to eql test_configuration
expect(imported_task.tests.first.configuration).to eql test_configuration
end
end
end
Expand All @@ -196,19 +200,19 @@
end

it 'imports the tasks from zip containing multiple zips' do
expect(import_service).to all be_an(Task)
expect(imported_task).to all be_an(Task)
end

it 'imports the zip exactly how they were exported' do
expect(import_service).to all be_an_equal_task_as(task).or be_an_equal_task_as(task2)
expect(imported_task).to all be_an_equal_task_as(task).or be_an_equal_task_as(task2)
end

context 'when a task has files and tests' do
let(:files) { build_list(:task_file, 2, :exportable) }
let(:tests) { build_list(:test, 2, test_type: 'test_type') }

it 'imports the zip exactly how the were exported' do
expect(import_service).to all be_an_equal_task_as(task).or be_an_equal_task_as(task2)
expect(imported_task).to all be_an_equal_task_as(task).or be_an_equal_task_as(task2)
end
end
end
Expand All @@ -222,22 +226,22 @@
end

it 'creates a new task' do
expect(import_service.id).not_to be task.id
expect(imported_task.id).not_to be task.id
end
end

context 'when task in zip has the same uuid and nothing has changed' do
let(:uuid) { SecureRandom.uuid }

it 'updates the old task' do
expect(import_service.id).to be task.id
expect(imported_task.id).to be task.id
end

context 'when another user imports the task' do
let(:import_user) { create(:user) }

it 'creates a new task' do
expect(import_service.id).not_to be task.id
expect(imported_task.id).not_to be task.id
end
end
end
Expand Down

0 comments on commit 2688d42

Please sign in to comment.