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