• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MarkUsProject / Markus / 24582640462

17 Apr 2026 07:19PM UTC coverage: 91.723% (+0.01%) from 91.709%
24582640462

Pull #7886

github

web-flow
Merge e8ea90d1f into 28089e552
Pull Request #7886: TICKET-604: Return structured JSON from grade entry forms show endpoint

950 of 1844 branches covered (51.52%)

Branch coverage included in aggregate %.

121 of 123 new or added lines in 3 files covered. (98.37%)

45269 of 48546 relevant lines covered (93.25%)

130.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.65
/app/controllers/api/grade_entry_forms_controller.rb
1
module Api
1✔
2
  class GradeEntryFormsController < MainApiController
1✔
3
    DEFAULT_FIELDS = [:id, :short_identifier, :description, :due_date, :is_hidden, :visible_on, :visible_until,
1✔
4
                      :show_total].freeze
5

6
    # Returns the specified grade entry form
7
    # Requires: id
8
    # Default: returns CSV export (backward compatible)
9
    # Optional: .json extension or Accept: application/json header for structured JSON with student grades
10
    #           user_names[] (filter to specific students)
11
    def show
1✔
12
      grade_entry_form = record
8✔
13
      if grade_entry_form.nil?
8✔
NEW
14
        render 'shared/http_status', locals: { code: '404', message:
×
15
          'Grade Entry Form not found' }, status: :not_found
NEW
16
        return
×
17
      end
18

19
      user_names = params[:user_names].presence
8✔
20

21
      respond_to do |format|
8✔
22
        format.json do
8✔
23
          render json: grade_entry_form.export_as_json(current_role, user_names: user_names)
7✔
24
        end
25
        format.any do
8✔
26
          send_data grade_entry_form.export_as_csv(current_role, user_names: user_names),
1✔
27
                    type: 'text/csv',
28
                    filename: "#{grade_entry_form.short_identifier}_grades_report.csv",
29
                    disposition: 'inline'
30
        end
31
      end
32
    end
33

34
    def index
1✔
35
      grade_entry_forms = get_collection(current_course.grade_entry_forms) || return
18✔
36

37
      include_args = { only: DEFAULT_FIELDS, include: { grade_entry_items: { only: [:id, :name, :out_of] } } }
18✔
38

39
      respond_to do |format|
18✔
40
        format.xml do
18✔
41
          xml = grade_entry_forms.to_xml(**include_args, root: 'grade_entry_forms', skip_types: 'true')
9✔
42
          render xml: xml
9✔
43
        end
44
        format.json { render json: grade_entry_forms.to_json(include_args) }
27✔
45
      end
46
    end
47

48
    # create a new grade entry form
49
    # Requires:
50
    #   :short_identifier, :description
51
    # Optional:
52
    #   :description, :due_date, :is_hidden
53
    #   grade_entry_items:
54
    #     :name, :out_of, :bonus
55
    def create
1✔
56
      if has_missing_params?([:short_identifier])
10✔
57
        # incomplete/invalid HTTP params
58
        render 'shared/http_status', locals: { code: '422', message:
2✔
59
          HttpStatusHelper::ERROR_CODE['message']['422'] }, status: :unprocessable_content
60
        return
2✔
61
      end
62

63
      # check if there is an existing assignment
64
      form = current_course.grade_entry_forms.find_by(short_identifier: params[:short_identifier])
8✔
65
      unless form.nil?
8✔
66
        render 'shared/http_status', locals: { code: '409', message:
1✔
67
          'Grade Entry Form already exists' }, status: :conflict
68
        return
1✔
69
      end
70

71
      ApplicationRecord.transaction do
7✔
72
        create_params = params.permit(*DEFAULT_FIELDS)
7✔
73
        create_params[:is_hidden] ||= false
7✔
74
        create_params[:description] ||= ''
7✔
75
        create_params[:course_id] = params[:course_id]
7✔
76
        new_form = GradeEntryForm.new(create_params)
7✔
77
        unless new_form.save
7✔
78
          render 'shared/http_status', locals: { code: '422', message:
3✔
79
            new_form.errors.full_messages.first }, status: :unprocessable_content
80
          raise ActiveRecord::Rollback
3✔
81
        end
82

83
        params[:grade_entry_items]&.each&.with_index do |column_params, i|
4✔
84
          column_params = column_params.permit(:name, :out_of, :bonus).to_h.symbolize_keys
×
85
          grade_item = new_form.grade_entry_items.build(**column_params, position: i + 1)
×
86
          unless grade_item.save
×
87
            render 'shared/http_status', locals: { code: '422', message:
×
88
              grade_item.errors.full_messages.first }, status: :unprocessable_content
89
            raise ActiveRecord::Rollback
×
90
          end
91
        end
92
        render 'shared/http_status', locals: { code: '201', message:
4✔
93
          HttpStatusHelper::ERROR_CODE['message']['201'] }, status: :created
94
      end
95
    end
96

97
    # create a new grade entry form
98
    # params:
99
    #   :short_identifier, :description, :date, :is_hidden
100
    #   grade_entry_items:
101
    #     :id, :name, :out_of, :bonus
102
    #
103
    # if the grade_entry_items id param is set, an existing item will be
