| Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
|---|---|---|---|---|
| app/models/rubric_criterion.rb | 250 | 170 | 64.00%
|
49.41%
|
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 require 'csv' |
3 |
4 class RubricCriterion < ActiveRecord::Base |
5 before_save :round_weight |
6 set_table_name "rubric_criteria" # set table name correctly |
7 belongs_to :assignment, :counter_cache => true |
8 has_many :marks, :as => :markable, :dependent => :destroy |
9 has_many :criterion_ta_associations, :as => :criterion, :dependent => :destroy |
10 has_many :tas, :through => :criterion_ta_associations |
11 validates_associated :assignment, :on => :create |
12 validates_uniqueness_of :rubric_criterion_name, :scope => :assignment_id |
13 validates_presence_of :rubric_criterion_name, :weight, :assignment_id, |
14 :assigned_groups_count |
15 validates_numericality_of :assignment_id, :only_integer => true, :greater_than => 0 |
16 validates_numericality_of :weight, :assigned_groups_count |
17 validate_on_update :validate_total_weight |
18 |
19 before_validation :update_assigned_groups_count |
20 |
21 def update_assigned_groups_count |
22 result = [] |
23 criterion_ta_associations.each do |cta| |
24 result = result.concat(cta.ta.get_groupings_by_assignment(assignment)) |
25 end |
26 self.assigned_groups_count = result.uniq.length |
27 end |
28 |
29 def validate_total_weight |
30 errors.add(:assignment, I18n.t("rubric_criteria.error_total")) if self.assignment.total_mark + (4 * (self.weight - self.weight_was)) <= 0 |
31 end |
32 |
33 # Just a small effort here to remove magic numbers... |
34 RUBRIC_LEVELS = 5 |
35 DEFAULT_WEIGHT = 1.0 |
36 DEFAULT_LEVELS = [ |
37 {'name'=> I18n.t("rubric_criteria.defaults.level_0"), 'description'=> I18n.t("rubric_criteria.defaults.description_0")}, |
38 {'name'=> I18n.t("rubric_criteria.defaults.level_1"), 'description'=> I18n.t("rubric_criteria.defaults.description_1")}, |
39 {'name'=> I18n.t("rubric_criteria.defaults.level_2"), 'description'=> I18n.t("rubric_criteria.defaults.description_2")}, |
40 {'name'=> I18n.t("rubric_criteria.defaults.level_3"), 'description'=> I18n.t("rubric_criteria.defaults.description_3")}, |
41 {'name'=> I18n.t("rubric_criteria.defaults.level_4"), 'description'=> I18n.t("rubric_criteria.defaults.description_4")} |
42 ] |
43 |
44 def mark_for(result_id) |
45 return marks.find_by_result_id(result_id) |
46 end |
47 |
48 def set_default_levels |
49 DEFAULT_LEVELS.each_with_index do |level, index| |
50 self['level_' + index.to_s + '_name'] = level['name'] |
51 self['level_' + index.to_s + '_description'] = level['description'] |
52 end |
53 end |
54 |
55 # Set all the level names at once and saves the object. |
56 # |
57 # ===Params: |
58 # |
59 # levels:: An array containing every level name. A rubric criterion contains |
60 # RUBRIC_LEVELS levels. If the array is smaller, only the first levels |
61 # are set. If the array is bigger, higher indexes are ignored. |
62 # |
63 # ===Returns: |
64 # |
65 # Wether the save operation was successful or not. |
66 def set_level_names(levels) |
67 levels.each_with_index do |level, index| |
68 self['level_' + index.to_s + '_name'] = level |
69 end |
70 save |
71 end |
72 |
73 # Create a CSV string from all the rubric criteria related to an assignment. |
74 # |
75 # ===Returns: |
76 # |
77 # A string. See create_or_update_from_csv_row for format reference. |
78 def self.create_csv(assignment) |
79 csv_string = FasterCSV.generate do |csv| |
80 assignment.rubric_criteria.each do |criterion| |
81 criterion_array = [criterion.rubric_criterion_name,criterion.weight] |
82 (0..RUBRIC_LEVELS - 1).each do |i| |
83 criterion_array.push(criterion['level_' + i.to_s + '_name']) |
84 end |
85 (0..RUBRIC_LEVELS - 1).each do |i| |
86 criterion_array.push(criterion['level_' + i.to_s + '_description']) |
87 end |
88 csv << criterion_array |
89 end |
90 end |
91 return csv_string |
92 end |
93 |
94 # Instantiate a RubricCriterion from a CSV row and attach it to the supplied |
95 # assignment. |
96 # |
97 # ===Params: |
98 # |
99 # row:: An array representing one CSV file row. Should be in the following |
100 # format: [name, weight, _names_, _descriptions_] where the _names_ part |
101 # must contain RUBRIC_LEVELS elements representing the name of each |
102 # level and the _descriptions_ part (optional) can contain up to |
103 # RUBRIC_LEVELS description (one for each level). |
104 # assignment:: The assignment to which the newly created criterion should belong. |
105 # |
106 # ===Raises: |
107 # |
108 # RuntimeError If the row does not contains enough information, if the weight value |
109 # is zero (or doesn't evaluate to a float) |
110 def self.create_or_update_from_csv_row(row, assignment) |
111 if row.length < RUBRIC_LEVELS + 2 |
112 raise I18n.t('criteria_csv_error.incomplete_row') |
113 end |
114 working_row = row.clone |
115 rubric_criterion_name = working_row.shift |
116 # If a RubricCriterion of the same name exits, load it up. Otherwise, |
117 # create a new one. |
118 criterion = assignment.rubric_criteria.find_or_create_by_rubric_criterion_name(rubric_criterion_name) |
119 #Check that the weight is not a string. |
120 begin |
121 criterion.weight = Float(working_row.shift) |
122 rescue ArgumentError => e |
123 raise I18n.t('criteria_csv_error.weight_not_number') |
124 end |
125 # Only set the position if this is a new record. |
126 if criterion.new_record? |
127 criterion.position = assignment.next_criterion_position |
128 end |
129 # next comes the level names. |
130 (0..RUBRIC_LEVELS-1).each do |i| |
131 criterion['level_' + i.to_s + '_name'] = working_row.shift |
132 end |
133 # the rest of the values are level descriptions. |
134 working_row.each_with_index do |desc, i| |
135 criterion['level_' + i.to_s + '_description'] = desc |
136 end |
137 if !criterion.save |
138 raise RuntimeError.new(criterion.errors) |
139 end |
140 return criterion |
141 end |
142 |
143 # Parse a rubric criteria CSV file. |
144 # |
145 # ===Params: |
146 # |
147 # file:: A file object which will be tried for parsing. |
148 # assignment:: The assignment to which the new criteria should belong to. |
149 # invalid_lines:: An object to recieve all encountered _invalid_ lines. |
150 # Strings representing the faulty line followed by |
151 # a human readable error message are appended to the object |
152 # via the << operator. |
153 # |
154 # *Hint*: An array allows for an easy |
155 # access of single invalid lines. |
156 # ===Returns: |
157 # |
158 # The number of successfully created criteria. |
159 def self.parse_csv(file, assignment, invalid_lines) |
160 nb_updates = 0 |
161 FasterCSV.parse(file.read) do |row| |
162 next if FasterCSV.generate_line(row).strip.empty? |
163 begin |
164 RubricCriterion.create_or_update_from_csv_row(row, assignment) |
165 nb_updates += 1 |
166 rescue RuntimeError => e |
167 invalid_lines << row.join(',') + ": " + e.message unless invalid_lines.nil? |
168 end |
169 end |
170 return nb_updates |
171 end |
172 |
173 def get_weight |
174 return self.weight |
175 end |
176 |
177 def round_weight |
178 factor = 10.0 ** 3 |
179 self.weight = (self.weight * factor).round.to_f / factor |
180 end |
181 |
182 def all_assigned_groups |
183 result = [] |
184 tas.each do |ta| |
185 result = result.concat(ta.get_groupings_by_assignment(assignment)) |
186 end |
187 return result.uniq |
188 end |
189 |
190 def add_tas(ta_array) |
191 ta_array = Array(ta_array) |
192 associations = criterion_ta_associations.all(:conditions => {:ta_id => ta_array}) |
193 ta_array.each do |ta| |
194 # & is the mathematical set intersection operator between two arrays |
195 if (ta.criterion_ta_associations & associations).size < 1 |
196 criterion_ta_associations.create(:ta => ta, :criterion => self, :assignment => self.assignment) |
197 end |
198 end |
199 end |
200 |
201 |
202 def get_name |
203 return rubric_criterion_name |
204 end |
205 |
206 def remove_tas(ta_array) |
207 ta_array = Array(ta_array) |
208 associations_for_criteria = criterion_ta_associations.all(:conditions => {:ta_id => ta_array}) |
209 ta_array.each do |ta| |
210 # & is the mathematical set intersection operator between two arrays |
211 assoc_to_remove = (ta.criterion_ta_associations & associations_for_criteria) |
212 if assoc_to_remove.size > 0 |
213 criterion_ta_associations.delete(assoc_to_remove) |
214 assoc_to_remove.first.destroy |
215 end |
216 end |
217 end |
218 |
219 def get_ta_names |
220 return criterion_ta_associations.collect {|association| association.ta.user_name} |
221 end |
222 |
223 def has_associated_ta?(ta) |
224 if !ta.ta? |
225 return false |
226 end |
227 return !(criterion_ta_associations.find_by_ta_id(ta.id) == nil) |
228 end |
229 |
230 def add_tas_by_user_name_array(ta_user_name_array) |
231 result = ta_user_name_array.map{|ta_user_name| |
232 Ta.find_by_user_name(ta_user_name)}.compact |
233 add_tas(result) |
234 end |
235 |
236 # Returns an array containing the criterion names that didn't exist |
237 def self.assign_tas_by_csv(csv_file_contents, assignment_id) |
238 failures = [] |
239 FasterCSV.parse(csv_file_contents) do |row| |
240 criterion_name = row.shift # Knocks the first item from array |
241 criterion = RubricCriterion.find_by_assignment_id_and_rubric_criterion_name(assignment_id, criterion_name) |
242 if criterion.nil? |
243 failures.push(criterion_name) |
244 else |
245 criterion.add_tas_by_user_name_array(row) # The rest of the array |
246 end |
247 end |
248 return failures |
249 end |
250 end |
Generated on Thu Sep 09 00:10:35 -0400 2010 with rcov 0.9.8