Skip to content

Commit ebf8b41

Browse files
author
Strand McCutchen
committed
GradeCalculator#compute_scores returns an array of hashes
refs CNVS-26685 While investigating CNVS-26685, I discovered that GradeCalculator#compute_scores returns an array of arrays with arrays in them with values. This nested structure hid the implicit meaning of the data, which maps to the current, final, current group, and final group for the computed scores. This more human-readable data structure improves the ability of developers to reason about the returned values. Test plan: - Run regression tests on the user, course, and enrollment APIs. Change-Id: I58c8572f94863e47be5d67e6d50fe795d42f1856 Reviewed-on: https://gerrit.instructure.com/74093 Reviewed-by: Cameron Matheson <cameron@instructure.com> Tested-by: Jenkins QA-Review: KC Naegle <knaegle@instructure.com> Product-Review: Keith T. Garner <kgarner@instructure.com>
1 parent 5846c94 commit ebf8b41

6 files changed

Lines changed: 112 additions & 70 deletions

File tree

app/controllers/users_controller.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def grades_for_student
207207
}
208208
}
209209
calculator = grade_calculator([enrollment.user_id], course, grading_periods)
210-
totals = calculator.compute_scores.first.first.first
210+
totals = calculator.compute_scores.first[:current]
211211
totals[:hide_final_grades] = course.hide_final_grades?
212212
render json: totals
213213
end
@@ -1961,9 +1961,9 @@ def grades_from_grade_calculator(user_ids, course, grading_periods)
19611961
calculator = grade_calculator(user_ids, course, grading_periods)
19621962
grades = {}
19631963
calculator.compute_scores.each_with_index do |score, index|
1964-
computed_score = score.first.first[:grade]
1965-
user_id = user_ids[index]
1966-
grades[user_id] = computed_score
1964+
computed_score = score[:current][:grade]
1965+
user_id = user_ids[index]
1966+
grades[user_id] = computed_score
19671967
end
19681968
grades
19691969
end

