Rcov C0 Coverage Information - RCov

app/models/rubric_criterion.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
app/models/rubric_criterion.rb 318 209
98.11%
97.13%

Key

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.

Coverage Details

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

Generated on Sun Feb 05 00:08:07 -0500 2012 with rcov 0.9.10