Skip to content

Add lesson create_from_project route #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion app/controllers/api/lessons_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,27 @@ def create_copy
end
end

def create_from_project
# authorize the project
if lesson_params[:project_identifier].present?
project = Project.find_by(identifier: lesson_params[:project_identifier])
authorize! :update, project if project
end

result = Lesson::CreateFromProject.call(lesson_params:)

if result.success?
@lesson_with_user = result[:lesson].with_user
render :show, formats: [:json], status: :created
else
render json: { error: result[:error] }, status: :unprocessable_entity
end
end

def update
# TODO: Consider removing user_id from the lesson_params for update so users can update other users' lessons without changing ownership
# OR consider dropping user_id on lessons and using teacher id/ids on the class instead
result = Lesson::Update.call(lesson: @lesson, lesson_params:)
result = Lesson::Update.call(lesson: @lesson, lesson_params: base_params)

if result.success?
@lesson_with_user = result[:lesson].with_user
Expand Down Expand Up @@ -86,6 +103,7 @@ def base_params
:description,
:visibility,
:due_date,
:project_identifier,
{
project_attributes: [
:name,
Expand Down
5 changes: 4 additions & 1 deletion app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,16 @@ def define_school_teacher_abilities(user:, school:)
can(%i[create update destroy], Lesson) do |lesson|
school_teacher_can_manage_lesson?(user:, school:, lesson:)
end
can(%i[create_from_project], Lesson) do |lesson|
school_teacher_can_manage_lesson?(user:, school:, lesson:) && school_teacher_can_manage_project?(user:, school:, project: lesson.project)
end
can(%i[read create_copy], Lesson, school_id: school.id, visibility: %w[teachers students])
can(%i[create], Project) do |project|
school_teacher_can_manage_project?(user:, school:, project:)
end
can(%i[read update show_context], Project, school_id: school.id, lesson: { visibility: %w[teachers students] })
can(%i[read], Project,
remixed_from_id: Project.where(school_id: school.id, remixed_from_id: nil, lesson_id: Lesson.where(school_class_id: ClassTeacher.where(teacher_id: user.id).select(:school_class_id))).pluck(:id))
remixed_from_id: Project.where(school_id: school.id, lesson_id: Lesson.where(school_class_id: ClassTeacher.where(teacher_id: user.id).select(:school_class_id))).pluck(:id))
end

def define_school_student_abilities(user:, school:)
Expand Down
14 changes: 14 additions & 0 deletions app/models/lesson.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class Lesson < ApplicationRecord

validate :user_has_the_school_owner_or_school_teacher_role_for_the_school
validate :user_is_the_school_teacher_for_the_school_class
validate :project_belongs_to_the_same_school
validate :project_belongs_to_the_same_user

scope :archived, -> { where.not(archived_at: nil) }
scope :unarchived, -> { where(archived_at: nil) }
Expand Down Expand Up @@ -74,4 +76,16 @@ def user_is_the_school_teacher_for_the_school_class

errors.add(:user, "'#{user_id}' is not the 'school-teacher' for school_class '#{school_class.id}'")
end

def project_belongs_to_the_same_school
return unless (project && (school || project&.school) && project&.school_id != school&.id)

errors.add(:project, "must belong to the same school (#{school&.id}) as the lesson (#{id})")
end

def project_belongs_to_the_same_user
return unless project && user_id && project.user_id != user_id

errors.add(:project, "must belong to the same user (#{user_id}) as the lesson (#{id})")
end
end
7 changes: 7 additions & 0 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module Types
validate :project_with_instructions_must_belong_to_school
validate :project_with_school_id_has_school_project
validate :school_project_school_matches_project_school
validate :lesson_id_cannot_be_changed

default_scope -> { where.not(project_type: Types::SCRATCH) }

Expand Down Expand Up @@ -142,4 +143,10 @@ def school_project_school_matches_project_school

errors.add(:school_project, 'School project school_id must match project school_id')
end

def lesson_id_cannot_be_changed
return unless lesson_id_changed? && lesson_id_was.present?

errors.add(:lesson_id, 'cannot be changed once set')
end
end
1 change: 0 additions & 1 deletion app/views/api/lessons/index.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ json.array!(@lessons_with_users) do |lesson, user|
:identifier,
:project_type
)
json.project.finished(lesson.project.finished) if lesson.project.remixed_from_id.present?
end

