Skip to content

Commit b46820b

Browse files
Auto-creating project when lesson created (#342)
Co-authored-by: Dan Halson <[email protected]>
1 parent 3f07206 commit b46820b

File tree

14 files changed

+149
-45
lines changed

14 files changed

+149
-45
lines changed

Gemfile.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ GEM
498498
PLATFORMS
499499
aarch64-linux
500500
arm64-darwin-22
501+
arm64-darwin-23
501502
x86_64-linux
502503

503504
DEPENDENCIES

app/controllers/api/lessons_controller.rb

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,24 +72,25 @@ def verify_school_class_belongs_to_school
7272
end
7373

7474
def lesson_params
75-
if school_owner?
76-
# A school owner must specify who the lesson user is.
77-
base_params
78-
else
79-
# A school teacher may only create lessons they own.
80-
base_params.merge(user_id: current_user.id)
81-
end
75+
base_params.merge(user_id: current_user.id)
8276
end
8377

8478
def base_params
8579
params.fetch(:lesson, {}).permit(
8680
:school_id,
8781
:school_class_id,
88-
:user_id,
8982
:name,
9083
:description,
9184
:visibility,
92-
:due_date
85+
:due_date,
86+
{
87+
project_attributes: [
88+
:name,
89+
:project_type,
90+
:locale,
91+
{ components: %i[id name extension content index default] }
92+
]
93+
}
9394
)
9495
end
9596

app/models/lesson.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ class Lesson < ApplicationRecord
55
belongs_to :school_class, optional: true
66
belongs_to :parent, optional: true, class_name: :Lesson, foreign_key: :copied_from_id, inverse_of: :copies
77
has_many :copies, dependent: :nullify, class_name: :Lesson, foreign_key: :copied_from_id, inverse_of: :parent
8-
has_many :projects, dependent: :nullify
8+
has_one :project, dependent: :nullify
9+
accepts_nested_attributes_for :project
910

1011
before_validation :assign_school_from_school_class
1112
before_destroy -> { throw :abort }

app/views/api/lessons/show.json.jbuilder

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,12 @@ json.call(
1818
:updated_at
1919
)
2020

21+
if lesson.project
22+
json.project(
23+
lesson.project,
24+
:identifier,
25+
:project_type
26+
)
27+
end
28+
2129
json.user_name(user&.name)

lib/concepts/lesson/operations/create.rb

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,30 @@ class Create
55
class << self
66
def call(lesson_params:)
77
response = OperationResponse.new
8-
response[:lesson] = Lesson.new(lesson_params)
8+
response[:lesson] = build_lesson(lesson_params)
99
response[:lesson].save!
1010
response
1111
rescue StandardError => e
1212
Sentry.capture_exception(e)
13-
errors = response[:lesson].errors.full_messages.join(',')
14-
response[:error] = "Error creating lesson: #{errors}"
13+
if response[:lesson].nil?
14+
response[:error] = "Error creating lesson #{e}"
15+
else
16+
errors = response[:lesson].errors.full_messages.join(',')
17+
response[:error] = "Error creating lesson: #{errors}"
18+
end
1519
response
1620
end
21+
22+
private
23+
24+
def build_lesson(lesson_hash)
25+
new_lesson = Lesson.new(lesson_hash.except(:project_attributes))
26+
project_params = lesson_hash[:project_attributes].merge({ user_id: lesson_hash[:user_id],
27+
school_id: lesson_hash[:school_id],
28+
lesson_id: new_lesson.id })
29+
new_lesson.project = Project.new(project_params)
30+
new_lesson
31+
end
1732
end
1833
end
1934
end

lib/concepts/project/operations/create.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def call(project_hash:)
1010
response
1111
rescue StandardError => e
1212
Sentry.capture_exception(e)
13-
response[:error] = 'Error creating project'
13+
response[:error] = "Error creating project: #{e}"
1414
response
1515
end
1616

lib/tasks/classroom_management.rake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ namespace :classroom_management do
2727
exit
2828
end
2929

30-
Project.where(school_id:).destroy_all
30+
lesson_id = Lesson.where(school_id:).pluck(:id)
31+
Project.where(lesson_id:).destroy_all
3132
# The `before_destroy` prevents us using destroy, but as we've removed projects already we can safely delete
3233
Lesson.where(school_id:).delete_all
3334
SchoolClass.where(school_id:).destroy_all

spec/concepts/lesson/create_spec.rb

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@
66
let(:teacher_id) { SecureRandom.uuid }
77

88
let(:lesson_params) do
9-
{ name: 'Test Lesson', user_id: teacher_id }
9+
{
10+
name: 'Test Lesson',
11+
user_id: teacher_id,
12+
project_attributes: {
13+
name: 'Hello world project',
14+
project_type: 'python',
15+
components: [
16+
{ name: 'main.py', extension: 'py', content: 'print("Hello, world!")' }
17+
]
18+
}
19+
}
1020
end
1121

1222
it 'returns a successful operation response' do
@@ -28,13 +38,71 @@
2838
expect(response[:lesson].name).to eq('Test Lesson')
2939
end
3040

41+
it 'creates a project for the lesson' do
42+
expect { described_class.call(lesson_params:) }.to change(Project, :count).by(1)
43+
end
44+
45+
it 'associates the project to the lesson' do
46+
response = described_class.call(lesson_params:)
47+
expect(response[:lesson].project).to be_a(Project)
48+
end
49+
3150
it 'assigns the user_id' do
3251
response = described_class.call(lesson_params:)
3352
expect(response[:lesson].user_id).to eq(teacher_id)
3453
end
3554

36-
context 'when creation fails' do
37-
let(:lesson_params) { {} }
55+
context 'when lesson creation fails' do
56+
let(:lesson_params) do
57+
{
58+
project_attributes: {
59+
name: 'Hello world project',
60+
project_type: 'python',
61+
components: [
62+
{ name: 'main.py', extension: 'py', content: 'print("Hello, world!")' }
63+
]
64+
}
65+
}
66+
end
67+
68+
before do
69+
allow(Sentry).to receive(:capture_exception)
70+
end
71+
72+
it 'does not create a lesson' do
73+
expect { described_class.call(lesson_params:) }.not_to change(Lesson, :count)
74+
end
75+
76+
it 'does not create a project' do
77+
expect { described_class.call(lesson_params:) }.not_to change(Project, :count)
78+
end
79+
80+
it 'returns a failed operation response' do
81+
response = described_class.call(lesson_params:)
82+
expect(response.failure?).to be(true)
83+
end
84+
85+
it 'returns the error message in the operation response' do
86+
response = described_class.call(lesson_params:)
87+
expect(response[:error]).to match(/Error creating lesson/)
88+
end
89+
90+
it 'sent the exception to Sentry' do
91+
described_class.call(lesson_params:)
92+
expect(Sentry).to have_received(:capture_exception).with(kind_of(StandardError))
93+
end
94+
end
95+
96+
context 'when project creation fails' do
97+
let(:lesson_params) do
98+
{
99+
name: 'Test Lesson',
100+
user_id: teacher_id,
101+
project_attributes: {
102+
invalid_attribute: 'blah blah blah'
103+
}
104+
}
105+
end
38106

39107
before do
40108
allow(Sentry).to receive(:capture_exception)
@@ -44,6 +112,10 @@
44112
expect { described_class.call(lesson_params:) }.not_to change(Lesson, :count)
45113
end
46114

115+
it 'does not create a project' do
116+
expect { described_class.call(lesson_params:) }.not_to change(Project, :count)
117+
end
118+
47119
it 'returns a failed operation response' do
48120
response = described_class.call(lesson_params:)
49121
expect(response.failure?).to be(true)

spec/concepts/project/create_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
end
5555

5656
it 'returns error message' do
57-
expect(create_project[:error]).to eq('Error creating project')
57+
expect(create_project[:error]).to eq('Error creating project: Some error')
5858
end
5959

6060
it 'sent the exception to Sentry' do

spec/factories/lesson.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
sequence(:name) { |n| "Lesson #{n}" }
77
description { 'Description' }
88
visibility { 'private' }
9+
project { create(:project) }
910
end
1011
end

0 commit comments

Comments
 (0)