| Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
|---|---|---|---|---|
| app/models/grade_entry_form.rb | 281 | 192 | 63.35%
|
57.29%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 require 'fastercsv' |
2 |
3 # GradeEntryForm can represent a test, lab, exam, etc. |
4 # A grade entry form has many columns which represent the questions and their total |
5 # marks (i.e. GradeEntryItems) and many rows which represent students and their |
6 # marks on each question (i.e. GradeEntryStudents). |
7 class GradeEntryForm < ActiveRecord::Base |
8 has_many :grade_entry_items, :dependent => :destroy |
9 has_many :grade_entry_students, :dependent => :destroy |
10 has_many :grades, :through => :grade_entry_items |
11 |
12 validates_presence_of :short_identifier |
13 validates_uniqueness_of :short_identifier, :case_sensitive => true |
14 |
15 accepts_nested_attributes_for :grade_entry_items, :allow_destroy => true |
16 |
17 BLANK_MARK = "" |
18 |
19 def validate |
20 |
21 # Check that the date is valid - the date is allowed to be in the past |
22 if Time.zone.parse(date.to_s).nil? |
23 errors.add :date, I18n.t('grade_entry_forms.invalid_date') |
24 end |
25 end |
26 |
27 # The total number of marks for this grade entry form |
28 def out_of_total |
29 return grade_entry_items.sum('out_of').to_i |
30 end |
31 |
32 # Determine the total mark for a particular student |
33 def calculate_total_mark(student_id) |
34 # Differentiate between a blank total mark and a total mark of 0 |
35 total = BLANK_MARK |
36 |
37 grade_entry_student = self.grade_entry_students.find_by_user_id(student_id) |
38 if !grade_entry_student.nil? |
39 total = grade_entry_student.grades.sum('grade') |
40 end |
41 |
42 if ((total == 0) && self.all_blank_grades?(grade_entry_student)) |
43 total = BLANK_MARK |
44 end |
45 return total |
46 end |
47 |
48 # Determine the total mark for a particular student, as a percentage |
49 def calculate_total_percent(student_id) |
50 total = self.calculate_total_mark(student_id) |
51 percent = BLANK_MARK |
52 |
53 if total != BLANK_MARK |
54 percent = (total / self.out_of_total) * 100 |
55 end |
56 |
57 return percent |
58 end |
59 |
60 # Determine the average of all of the students' marks that have been |
61 # released so far (return a percentage). |
62 def calculate_released_average() |
63 totalMarks = 0 |
64 numReleased = 0 |
65 |
66 grade_entry_students = self.grade_entry_students.find(:all, :conditions => { :released_to_student => true }) |
67 grade_entry_students.each do |grade_entry_student| |
68 totalMark = self.calculate_total_mark(grade_entry_student.user_id) |
69 if totalMark != BLANK_MARK |
70 totalMarks += totalMark |
71 numReleased += 1 |
72 end |
73 end |
74 |
75 # Watch out for division by 0 |
76 if (numReleased == 0) |
77 return 0 |
78 end |
79 |
80 return ((totalMarks / numReleased) / self.out_of_total) * 100 |
81 end |
82 |
83 # Return whether or not the given student's grades are all blank |
84 # (Needed because ActiveRecord's "sum" method returns 0 even if |
85 # all the grade.grade values are nil and we need to distinguish |
86 # between a total mark of 0 and a blank mark.) |
87 def all_blank_grades?(grade_entry_student) |
88 grades = grade_entry_student.grades |
89 grades_without_nils = grades.select do |grade| |
90 !grade.grade.nil? |
91 end |
92 return grades_without_nils.blank? |
93 end |
94 |
95 # Given two last names, construct an alphabetical category for pagination. |
96 # eg. If the input is "Albert" and "Auric", return "Al" and "Au". |
97 def construct_alpha_category(last_name1, last_name2, alpha_categories, i) |
98 sameSoFar = true |
99 index = 0 |
100 length_of_shorter_name = [last_name1.length, last_name2.length].min |
101 |
102 # Attempt to find the first character that differs |
103 while sameSoFar && (index < length_of_shorter_name) |
104 char1 = last_name1[index].chr |
105 char2 = last_name2[index].chr |
106 |
107 sameSoFar = (char1 == char2) |
108 index += 1 |
109 end |
110 |
111 # Form the category name |
112 if sameSoFar and (index < last_name1.length) |
113 # There is at least one character remaining in the first name |
114 alpha_categories[i] << last_name1[0,index+1] |
115 alpha_categories[i+1] << last_name2[0, index] |
116 elsif sameSoFar and (index < last_name2.length) |
117 # There is at least one character remaining in the second name |
118 alpha_categories[i] << last_name1[0,index] |
119 alpha_categories[i+1] << last_name2[0, index+1] |
120 else |
121 alpha_categories[i] << last_name1[0, index] |
122 alpha_categories[i+1] << last_name2[0, index] |
123 end |
124 |
125 return alpha_categories |
126 end |
127 |
128 # An algorithm for determining the category names for alphabetical pagination |
129 def alpha_paginate(all_grade_entry_students, per_page, total_pages) |
130 alpha_categories = Array.new(2 * total_pages){[]} |
131 alpha_pagination = [] |
132 |
133 if total_pages == 0 |
134 return alpha_pagination |
135 end |
136 |
137 i = 0 |
138 (1..(total_pages - 1)).each do |page| |
139 grade_entry_students1 = all_grade_entry_students.paginate(:per_page => per_page, :page => page) |
140 grade_entry_students2 = all_grade_entry_students.paginate(:per_page => per_page, :page => page+1) |
141 |
142 # To figure out the category names, we need to keep track of the first and last students |
143 # on a particular page and the first student on the next page. For example, if these |
144 # names are "Alwyn, Anderson, and Antheil", the category for this page would be: |
145 # "Al-And". |
146 first_student = grade_entry_students1.first.last_name |
147 last_student = grade_entry_students1.last.last_name |
148 next_student = grade_entry_students2.first.last_name |
149 |
150 # Update the possible categories |
151 alpha_categories = self.construct_alpha_category(first_student, last_student, |
152 alpha_categories, i) |
153 alpha_categories = self.construct_alpha_category(last_student, next_student, |
154 alpha_categories, i+1) |
155 |
156 i += 2 |
157 end |
158 |
159 # Handle the last page |
160 page = total_pages |
161 grade_entry_students = all_grade_entry_students.paginate(:per_page => per_page, :page => page) |
162 first_student = grade_entry_students.first.last_name |
163 last_student = grade_entry_students.last.last_name |
164 |
165 alpha_categories = self.construct_alpha_category(first_student, last_student, alpha_categories, i) |
166 |
167 # We can now form the category names |
168 j=0 |
169 (1..total_pages).each do |i| |
170 alpha_pagination << (alpha_categories[j].max + "-" + alpha_categories[j+1].max) |
171 j += 2 |
172 end |
173 |
174 return alpha_pagination |
175 end |
176 |
177 # Get a CSV report of the grades for this grade entry form |
178 def get_csv_grades_report |
179 students = Student.all(:conditions => {:hidden => false}, :order => "user_name") |
180 csv_string = FasterCSV.generate do |csv| |
181 |
182 # The first row in the CSV file will contain the question names |
183 final_result = [] |
184 final_result.push('') |
185 grade_entry_items.each do |grade_entry_item| |
186 final_result.push(grade_entry_item.name) |
187 end |
188 csv << final_result |
189 |
190 # The second row in the CSV file will contain the question totals |
191 final_result = [] |
192 final_result.push('') |
193 grade_entry_items.each do |grade_entry_item| |
194 final_result.push(grade_entry_item.out_of) |
195 end |
196 csv << final_result |
197 |
198 # The rest of the rows in the CSV file will contain the students' grades |
199 students.each do |student| |
200 final_result = [] |
201 final_result.push(student.user_name) |
202 grade_entry_student = self.grade_entry_students.find_by_user_id(student.id) |
203 |
204 # Check whether or not we have grades recorded for this student |
205 if grade_entry_student.nil? |
206 self.grade_entry_items.each do |grade_entry_item| |
207 # Blank marks for each question |
208 final_result.push(BLANK_MARK) |
209 end |
210 # Blank total percent |
211 final_result.push(BLANK_MARK) |
212 else |
213 self.grade_entry_items.each do |grade_entry_item| |
214 grade = grade_entry_student.grades.find_by_grade_entry_item_id(grade_entry_item.id) |
215 if grade.nil? |
216 final_result.push(BLANK_MARK) |
217 else |
218 final_result.push(grade.grade || BLANK_MARK) |
219 end |
220 end |
221 total_percent = self.calculate_total_percent(student.id) |
222 final_result.push(total_percent) |
223 end |
224 csv << final_result |
225 end |
226 end |
227 return csv_string |
228 end |
229 |
230 # Parse a grade entry form CSV file. |
231 # grades_file is the CSV file to be parsed |
232 # grade_entry_form is the grade entry form that is being updated |
233 # invalid_lines will store all problematic lines from the CSV file |
234 def self.parse_csv(grades_file, grade_entry_form, invalid_lines) |
235 num_updates = 0 |
236 num_lines_read = 0 |
237 names = [] |
238 totals = [] |
239 |
240 # Parse the question names |
241 FasterCSV.parse(grades_file.readline) do |row| |
242 if !FasterCSV.generate_line(row).strip.empty? |
243 names = row |
244 num_lines_read += 1 |
245 end |
246 end |
247 |
248 # Parse the question totals |
249 FasterCSV.parse(grades_file.readline) do |row| |
250 if !FasterCSV.generate_line(row).strip.empty? |
251 totals = row |
252 num_lines_read += 1 |
253 end |
254 end |
255 |
256 # Create/update the grade entry items |
257 begin |
258 GradeEntryItem.create_or_update_from_csv_rows(names, totals, grade_entry_form) |
259 num_updates += 1 |
260 rescue RuntimeError => e |
261 invalid_lines << names.join(',') |
262 invalid_lines << totals.join(',') + ": " + e.message unless invalid_lines.nil? |
263 end |
264 |
265 # Parse the grades |
266 FasterCSV.parse(grades_file.read) do |row| |
267 next if FasterCSV.generate_line(row).strip.empty? |
268 begin |
269 if num_lines_read > 1 |
270 GradeEntryStudent.create_or_update_from_csv_row(row, grade_entry_form) |
271 num_updates += 1 |
272 end |
273 num_lines_read += 1 |
274 rescue RuntimeError => e |
275 invalid_lines << row.join(',') + ": " + e.message unless invalid_lines.nil? |
276 end |
277 end |
278 return num_updates |
279 end |
280 |
281 end |
Generated on Wed Sep 08 00:10:30 -0400 2010 with rcov 0.9.8