json.user_name(user&.name)
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

resources :lessons, only: %i[index create show update destroy] do
post :copy, on: :member, to: 'lessons#create_copy'
post :create_from_project, on: :collection, to: 'lessons#create_from_project'
end

resources :teacher_invitations, param: :token, only: :show do
Expand Down
31 changes: 31 additions & 0 deletions lib/concepts/lesson/operations/create_from_project.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

class Lesson
class CreateFromProject
class << self
def call(lesson_params:)
response = OperationResponse.new
response[:lesson] = build_lesson_from_project(lesson_params)
response[:lesson].save!
response
rescue StandardError => e
Sentry.capture_exception(e)
errors = response[:lesson].errors.full_messages.join(',')
response[:error] = "Error creating lesson from project: #{errors}"
response
end

private

def build_lesson_from_project(lesson_params)
project = Project.find_by(identifier: lesson_params[:project_identifier])
lesson = Lesson.new(
name: project.name
)
lesson.assign_attributes(lesson_params.except(:project_identifier))
lesson.project = project
lesson
end
end
end
end
2 changes: 1 addition & 1 deletion spec/factories/lesson.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
sequence(:name) { |n| "Lesson #{n}" }
description { 'Description' }
visibility { 'teachers' }
project { create(:project, user_id:, name:) }
project { create(:project, user_id:, name:, school_id: school_id || school&.id || school_class&.school&.id) }

trait :with_project_components do
transient do
Expand Down
2 changes: 1 addition & 1 deletion spec/features/lesson/archiving_a_lesson_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
end

it "responds 403 Forbidden when the user is not the lesson's owner" do
lesson.update!(user_id: SecureRandom.uuid)
authenticated_in_hydra_as(teacher)

delete("/api/lessons/#{lesson.id}", headers:)
expect(response).to have_http_status(:forbidden)
Expand Down
5 changes: 2 additions & 3 deletions spec/features/lesson/updating_a_lesson_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
}
end
let!(:lesson) { create(:lesson, name: 'Test Lesson', user_id: owner.id) }
let(:teacher_lesson) { create(:lesson, name: 'Test Teacher Lesson', user_id: teacher.id) }
let(:teacher) { create(:teacher, school:) }
let(:school) { create(:verified_school) }
let(:owner) { create(:owner, school:, name: 'School Owner') }
Expand Down Expand Up @@ -51,9 +52,7 @@
end

it "responds 403 Forbidden when the user is not the lesson's owner" do
lesson.update!(user_id: SecureRandom.uuid)

put("/api/lessons/#{lesson.id}", headers:, params:)
put("/api/lessons/#{teacher_lesson.id}", headers:, params:)
expect(response).to have_http_status(:forbidden)
end

Expand Down
12 changes: 12 additions & 0 deletions spec/models/lesson_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,19 @@
expect(lesson).to be_invalid
end

it 'requires the user id to match the user_id on the project' do
lesson.project = build(:project, user_id: SecureRandom.uuid)
expect(lesson).to be_invalid
end

it 'requires the project to belong to the same school as the lesson' do
lesson.project = build(:project, school: create(:school))
expect(lesson).to be_invalid
end

context 'when the lesson has a school' do
before do
lesson.project.update!(school:)
lesson.update!(school:)
end

Expand All @@ -84,6 +95,7 @@

context 'when the lesson has a school_class' do
before do
lesson.project.update!(school:)
lesson.update!(school_class: create(:school_class, teacher_ids: [teacher.id], school:))
end

Expand Down