104
    # updated, otherwise a new grade_entry_item will be created
105
    def update
1✔
106
      # check if there is an existing assignment
107
      form = record
2✔
108
      if form.nil?
2✔
109
        render 'shared/http_status', locals: { code: '404', message:
×
110
          'Grade Entry Form not found' }, status: :not_found
111
        return
×
112
      end
113

114
      ApplicationRecord.transaction do
2✔
115
        update_params = params.permit(*DEFAULT_FIELDS)
2✔
116
        unless form.update(update_params)
2✔
117
          render 'shared/http_status', locals: { code: '500', message:
1✔
118
            form.errors.full_messages.first }, status: :internal_server_error
119
          raise ActiveRecord::Rollback
1✔
120
        end
121

122
        position = form.grade_entry_items.count
1✔
123
        params[:grade_entry_items]&.each do |column_params|
1✔
124
          if column_params[:id].nil?
×
125
            column_params = column_params.permit(:name, :out_of, :bonus).to_h.symbolize_keys
×
126
            grade_item = form.grade_entry_items.build(**column_params, position: position += 1)
×
127
            unless grade_item.save
×
128
              render 'shared/http_status', locals: { code: '500', message:
×
129
                grade_item.errors.full_messages.first }, status: :internal_server_error
130
              raise ActiveRecord::Rollback
×
131
            end
132
          else
133
            column_params = column_params.permit(:id, :name, :out_of, :bonus).to_h.symbolize_keys
×
134
            grade_item = form.grade_entry_items.where(id: column_params[:id]).first
×
135
            if grade_item.nil?
×
136
              render 'shared/http_status', locals: { code: '404', message:
×
137
                "Grade Entry Item with id=#{column_params[:id]} not found" }, status: :not_found
138
              raise ActiveRecord::Rollback
×
139
            end
140
            unless grade_item.update(column_params)
×
141
              render 'shared/http_status', locals: { code: '500', message:
×
142
                grade_item.errors.full_messages.first }, status: :internal_server_error
143
              raise ActiveRecord::Rollback
×
144
            end
145
          end
146
        end
147
        render 'shared/http_status', locals: { code: '200', message:
1✔
148
          HttpStatusHelper::ERROR_CODE['message']['200'] }, status: :ok
149
      end
150
    end
151

152
    def update_grades
1✔
153
      if has_missing_params?([:user_name, :grade_entry_items])
3✔
154
        # incomplete/invalid HTTP params
155
        render 'shared/http_status', locals: { code: '422', message:
×
156
          HttpStatusHelper::ERROR_CODE['message']['422'] }, status: :unprocessable_content
157
        return
×
158
      end
159

160
      grade_entry_form = record
3✔
161

162
      grade_entry_student = grade_entry_form.grade_entry_students
3✔
163
                                            .joins(:user)
164
                                            .where('users.user_name': params[:user_name])
165
                                            .first
166

167
      if grade_entry_student.nil?
3✔
168
        # There is no student with that user_name
169
        render 'shared/http_status', locals: { code: '422', message:
×
170
          'There is no student with that user_name' }, status: :unprocessable_content
171
        return
×
172
      end
173

174
      Grade.transaction do
3✔
175
        params[:grade_entry_items].each do |item, score|
3✔
176
          grade_entry_item = GradeEntryItem.find_by(name: item, assessment_id: params[:id])
3✔
177

178
          if grade_entry_item.nil?
3✔
179
            # There is no such grade entry item
180
            render 'shared/http_status', locals: { code: '422', message:
×
181
              "There is no grade entry item named #{item}" }, status: :unprocessable_content
182
            raise ActiveRecord::Rollback
×
183
          end
184
          grade = grade_entry_student.grades.find_or_create_by(grade_entry_item_id: grade_entry_item.id)
3✔
185
          grade.update(grade: score)
3✔
186
        end
187
        if grade_entry_student.save
3✔
188
          render 'shared/http_status', locals: { code: '200', message:
3✔
189
            HttpStatusHelper::ERROR_CODE['message']['200'] }, status: :ok
190
        else
191
          # Some error occurred (including invalid mark)
192
          render 'shared/http_status', locals: { code: '500', message:
×
193
            grade_entry_student.errors.full_messages.first }, status: :internal_server_error
194
          raise ActiveRecord::Rollback
×
195
        end
196
      end
197
    end
198

199
    def destroy
1✔
200
      # check if the grade entry form exists
201
      grade_entry_form = record
2✔
202
      if grade_entry_form.nil?
2✔
203
        render 'shared/http_status', locals: { code: '404', message:
×
204
          'Grade Entry Form not found' }, status: :not_found
205
        return
×
206
      end
207
      # delete the grade entry form
208
      begin
209
        grade_entry_form.destroy!
2✔
210
        render 'shared/http_status',
1✔
211
               locals: { code: '200',
212
                         message: 'Grade Entry Form successfully deleted' }, status: :ok
213
      rescue ActiveRecord::RecordNotDestroyed
214
        render 'shared/http_status',
1✔
215
               locals: { code: :conflict,
216
                         message: 'Grade Entry Form contains non-nil grades' }, status: :conflict
217
      end
218
    end
219
  end
220
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc