Skip to content

Commit 2fc0f23

Browse files
authored
Create feedback (#601)
## Status Related to RaspberryPiFoundation/digital-editor-issues#928 Overall epic is RaspberryPiFoundation/digital-editor-issues#944 ## What's changed? - Added model, controller, route etc. to allow teacher to create feedback on their students' work.
1 parent 1876049 commit 2fc0f23

File tree

17 files changed

+501
-3
lines changed

17 files changed

+501
-3
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
module Api
4+
class FeedbackController < ApiController
5+
before_action :authorize_user
6+
load_and_authorize_resource :feedback
7+
8+
def create
9+
result = Feedback::Create.call(feedback_params: feedback_create_params)
10+
11+
if result.success?
12+
@feedback = result[:feedback]
13+
render :show, formats: [:json], status: :created
14+
else
15+
render json: { error: result[:error] }, status: :unprocessable_entity
16+
end
17+
end
18+
19+
# These params are used to authorize the resource with CanCanCan. The project identifier is sent in the URL,
20+
# but these params need to match the shape of the feedback object whiich is attached to the SchoolProject,
21+
# not the Project.
22+
def feedback_params
23+
school_project = Project.find_by(identifier: base_params[:identifier])&.school_project
24+
feedback_create_params.except(:identifier).merge(
25+
school_project_id: school_project&.id
26+
)
27+
end
28+
29+
# These params are used to create the feedback in the Feedback::Create operation. The project_id parameter,
30+
# which is automatically named by Rails based on the route structure, is renamed to identifier for readability,
31+
# as it is actually the human-readable project_identifier, not the project_id.
32+
def feedback_create_params
33+
base_params.merge(user_id: current_user.id)
34+
end
35+
36+
def url_params
37+
permitted_params = params.permit(:project_id)
38+
{ identifier: permitted_params[:project_id] }
39+
end
40+
41+
def base_params
42+
params.fetch(:feedback, {}).permit(
43+
:content
44+
).merge(url_params)
45+
end
46+
end
47+
end

app/models/ability.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,15 @@ def define_school_teacher_abilities(user:, school:)
8888
school_teacher_can_manage_project?(user:, school:, project:)
8989
end
9090
can(%i[read update show_context], Project, school_id: school.id, lesson: { visibility: %w[teachers students] })
91-
can(%i[read], Project,
92-
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))
91+
teacher_project_ids = Project.where(
92+
school_id: school.id,
93+
remixed_from_id: nil,
94+
lesson_id: Lesson.where(
95+
school_class_id: ClassTeacher.where(teacher_id: user.id).select(:school_class_id)
96+
)
97+
).pluck(:id)
98+
can(%i[read], Project, remixed_from_id: teacher_project_ids)
99+
can(%i[create], Feedback, school_project: { project: { remixed_from_id: teacher_project_ids } })
93100
end
94101

95102
def define_school_student_abilities(user:, school:)

app/models/feedback.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
class Feedback < ApplicationRecord
4+
belongs_to :school_project
5+
validates :content, presence: true
6+
validates :user_id, presence: true
7+
validate :user_has_the_school_owner_or_school_teacher_role_for_the_school
8+
validate :parent_project_belongs_to_lesson
9+
validate :parent_project_belongs_to_school_class
10+
validate :user_is_the_class_teacher_for_the_school_project
11+
12+
has_paper_trail(
13+
meta: {
14+
meta_school_project_id: ->(f) { f.school_project&.id },
15+
meta_school_id: ->(f) { f.school_project&.school_id }
16+
}
17+
)
18+
19+
private
20+
21+
def user_has_the_school_owner_or_school_teacher_role_for_the_school
22+
school = school_project&.school
23+
return unless user_id_changed? && errors.blank? && school
24+
25+
user = User.new(id: user_id)
26+
return if user.school_owner?(school)
27+
return if user.school_teacher?(school)
28+
29+
msg = "'#{user_id}' does not have the 'school-owner' or 'school-teacher' role for organisation '#{school.id}'"
30+
errors.add(:user, msg)
31+
end
32+
33+
def parent_project_belongs_to_lesson
34+
parent_project = school_project&.project&.parent
35+
return if parent_project&.lesson_id.present?
36+
37+
msg = "Parent project '#{parent_project&.id}' does not belong to a 'lesson'"
38+
errors.add(:user, msg)
39+
end
40+
41+
def parent_project_belongs_to_school_class
42+
parent_project = school_project&.project&.parent
43+
return if parent_project&.lesson&.school_class_id.present?
44+
45+
msg = "Parent project '#{parent_project&.id}' does not belong to a 'school-class'"
46+
errors.add(:user, msg)
47+
end
48+
49+
def user_is_the_class_teacher_for_the_school_project
50+
return if !school_project || school_class&.teacher_ids&.include?(user_id)
51+
52+
errors.add(:user, "'#{user_id}' is not the 'school-teacher' for school_project '#{school_project.id}'")
53+
end
54+
55+
def school_class
56+
school_project&.project&.parent&.lesson&.school_class
57+
end
58+
end

app/models/school_project.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
class SchoolProject < ApplicationRecord
44
belongs_to :school
55
belongs_to :project
6+
has_many :feedback, dependent: :destroy
67
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
json.call(
4+
feedback,
5+
:id,
6+
:school_project_id,
7+
:user_id,
8+
:content,
9+
:created_at,
10+
:updated_at
11+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# frozen_string_literal: true
2+
3+
json.partial! 'feedback', feedback: @feedback

config/initializers/inflections.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
# inflect.uncountable %w( fish sheep )
1313
# end
1414

15+
ActiveSupport::Inflector.inflections(:en) do |inflect|
16+
inflect.uncountable %w[feedback]
17+
end
18+
1519
# These inflection rules are supported but not enabled by default:
1620
ActiveSupport::Inflector.inflections(:en) do |inflect|
1721
inflect.acronym 'SSO'

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
resource :remix, only: %i[show create], controller: 'projects/remixes'
4242
resources :remixes, only: %i[index], controller: 'projects/remixes'
4343
resource :images, only: %i[show create], controller: 'projects/images'
44+
resource :feedback, only: %i[create], controller: 'feedback'
4445
end
4546

4647
resource :project_errors, only: %i[create]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class CreateFeedback < ActiveRecord::Migration[7.2]
2+
def change
3+
create_table :feedback, id: :uuid do |t|
4+
t.references :school_project, foreign_key: true, type: :uuid
5+
t.text :content
6+
t.uuid :user_id, null: false
7+
t.timestamps
8+
end
9+
end
10+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddMetaSchoolProjectIdToVersions < ActiveRecord::Migration[7.2]
2+
def change
3+
add_column :versions, :meta_school_project_id, :string
4+
end
5+
end

0 commit comments

Comments
 (0)