Rcov C0 Coverage Information - RCov

app/models/assignment.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
app/models/assignment.rb 763 569
99.74%
99.65%

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 'csv_invalid_line_error'
2 class Assignment < ActiveRecord::Base
3 
4   MARKING_SCHEME_TYPE = {
5     :flexible => 'flexible',
6     :rubric => 'rubric'
7   }
8 
9   has_many :rubric_criteria,
10            :class_name => "RubricCriterion",
11            :order => :position
12   has_many :flexible_criteria,
13            :class_name => "FlexibleCriterion",
14            :order => :position
15   has_many :assignment_files
16   has_many :test_files
17   has_many :criterion_ta_associations
18   has_one :submission_rule
19   has_one :assignment_stat
20   accepts_nested_attributes_for :submission_rule, :allow_destroy => true
21   accepts_nested_attributes_for :assignment_files, :allow_destroy => true
22   accepts_nested_attributes_for :test_files, :allow_destroy => true
23   accepts_nested_attributes_for :assignment_stat, :allow_destroy => true
24 
25   has_many :annotation_categories
26 
27   has_many :groupings
28   has_many :ta_memberships,
29            :class_name => "TaMembership",
30            :through => :groupings
31   has_many :student_memberships, :through => :groupings
32   has_many :tokens, :through => :groupings
33 
34   has_many :submissions, :through => :groupings
35   has_many :groups, :through => :groupings
36 
37   has_many :notes, :as => :noteable, :dependent => :destroy
38 
39   has_many :section_due_dates
40   accepts_nested_attributes_for :section_due_dates
41 
42   validates_associated :assignment_files
43 
44   validates_presence_of :repository_folder
45   validates_presence_of :short_identifier, :group_min
46   validates_uniqueness_of :short_identifier, :case_sensitive => true
47 
48   validates_numericality_of :group_min,
49                             :only_integer => true,
50                             :greater_than => 0
51   validates_numericality_of :group_max, :only_integer => true
52   validates_numericality_of :tokens_per_day,
53                             :only_integer => true,
54                             :greater_than_or_equal_to => 0
55 
56   validates_associated :submission_rule
57   validates_associated :assignment_stat
58   validates_presence_of :submission_rule
59 
60   validates_presence_of :marking_scheme_type
61 
62   # since allow_web_submits is a boolean, validates_presence_of does not work:
63   # see the Rails API documentation for validates_presence_of (Model
64   # validations)
65   validates_inclusion_of :allow_web_submits, :in => [true, false]
66   validates_inclusion_of :display_grader_names_to_students, :in => [true, false]
67   validates_inclusion_of :enable_test, :in => [true, false]
68   validates_inclusion_of :assign_graders_to_criteria, :in => [true, false]
69 
70   before_save :reset_collection_time
71   validate :minimum_number_of_groups, :check_timezone
72   after_save :update_assigned_tokens
73 
74   # Export a YAML formatted string created from the assignment rubric criteria.
75   def export_rubric_criteria_yml
76     criteria = self.rubric_criteria
77     final = Hash.new
78     criteria.each do |criterion|
79       inner = ActiveSupport::OrderedHash.new
80       inner["weight"] =  criterion["weight"]
81       inner["level_0"] = {
82         "name"=>  criterion["level_0_name"] ,
83         "description"=>  criterion["level_0_description"]
84       }
85       inner["level_1"] = {
86         "name"=>  criterion["level_1_name"] ,
87         "description"=>  criterion["level_1_description"]
88       }
89       inner["level_2"] = {
90         "name"=>  criterion["level_2_name"] ,
91         "description"=>  criterion["level_2_description"]
92       }
93       inner["level_3"] = {
94         "name"=>  criterion["level_3_name"] ,
95         "description"=>  criterion["level_3_description"]
96       }
97       inner["level_4"] = {
98         "name"=>  criterion["level_4_name"] ,
99         "description"=> criterion["level_4_description"]
100       }
101       criteria_yml = {"#{criterion["rubric_criterion_name"]}" => inner}
102       final = final.merge(criteria_yml)
103     end
104     return final.to_yaml
105   end
106 
107   def minimum_number_of_groups
108     if (group_max && group_min) && group_max < group_min
109       errors.add(:group_max, "must be greater than the minimum number of groups")
110       return false
111     end
112   end
113 
114   def check_timezone
115     if Time.zone.parse(due_date.to_s).nil?
116       errors.add :due_date, 'is not a valid date'
117       return false
118     end
119   end
120 
121   # Are we past all the due dates for this assignment?
122   def past_due_date?
123     # If no section due dates
124     if !self.section_due_dates_type && self.section_due_dates.empty?
125       return !due_date.nil? && Time.now > due_date
126     # If section due dates
127     else
128       self.section_due_dates.each do |d|
129         if !d.due_date.nil? && Time.now > d.due_date
130           return true
131         end
132       end
133       return false
134     end
135   end
136 
137   # Are we past the due date for this assignment, for this grouping ?
138   def section_past_due_date?(grouping)
139     if self.section_due_dates_type and !grouping.inviter.section.nil?
140         section_due_date =
141     SectionDueDate.due_date_for(grouping.inviter.section, self)
142         return !section_due_date.nil? && Time.now > section_due_date
143     else
144       self.past_due_date?
145     end
146   end
147 
148   # return the due date for a section
149   def section_due_date(section)
150     if self.section_due_dates_type
151       if !section.nil?
152         return SectionDueDate.due_date_for(section, self)
153       end
154     end
155     return self.due_date
156   end
157 
158   # Calculate the latest due date. Used to calculate the collection time
159   def latest_due_date
160     if !self.section_due_dates_type
161       return self.due_date
162     else
163       due_date = self.due_date
164       self.section_due_dates.each do |d|
165         if !d.due_date.nil? && due_date < d.due_date
166           due_date = d.due_date
167         end
168       end
169       return due_date
170     end
171   end
172 
173   def past_collection_date?
174     return Time.now > submission_rule.calculate_collection_time
175   end
176 
177   def past_remark_due_date?
178     return !remark_due_date.nil? && Time.now > remark_due_date
179   end
180 
181   # Returns a Submission instance for this user depending on whether this
182   # assignment is a group or individual assignment
183   def submission_by(user) #FIXME: needs schema updates
184 
185     # submission owner is either an individual (user) or a group
186     owner = self.group_assignment? ? self.group_by(user.id) : user
187     return nil unless owner
188 
189     # create a new submission for the owner
190     # linked to this assignment, if it doesn't exist yet
191 
192     # submission = owner.submissions.find_or_initialize_by_assignment_id(id)
193     # submission.save if submission.new_record?
194     # return submission
195 
196     assignment_groupings = user.active_groupings.delete_if {|grouping|
197       grouping.assignment.id != self.id
198     }
199 
200     unless assignment_groupings.empty?
201       return assignment_groupings.first.submissions.first
202     else
203       return nil
204     end
205   end
206 
207   # Return true if this is a group assignment; false otherwise
208   def group_assignment?
209     invalid_override || group_min != 1 || group_max > 1
210   end
211 
212   # Returns the group by the user for this assignment. If pending=true,
213   # it will return the group that the user has a pending invitation to.
214   # Returns nil if user does not have a group for this assignment, or if it is
215   # not a group assignment
216   def group_by(uid, pending=false)
217     return nil unless group_assignment?
218 
219     # condition = "memberships.user_id = ?"
220     # condition += " and memberships.status != 'rejected'"
221     # add non-pending status clause to condition
222     # condition += " and memberships.status != 'pending'" unless pending
223     # groupings.find(:first, :include => :memberships, :conditions => [condition, uid]) #FIXME: needs schema update
224 
225     #FIXME: needs to be rewritten using a proper query...
226     return User.find(uid).accepted_grouping_for(self.id)
227   end
228 
229   # Make a list of students without any groupings
230   def no_grouping_students_list
231    @students = Student.all(:order => :last_name, :conditions => {:hidden => false})
232    @students_list = []
233    @students.each do |s|
234      if !s.has_accepted_grouping_for?(self.id)
235        @students_list.push(s)
236       end
237    end
238    return @students_list
239   end
240 
241   def display_for_note
242     return short_identifier
243   end
244 
245   def total_mark
246     total = 0
247     if self.marking_scheme_type == 'rubric'
248       rubric_criteria.each do |criterion|
249         total = total + criterion.weight * 4
250       end
251     else
252       total = flexible_criteria.sum('max')
253     end
254     return total
255   end
256 
257   # calculates the average of released results for this assignment
258   def set_results_average
259     groupings = Grouping.find_all_by_assignment_id(self.id)
260     results_count = 0
261     results_sum = 0
262     groupings.each do |grouping|
263       submission = grouping.current_submission_used
264       if !submission.nil?
265         if submission.has_result? && submission.remark_submitted?
266           result = submission.remark_result
267         elsif submission.has_result?
268           result = submission.result
269         end
270         if result.released_to_students
271           results_sum += result.total_mark
272           results_count += 1
273         end
274       end
275     end
276     if results_count == 0
277       return false # no marks released for this assignment
278     end
279     # Need to avoid divide by zero
280     if results_sum == 0
281       self.results_average = 0
282       return self.save
283     end
284     avg_quantity = results_sum / results_count
285     # compute average in percent
286     self.results_average = (avg_quantity * 100 / self.total_mark)
287     self.save
288   end
289 
290   def total_criteria_weight
291     factor = 10.0 ** 2
292     return (rubric_criteria.sum('weight') * factor).floor / factor
293   end
294 
295   def add_group(new_group_name=nil)
296     if self.group_name_autogenerated
297       group = Group.new
298       group.save(:validate => false)
299       group.group_name = group.get_autogenerated_group_name
300       group.save
301     else
302       return nil if new_group_name.nil?
303       if Group.find(:first, :conditions => {:group_name => new_group_name})
304         group = Group.find(:first, :conditions => {:group_name =>       new_group_name})
305         if !self.groupings.find_by_group_id(group.id).nil?
306           raise "Group #{new_group_name} already exists"
307         end
308       else
309         group = Group.new
310         group.group_name = new_group_name
311         group.save
312       end
313     end
314     grouping = Grouping.new
315     grouping.group = group
316     grouping.assignment = self
317     grouping.save
318     return grouping
319   end
320 
321 
322   # Create all the groupings for an assignment where students don't work
323   # in groups.
324   def create_groupings_when_students_work_alone
325      @students = Student.find(:all)
326      for student in @students do
327        if !student.has_accepted_grouping_for?(self.id)
328         student.create_group_for_working_alone_student(self.id)
329        end
330      end
331   end
332 
333   # Clones the Groupings from the assignment with id assignment_id
334   # into self.  Destroys any previously existing Groupings associated
335   # with this Assignment
336   def clone_groupings_from(assignment_id)
337     original_assignment = Assignment.find(assignment_id)
338     self.transaction do
339       self.group_min = original_assignment.group_min
340       self.group_max = original_assignment.group_max
341       self.student_form_groups = original_assignment.student_form_groups
342       self.group_name_autogenerated = original_assignment.group_name_autogenerated
343       self.group_name_displayed = original_assignment.group_name_displayed
344       self.groupings.destroy_all
345       self.save
346       self.reload
347       original_assignment.groupings.each do |g|
348         unhidden_student_memberships = g.accepted_student_memberships.select do |m|
349           !m.user.hidden
350         end
351         unhidden_ta_memberships = g.ta_memberships.select do |m|
352           !m.user.hidden
353         end
354         #create the memberships for any user that is not hidden
355         if !unhidden_student_memberships.empty?
356           #create the groupings
357           grouping = Grouping.new
358           grouping.group_id = g.group_id
359           grouping.assignment_id = self.id
360           grouping.admin_approved = g.admin_approved
361           raise "Could not save grouping" if !grouping.save
362           all_memberships = unhidden_student_memberships + unhidden_ta_memberships
363           all_memberships.each do |m|
364             membership = Membership.new
365             membership.user_id = m.user_id
366             membership.type = m.type
367             membership.membership_status = m.membership_status
368             raise "Could not save membership" if !(grouping.memberships << membership)
369           end
370           # Ensure all student members have permissions on their group repositories
371           grouping.update_repository_permissions
372         end
373       end
374     end
375   end
376 
377   # Add a group and corresponding grouping as provided in
378   # the passed in Array.
379   # Format: [ groupname, repo_name, member, member, etc ]
380   # Any member names that do not exist in the database will simply be ignored
381   # (This makes it possible to have empty groups created from a bad csv row)
382   def add_csv_group(row)
383     return if row.length == 0
384 
385     # Note: We cannot use find_or_create_by here, because it has its own
386     # save semantics. We need to set and save attributes in a very particular
387     # order, so that everything works the way we want it to.
388     group = Group.find_by_group_name(row[0])
389     if group.nil?
390       group = Group.new
391       group.group_name = row[0]
392     end
393 
394     # Since repo_name of "group" will be set before the first save call, the
395     # set repo_name will be used instead of the autogenerated name. See
396     # set_repo_name and build_repository in the groups model. Also, see
397     # create_group_for_working_alone_student in the students model for
398     # similar semantics.
399     if is_candidate_for_setting_custom_repo_name?(row)
400       # Do this only if user_name exists and is a student.
401       if !Student.find_by_user_name(row[2]).nil?
402         group.repo_name = row[0]
403       else
404         # Student name does not exist, use provided repo_name
405         group.repo_name = row[1].strip # remove whitespace
406       end
407     end
408 
409     # If we are not repository admin, set the repository name as provided
410     # in the csv upload file
411     if !group.repository_admin?
412       group.repo_name = row[1].strip # remove whitespace
413     end
414     # Note: after_create hook build_repository might raise
415     # Repository::RepositoryCollision. If it does, it adds the colliding
416     # repo_name to errors.on_base. This is how we can detect repo
417     # collisions here. Unfortunately, we can't raise an exception
418     # here, because we still want the grouping to be created. This really
419     # shouldn't happen anyway, because the lookup earlier should prevent
420     # repo collisions e.g. when uploading the same CSV file twice.
421     group.save
422     if !group.errors[:base].blank?
423       collision_error = I18n.t("csv.repo_collision_warning",
424                           { :repo_name => group.errors.on_base,
425                             :group_name => row[0] })
426     end
427 
428     # Create a new Grouping for this assignment and the newly
429     # crafted group
430     grouping = Grouping.new(:assignment => self, :group => group)
431     grouping.save
432 
433     # Form groups
434     start_index_group_members = 2 # first field is the group-name, second the repo name, so start at field 3
435     (start_index_group_members..(row.length - 1)).each do |i|
436       student = Student.find_by_user_name(row[i].strip) # remove whitespace
437       if !student.nil?
438         if (grouping.student_membership_number == 0)
439           # Add first valid member as inviter to group.
440           grouping.group_id = group.id
441           grouping.save # grouping has to be saved, before we can add members
442           grouping.add_member(student, StudentMembership::STATUSES[:inviter])
443         else
444           grouping.add_member(student)
445         end
446       end
447 
448     end
449     return collision_error
450   end
451 
452   # Updates repository permissions for all groupings of
453   # an assignment. This is a handy method, if for example grouping
454   # creation/deletion gets rolled back. The rollback does not
455   # reestablish proper repository permissions.
456   def update_repository_permissions_forall_groupings
457     # IMPORTANT: need to reload from DB
458     self.reload
459     groupings.each do |grouping|
460       grouping.update_repository_permissions
461     end
462   end
463 
464   def grouped_students
465     result_students = []
466     student_memberships.each do |student_membership|
467       result_students.push(student_membership.user)
468     end
469     return result_students
470   end
471 
472   def ungrouped_students
473     Student.all(:conditions => {:hidden => false}) - grouped_students
474   end
475 
476   def valid_groupings
477     result = []
478     groupings.all(:include => [{:student_memberships => :user}]).each do |grouping|
479       if grouping.admin_approved || grouping.student_memberships.count >= group_min
480         result.push(grouping)
481       end
482     end
483     return result
484   end
485 
486   def invalid_groupings
487     return groupings - valid_groupings
488   end
489 
490   def assigned_groupings
491     return groupings.all(:joins => :ta_memberships, :include => [{:ta_memberships => :user}]).uniq
492   end
493 
494   def unassigned_groupings
495     return groupings - assigned_groupings
496   end
497 
498   # Get a list of subversion client commands to be used for scripting
499   def get_svn_export_commands
500     svn_commands = [] # the commands to be exported
501     self.submissions.each do |submission|
502       grouping = submission.grouping
503       svn_commands.push("svn export -r #{submission.revision_number} #{grouping.group.repository_external_access_url}/#{self.repository_folder} \"#{grouping.group.group_name}\"")
504     end
505     return svn_commands
506   end
507 
508   # Get a list of group_name, repo-url pairs
509   def get_svn_repo_list
510     string = FasterCSV.generate do |csv|
511       self.groupings.each do |grouping|
512         group = grouping.group
513         csv << [group.group_name,group.repository_external_access_url]
514       end
515     end
516     return string
517   end
518 
519   # Get a simple CSV report of marks for this assignment
520   def get_simple_csv_report
521     students = Student.all
522     out_of = self.total_mark
523     csv_string = FasterCSV.generate do |csv|
524        students.each do |student|
525          final_result = []
526          final_result.push(student.user_name)
527          grouping = student.accepted_grouping_for(self.id)
528          if grouping.nil? || !grouping.has_submission?
529            final_result.push('')
530          else
531            submission = grouping.current_submission_used
532            final_result.push(submission.result.total_mark / out_of * 100)
533          end
534          csv << final_result
535        end
536     end
537     return csv_string
538   end
539 
540   # Get a detailed CSV report of marks (includes each criterion)
541   # for this assignment. Produces slightly different reports, depending
542   # on which criteria type has been used the this assignment.
543   def get_detailed_csv_report
544     # which marking scheme do we have?
545     if self.marking_scheme_type == MARKING_SCHEME_TYPE[:flexible]
546       return get_detailed_csv_report_flexible
547     else
548       # default to rubric
549       return get_detailed_csv_report_rubric
550     end
551   end
552 
553   # Get a detailed CSV report of rubric based marks
554   # (includes each criterion) for this assignment.
555   # Produces CSV rows such as the following:
556   #   student_name,95.22222,3,4,2,5,5,4,0/2
557   # Criterion values should be read in pairs. I.e. 2,3 means
558   # a student scored 2 for a criterion with weight 3.
559   # Last column are grace-credits.
560   def get_detailed_csv_report_rubric
561     out_of = self.total_mark
562     students = Student.all
563     rubric_criteria = self.rubric_criteria
564     csv_string = FasterCSV.generate do |csv|
565       students.each do |student|
566         final_result = []
567         final_result.push(student.user_name)
568         grouping = student.accepted_grouping_for(self.id)
569         if grouping.nil? || !grouping.has_submission?
570           # No grouping/no submission
571           final_result.push('')                         # total percentage
572           rubric_criteria.each do |rubric_criterion|
573             final_result.push('')                       # mark
574             final_result.push(rubric_criterion.weight)  # weight
575           end
576           final_result.push('')                         # extra-mark
577           final_result.push('')                         # extra-percentage
578         else
579           submission = grouping.current_submission_used
580           final_result.push(submission.result.total_mark / out_of * 100)
581           rubric_criteria.each do |rubric_criterion|
582             mark = submission.result.marks.find_by_markable_id_and_markable_type(rubric_criterion.id, "RubricCriterion")
583             if mark.nil?
584               final_result.push('')
585             else
586               final_result.push(mark.mark || '')
587             end
588             final_result.push(rubric_criterion.weight)
589           end
590           final_result.push(submission.result.get_total_extra_points)
591           final_result.push(submission.result.get_total_extra_percentage)
592         end
593         # push grace credits info
594         grace_credits_data = student.remaining_grace_credits.to_s + "/" + student.grace_credits.to_s
595         final_result.push(grace_credits_data)
596 
597         csv << final_result
598       end
599     end
600     return csv_string
601   end
602 
603   # Get a detailed CSV report of flexible criteria based marks
604   # (includes each criterion, with it's out-of value) for this assignment.
605   # Produces CSV rows such as the following:
606   #   student_name,95.22222,3,4,2,5,5,4,0/2
607   # Criterion values should be read in pairs. I.e. 2,3 means 2 out-of 3.
608   # Last column are grace-credits.
609   def get_detailed_csv_report_flexible
610     out_of = self.total_mark
611     students = Student.all
612     flexible_criteria = self.flexible_criteria
613     csv_string = FasterCSV.generate do |csv|
614       students.each do |student|
615         final_result = []
616         final_result.push(student.user_name)
617         grouping = student.accepted_grouping_for(self.id)
618         if grouping.nil? || !grouping.has_submission?
619           # No grouping/no submission
620           final_result.push('')                 # total percentage
621           flexible_criteria.each do |criterion| ##  empty criteria
622             final_result.push('')               # mark
623             final_result.push(criterion.max)    # out-of
624           end
625           final_result.push('')                 # extra-marks
626           final_result.push('')                 # extra-percentage
627         else
628           # Fill in actual values, since we have a grouping
629           # and a submission.
630           submission = grouping.current_submission_used
631           final_result.push(submission.result.total_mark / out_of * 100)
632           flexible_criteria.each do |criterion|
633             mark = submission.result.marks.find_by_markable_id_and_markable_type(criterion.id, "FlexibleCriterion")
634             if mark.nil?
635               final_result.push('')
636             else
637               final_result.push(mark.mark || '')
638             end
639             final_result.push(criterion.max)
640           end
641           final_result.push(submission.result.get_total_extra_points)
642           final_result.push(submission.result.get_total_extra_percentage)
643         end
644         # push grace credits info
645         grace_credits_data = student.remaining_grace_credits.to_s + "/" + student.grace_credits.to_s
646         final_result.push(grace_credits_data)
647 
648         csv << final_result
649       end
650     end
651     return csv_string
652   end
653 
654   def replace_submission_rule(new_submission_rule)
655     if self.submission_rule.nil?
656       self.submission_rule = new_submission_rule
657       self.save
658     else
659       self.submission_rule.destroy
660       self.submission_rule = new_submission_rule
661       self.save
662     end
663   end
664 
665   def next_criterion_position
666     # We're using count here because this fires off a DB query, thus
667     # grabbing the most up-to-date count of the rubric criteria.
668     return self.rubric_criteria.count + 1
669   end
670 
671   def get_criteria
672     if self.marking_scheme_type == 'rubric'
673        return self.rubric_criteria
674     else
675        return self.flexible_criteria
676     end
677   end
678 
679   def criteria_count
680     if self.marking_scheme_type == 'rubric'
681        return self.rubric_criteria.size
682     else
683        return self.flexible_criteria.size
684     end
685   end
686 
687   # Returns an array with the number of groupings who scored between
688   # certain percentage ranges [0-5%, 6-10%, ...]
689   # intervals defaults to 20
690   def grade_distribution_as_percentage(intervals=20)
691     distribution = Array.new(intervals, 0)
692     out_of = self.total_mark
693 
694     if out_of == 0
695       return distribution
696     end
697 
698     steps = 100 / intervals # number of percentage steps in each interval
699     groupings = self.groupings.all(:include => [{:current_submission_used => :result}])
700 
701     groupings.each do |grouping|
702       submission = grouping.current_submission_used
703       if !submission.nil? && submission.has_result?
704         result = submission.result
705         if result.marking_state == Result::MARKING_STATES[:complete]
706           percentage = (result.total_mark / out_of * 100).ceil
707           if percentage == 0
708             distribution[0] += 1
709           elsif percentage >= 100
710             distribution[intervals - 1] += 1
711           elsif (percentage % steps) == 0
712             distribution[percentage / steps - 1] += 1
713           else
714             distribution[percentage / steps] += 1
715           end
716         end
717       end
718     end # end of groupings loop
719 
720     return distribution
721   end
722 
723   # Returns all the TAs associated with the assignment
724   def tas
725     ids = self.ta_memberships.map { |m| m.user_id }
726     return Ta.find(ids)
727   end
728 
729   # Returns all the submissions that have been graded
730   def graded_submissions
731     return self.submissions.select { |submission| submission.result.marking_state == Result::MARKING_STATES[:complete] }
732   end
733 
734   private
735 
736   # Returns true if we are safe to set the repository name
737   # to a non-autogenerated value. Called by add_csv_group.
738   def is_candidate_for_setting_custom_repo_name?(row)
739     # Repository name can be customized if
740     #  - this assignment is set up to allow external submits only
741     #  - group_max = 1
742     #  - there's only one student member in this row of the csv and
743     #  - the group name is equal to the only group member
744     if MarkusConfigurator.markus_config_repository_admin? &&
745        self.allow_web_submits == false &&
746        row.length == 3 && self.group_max == 1 &&
747        !row[2].blank? && row[0] == row[2]
748       return true
749     else
750       return false
751     end
752   end
753 
754   def reset_collection_time
755     submission_rule.reset_collection_time
756   end
757 
758   def update_assigned_tokens
759     self.tokens.each do |t|
760       t.update_tokens(self.tokens_per_day_was, self.tokens_per_day)
761     end
762   end
763 end

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