lib/api/v1/course_json.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ def nil_grading_period_scores(student_enrollments, mgp_enabled, totals_for_all_g
183183

184184
def mgp_scores_from_calculator(grade_calculator)
185185
grade_calculator.compute_scores.map do |scores|
186-
current_score = scores.first.first[:grade]
187-
final_score = scores.second.first[:grade]
186+
current_score = scores[:current][:grade]
187+
final_score = scores[:final][:grade]
188188
{
189189
current_period_computed_current_score: current_score,
190190
current_period_computed_final_score: final_score,

lib/api/v1/user.rb

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -214,31 +214,7 @@ def enrollment_json(enrollment, user, session, includes = [], opts = {})
214214
json[:sis_import_id] = enrollment.sis_batch_id
215215
end
216216
if enrollment.student?
217-
json[:grades] = {
218-
:html_url => course_student_grades_url(enrollment.course_id, enrollment.user_id),
219-
}
220-
221-
if has_grade_permissions?(user, enrollment)
222-
if opts[:grading_period]
223-
student_id = user.id
224-
if enrollment.is_a? StudentEnrollment
225-
student_id = enrollment.student.id
226-
end
227-
228-
course = enrollment.course
229-
gc = GradeCalculator.new(student_id, course,
230-
grading_period: opts[:grading_period])
231-
((current, _), (final, _)) = gc.compute_scores.first
232-
json[:grades][:current_score] = current[:grade]
233-
json[:grades][:current_grade] = course.score_to_grade(current[:grade])
234-
json[:grades][:final_score] = final[:grade]
235-
json[:grades][:final_grade] = course.score_to_grade(final[:grade])
236-
else
237-
%w{current_score final_score current_grade final_grade}.each do |method|
238-
json[:grades][method.to_sym] = enrollment.send("computed_#{method}")
239-
end
240-
end
241-
end
217+
json[:grades] = grades_hash(enrollment, user, opts[:grading_period])
242218
end
243219
if @domain_root_account.grants_any_right?(@current_user, :read_sis, :manage_sis)
244220
json[:sis_source_id] = enrollment.sis_source_id
@@ -267,8 +243,39 @@ def enrollment_json(enrollment, user, session, includes = [], opts = {})
267243
end
268244
end
269245

270-
protected
271-
def has_grade_permissions?(user, enrollment)
246+
private
247+
def grades_hash(enrollment, user, grading_period)
248+
grades = {
249+
html_url: course_student_grades_url(enrollment.course_id, enrollment.user_id)
250+
}
251+
252+
if grade_permissions?(user, enrollment)
253+
if grading_period
254+
student_id = enrollment.is_a?(StudentEnrollment) ? enrollment.student.id : user.id
255+
calculator = GradeCalculator.new(
256+
student_id,
257+
enrollment.course,
258+
grading_period: grading_period
259+
)
260+
261+
computed = calculator.compute_scores.first
262+
current, final = computed[:current], computed[:final]
263+
264+
grades[:current_score] = current[:grade]
265+
grades[:current_grade] = enrollment.course.score_to_grade(current[:grade])
266+
grades[:final_score] = final[:grade]
267+
grades[:final_grade] = enrollment.course.score_to_grade(final[:grade])
268+
else
269+
grades[:current_score] = enrollment.computed_current_score
270+
grades[:current_grade] = enrollment.computed_current_grade
271+
grades[:final_score] = enrollment.computed_final_score
272+
grades[:final_grade] = enrollment.computed_final_grade
273+
end
274+
end
275+
grades
276+
end
277+
278+
def grade_permissions?(user, enrollment)
272279
course = enrollment.course
273280

274281
(user.id == enrollment.user_id && !course.hide_final_grades?) ||

lib/grade_calculator.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,27 @@ def compute_scores
5757
select("submissions.id, user_id, assignment_id, score, excused")
5858
submissions_by_user = @submissions.group_by(&:user_id)
5959

60-
scores = []
60+
result = []
6161
@user_ids.each_slice(100) do |batched_ids|
6262
load_assignment_visibilities_for_users(batched_ids)
6363
batched_ids.each do |user_id|
6464
user_submissions = submissions_by_user[user_id] || []
6565
user_submissions.select!{|s| assignment_ids_visible_to_user(user_id).include?(s.assignment_id)}
6666
current, current_groups = calculate_current_score(user_id, user_submissions)
6767
final, final_groups = calculate_final_score(user_id, user_submissions)
68-
scores << [[current, current_groups], [final, final_groups]]
68+
69+
scores = {
70+
current: current,
71+
current_groups: current_groups,
72+
final: final,
73+
final_groups: final_groups
74+
}
75+
76+
result << scores
6977
end
7078
clear_assignment_visibilities_cache
7179
end
72-
scores
80+
result
7381
end
7482

7583
def compute_and_save_scores

lib/gradebook_exporter.rb

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,21 +156,10 @@ def to_csv
156156
row << student_sections
157157
row.concat(student_submissions)
158158

159-
160159
if should_show_totals
161-
(current_info, current_group_info),
162-
(final_info, final_group_info) = grades.shift
163-
groups.each do |g|
164-
row << current_group_info[g.id][:score] << final_group_info[g.id][:score] if include_points
165-
row << current_group_info[g.id][:grade] << final_group_info[g.id][:grade]
166-
end
167-
row << current_info[:total] << final_info[:total] if include_points
168-
row << current_info[:grade] << final_info[:grade]
169-
if @course.grading_standard_enabled?
170-
row << @course.score_to_grade(current_info[:grade])
171-
row << @course.score_to_grade(final_info[:grade])
172-
end
160+
row += show_totals(grades.shift, groups, include_points)
173161
end
162+
174163
csv << row
175164
end
176165
end
@@ -189,6 +178,34 @@ def enrollments_for_csv(scope, options={})
189178
enrollments.partition { |e| e.type != "StudentViewEnrollment" }.flatten
190179
end
191180

181+
def show_totals(grade, groups, include_points)
182+
result = []
183+
184+
groups.each do |group|
185+
if include_points
186+
result << grade[:current_groups][group.id][:score]
187+
result << grade[:final_groups][group.id][:score]
188+
end
189+
190+
result << grade[:current_groups][group.id][:grade]
191+
result << grade[:final_groups][group.id][:grade]
192+
end
193+
194+
if include_points
195+
result << grade[:current][:total]
196+
result << grade[:final][:total]
197+
end
198+
199+
result << grade[:current][:grade]
200+
result << grade[:final][:grade]
201+
202+
if @course.grading_standard_enabled?
203+
result << @course.score_to_grade(grade[:current][:grade])
204+
result << @course.score_to_grade(grade[:final][:grade])
205+
end
206+
result
207+
end
208+
192209
def show_totals?
193210
return true if !@course.feature_enabled?(:multiple_grading_periods)
194211
return true if @options[:grading_period_id].try(:to_i) != 0

spec/lib/grade_calculator_spec.rb

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -133,17 +133,18 @@ def two_groups_two_assignments(g1_weight, a1_possible, g2_weight, a2_possible)
133133
calc = GradeCalculator.new [@user.id],
134134
@course.id,
135135
:ignore_muted => false
136-
(current, _), (final, _) = calc.compute_scores.first
137-
expect(current[:grade]).to eq 50
138-
expect(final[:grade]).to eq 25
136+
scores = calc.compute_scores.first
137+
expect(scores[:current][:grade]).to eq 50
138+
expect(scores[:final][:grade]).to eq 25
139139
end
140140

141141
it "should be impossible to save grades that considered muted assignments" do
142142
@course.update_attribute(:group_weighting_scheme, "percent")
143143
calc = GradeCalculator.new [@user.id],
144144
@course.id,
145145
:ignore_muted => false
146-
expect { calc.save_scores }.to raise_error
146+
# save_scores is a private method
147+
expect { calc.send(:save_scores) }.to raise_error("Can't save scores when ignore_muted is false")
147148
end
148149
end
149150
end
@@ -153,10 +154,14 @@ def two_groups_two_assignments(g1_weight, a1_possible, g2_weight, a2_possible)
153154
@assignment.grade_student @user, grade: 5
154155
@assignment2.grade_student @user, grade: 10
155156
calc = GradeCalculator.new [@user.id], @course.id
156-
(_, current_group_info), (_, final_group_info) = calc.compute_scores.first
157-
expect(current_group_info).to eq final_group_info
158-
expect(current_group_info[@group.id][:grade]).to eq 50
159-
expect(current_group_info[@group2.id][:grade]).to eq 100
157+
158+
computed_scores = calc.compute_scores.first
159+
current_groups = computed_scores[:current_groups]
160+
final_groups = computed_scores[:final_groups]
161+
162+
expect(current_groups).to eq final_groups
163+
expect(current_groups[@group.id][:grade]).to eq 50
164+
expect(current_groups[@group2.id][:grade]).to eq 100
160165
end
161166

162167
it "should compute a weighted grade when specified" do
@@ -389,16 +394,19 @@ def nil_graded_assignment
389394

390395
calc = GradeCalculator.new([@student2.id, @student1.id], @course)
391396
grades = calc.compute_scores
392-
expect(grades.shift.map { |g,_| g[:grade] }).to eq [100, 100]
393-
expect(grades.shift.map { |g,_| g[:grade] }).to eq [50, 50]
397+
398+
expect(grades.first[:current][:grade]).to eq 100
399+
expect(grades.first[:final][:grade]).to eq 100
400+
expect(grades.last[:current][:grade]).to eq 50
401+
expect(grades.last[:final][:grade]).to eq 50
394402
end
395403

396404
it "returns point information for unweighted courses" do
397405
course_with_student
398406
a = @course.assignments.create! :points_possible => 50
399407
a.grade_student @student, :grade => 25
400408
calc = GradeCalculator.new([@student.id], @course)
401-
((grade_info, _), _) = calc.compute_scores.first
409+
grade_info = calc.compute_scores.first[:current]
402410
expect(grade_info).to eq({:grade => 50, :total => 25, :possible => 50})
403411
end
404412

@@ -600,11 +608,11 @@ def check_grades(current, final)
600608

601609
it "can compute grades for a grading period" do
602610
gc = GradeCalculator.new([@student.id], @course, grading_period: @gp1)
603-
(current, _), _ = gc.compute_scores.first
611+
current = gc.compute_scores.first[:current]
604612
expect(current[:grade]).to eql 25.0
605613

606614
gc = GradeCalculator.new([@student.id], @course, grading_period: @gp2)
607-
(current, _), _ = gc.compute_scores.first
615+
current = gc.compute_scores.first[:current]
608616
expect(current[:grade]).to eql 75.0
609617
end
610618
end
@@ -633,10 +641,8 @@ def set_up_course_for_differentiated_assignments
633641
create_section_override_for_assignment(@overridden_middle, course_section: @section)
634642
end
635643

636-
def get_user_points_and_course_total(user,course)
637-
calc = GradeCalculator.new [user.id], course.id
638-
final_grade_info = calc.compute_scores.first.last.first
639-
[final_grade_info[:total], final_grade_info[:possible]]
644+
def final_grade_info(user, course)
645+
GradeCalculator.new([user.id], course.id).compute_scores.first[:final]
640646
end
641647

642648
context "DA" do
@@ -645,22 +651,26 @@ def get_user_points_and_course_total(user,course)
645651
end
646652
it "should calculate scores based on visible assignments only" do
647653
# 5 + 15 + 10 + 20 + 10
648-
expect(get_user_points_and_course_total(@user,@course)).to eq [60,100]
654+
expect(final_grade_info(@user, @course)[:total]).to eq 60
655+
expect(final_grade_info(@user, @course)[:possible]).to eq 100
649656
end
650657
it "should drop the lowest visible when that rule is in place" do
651658
@group.update_attribute(:rules, 'drop_lowest:1')
652659
# 5 + 15 + 10 + 20 + 10 - 5
653-
expect(get_user_points_and_course_total(@user,@course)).to eq [55,80]
660+
expect(final_grade_info(@user, @course)[:total]).to eq 55
661+
expect(final_grade_info(@user, @course)[:possible]).to eq 80
654662
end
655663
it "should drop the highest visible when that rule is in place" do
656664
@group.update_attribute(:rules, 'drop_highest:1')
657665
# 5 + 15 + 10 + 20 + 10 - 20
658-
expect(get_user_points_and_course_total(@user,@course)).to eq [40,80]
666+
expect(final_grade_info(@user, @course)[:total]).to eq 40
667+
expect(final_grade_info(@user, @course)[:possible]).to eq 80
659668
end
660669
it "shouldnt count an invisible assingment with never drop on" do
661670
@group.update_attribute(:rules, "drop_lowest:2\nnever_drop:#{@overridden_lowest.id}")
662671
# 5 + 15 + 10 + 20 + 10 - 10 - 10
663-
expect(get_user_points_and_course_total(@user,@course)).to eq [40,60]
672+
expect(final_grade_info(@user, @course)[:total]).to eq 40
673+
expect(final_grade_info(@user, @course)[:possible]).to eq 60
664674
end
665675
end
666676
end

0 commit comments

Comments
 (0)