canvas-lms/app/models/enrollment.rb

509 lines
15 KiB
Ruby
Raw Normal View History

2011-02-01 09:57:29 +08:00
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class Enrollment < ActiveRecord::Base
include Workflow
include EnrollmentDateRestrictions
2011-02-01 09:57:29 +08:00
belongs_to :course, :touch => true
belongs_to :course_section
belongs_to :root_account, :class_name => 'Account'
belongs_to :user
belongs_to :associated_user, :class_name => 'User'
has_many :role_overrides, :as => :context
has_many :pseudonyms, :primary_key => :user_id, :foreign_key => :user_id
has_many :course_account_associations, :foreign_key => 'course_id', :primary_key => 'course_id'
validates_presence_of :user_id
validates_presence_of :course_id
before_save :assign_uuid
before_save :assert_section
after_save :touch_user
after_create :update_user_account_associations
trigger.after(:insert).where("NEW.workflow_state = 'active'") do
<<-SQL
UPDATE assignments
SET needs_grading_count = needs_grading_count + 1
WHERE id IN (SELECT assignment_id
FROM submissions
WHERE user_id = NEW.user_id
AND context_code = 'course_' || NEW.course_id
AND (#{Submission.needs_grading_conditions})
);
SQL
end
trigger.after(:update).where("NEW.workflow_state <> OLD.workflow_state AND (NEW.workflow_state = 'active' OR OLD.workflow_state = 'active')") do
<<-SQL
UPDATE assignments
SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END
WHERE id IN (SELECT assignment_id
FROM submissions
WHERE user_id = NEW.user_id
AND context_code = 'course_' || NEW.course_id
AND (#{Submission.needs_grading_conditions})
);
SQL
end
2011-02-01 09:57:29 +08:00
adheres_to_policy
has_a_broadcast_policy
set_broadcast_policy do |p|
p.dispatch :enrollment_invitation
p.to { self.user }
p.whenever { |record|
record.course and
record.user.registered? and
((record.just_created && record.invited?) || record.changed_state(:invited) || @re_send_confirmation)
}
p.dispatch :enrollment_registration
p.to { self.user.communication_channel }
p.whenever { |record|
record.course and
!record.user.registered? and
((record.just_created && record.invited?) || record.changed_state(:invited) || @re_send_confirmation)
}
p.dispatch :enrollment_notification
p.to { self.user }
p.whenever { |record|
record.course &&
!record.course.created? &&
record.just_created && record.active?
}
p.dispatch :enrollment_accepted
p.to {self.course.admins - [self.user] }
p.whenever { |record|
record.course &&
!record.just_created && (record.changed_state(:active, :invited) || record.changed_state(:active, :creation_pending))
}
end
named_scope :active,
:conditions => ['enrollments.workflow_state != ?', 'deleted']
named_scope :admin,
:select => 'course_id',
:joins => :course,
:conditions => "enrollments.type IN ('TeacherEnrollment','TAEnrollment', 'DesignerEnrollment')
AND (courses.workflow_state = 'claimed' OR (enrollments.workflow_state = 'active' and courses.workflow_state = 'available'))"
named_scope :student,
:select => 'course_id',
:joins => :course,
:conditions => "enrollments.type = 'StudentEnrollment'
AND enrollments.workflow_state = 'active'
AND courses.workflow_state = 'available'"
named_scope :all_student,
:include => :course,
:conditions => "enrollments.type = 'StudentEnrollment'
AND enrollments.workflow_state IN ('invited', 'active', 'completed')
AND courses.workflow_state IN ('available', 'completed')"
named_scope :ended,
:joins => :course,
:conditions => "courses.workflow_state = 'aborted' or courses.workflow_state = 'completed' or enrollments.workflow_state = 'rejected' or enrollments.workflow_state = 'completed'"
def self.highest_enrollment_type(type, type2)
res = ['TeacherEnrollment','TaEnrollment','DesignerEnrollment','StudentEnrollment','ObserverEnrollment'].find{|t| t == type || t == type2}
res ||= type || type2
res
end
def update_user_account_associations
self.user.send_later(:update_account_associations)
end
2011-02-01 09:57:29 +08:00
def conclude
self.workflow_state = "completed"
self.completed_at = Time.now
self.save
end
def page_views_by_day(options={})
conditions = {
:context_id => course.id,
:context_type => course.class.to_s,
:user_id => user.id
}
if options[:dates]
conditions.merge!({
:created_at, (options[:dates].first)..(options[:dates].last)
})
end
page_views_as_hash = {}
PageView.count(
:group => "date(created_at)",
:conditions => conditions
).each do |day|
page_views_as_hash[day.first] = day.last
end
page_views_as_hash
end
memoize :page_views_by_day
def defined_by_sis?
!!self.sis_source_id
end
def participating?
participating_student? || participating_admin? || participating_observer?
end
def student?
self.is_a?(StudentEnrollment)
end
def assigned_observer?
self.is_a?(ObserverEnrollment) && self.associated_user_id
end
def participating_student?
self.is_a?(StudentEnrollment) && self.active?
end
def participating_observer?
self.is_a?(ObserverEnrollment) && self.active?
end
def participating_admin?
(self.is_a?(TeacherEnrollment) || self.is_a?(TaEnrollment)) && self.active?
end
def associated_user_name
self.associated_user && self.associated_user.short_name
end
def assert_section
self.course_section ||= self.course.default_section if self.course
self.root_account_id = self.course_section.root_account_id rescue nil
end
def short_name(length=nil)
return @short_name if @short_name
@short_name = self.course_section.display_name if self.course_section && self.root_account && self.root_account.show_section_name_as_course_name
@short_name ||= self.course.name
@short_name ||= "Course"
@short_name = @short_name[0..length] if length
@short_name
end
def long_name
return @long_name if @long_name
@long_name = self.course.name || "Course"
@long_name += ", #{self.course_section.display_name}" if self.course_section && self.course_section.display_name && self.course_section.display_name != self.course.name
@long_name
end
def rank_sortable(student_first=false)
type = self.class.to_s
2011-02-01 09:57:29 +08:00
case type
when 'StudentEnrollment'
student_first ? 0 : 4
when 'TeacherEnrollment'
1
when 'TaEnrollment'
2
when 'ObserverEnrollment'
5
when 'DesignerEnrollment'
3
else
6
end
end
def state_sortable
case state
when :invited
1
when :creation_pending
1
when :active
0
when :deleted
5
when :rejected
4
when :completed
2
else
6
end
end
def accept!
res = accept
raise "can't accept" unless res
res
end
def accept
return false unless invited?
ids = nil
ids = self.user.dashboard_messages.find_all_by_context_id_and_context_type(self.id, 'Enrollment', :select => "id").map(&:id) if self.user
Message.delete_all({:id => ids}) if ids && !ids.empty?
update_attribute(:workflow_state, course.enrollment_state_based_on_date(self))
true
end
2011-02-01 09:57:29 +08:00
workflow do
state :invited do
event :reject, :transitions_to => :rejected
event :complete, :transitions_to => :completed
event :pend, :transitions_to => :pending
end
state :creation_pending do
event :invite, :transitions_to => :invited
end
state :active do
event :reject, :transitions_to => :rejected
event :complete, :transitions_to => :completed
event :pend, :transitions_to => :pending
end
state :inactive do
event :activate, :transitions_to => :active
end
2011-02-01 09:57:29 +08:00
state :deleted
state :rejected do
event :unreject, :transitions_to => :invited
end
state :completed
end
alias_method :destroy!, :destroy
def destroy
self.workflow_state = 'deleted'
self.save
end
def restore
self.workflow_state = 'active'
self.save
end
def re_send_confirmation!
@re_send_confirmation = true
self.save
@re_send_confirmation = false
true
end
def has_permission_to?(action)
@permission_lookup ||= {}
unless @permission_lookup.has_key? action
@permission_lookup[action] = RoleOverride.permission_for(self, action, self.class.to_s)[:enabled]
end
@permission_lookup[action]
end
def pending?
self.invited? || self.creation_pending?
end
def active_or_pending?
self.active? || self.inactive? || self.pending?
2011-02-01 09:57:29 +08:00
end
def email
self.user.email rescue "No Email"
end
def user_name
read_attribute(:user_name) || self.user.name rescue "Unknown User"
end
def context
@context ||= course
end
def context_id
@context_id ||= course_id
end
def can_switch_to?(type)
case type
when 'ObserverEnrollment'
['TeacherEnrollment', 'TaEnrollment', 'DesignerEnrollment'].include?(self.type)
when 'StudentEnrollment'
['TeacherEnrollment', 'TaEnrollment', 'DesignerEnrollment'].include?(self.type)
when 'TaEnrollment'
['TeacherEnrollment'].include?(self.type)
else
false
end
end
def self.readable_type(type)
case type
when 'TeacherEnrollment'
"Teacher"
when 'StudentEnrollment'
"Student"
when 'TaEnrollment'
"TA"
when 'ObserverEnrollment'
"Observer"
when 'DesignerEnrollment'
"Designer"
else
"Student"
end
end
def readable_type
Enrollment.readable_type(self.class.to_s)
end
def self.recompute_final_grades(user_id)
user = User.find(user_id)
user.student_enrollments.each do |enrollment|
send_later(:recompute_final_grade, user_id, enrollment.course_id)
end
end
def self.recompute_final_grade(user_ids, course_id)
GradeCalculator.recompute_final_grade(user_ids, course_id)
end
def self.students(opts={})
with_scope :find => opts do
find(:all, :conditions => {:type => 'Student'}).map(&:user).compact
end
end
def self.typed_enrollment(type)
return nil unless ['StudentEnrollment', 'TeacherEnrollment', 'TaEnrollment', 'ObserverEnrollment', 'DesignerEnrollment'].include?(type)
type.constantize
end
def admin?
false
end
def to_atom
Atom::Entry.new do |entry|
entry.title = "#{self.user.name} in #{self.course.name}"
entry.updated = self.updated_at
entry.published = self.created_at
entry.links << Atom::Link.new(:rel => 'alternate',
:href => "/courses/#{self.course.id}/enrollments/#{self.id}")
end
end
set_policy do
given { |user| self.user == user }
set { can :read and can :read_grades }
given {|user, session| self.course.grants_right?(user, session, :participate_as_student) && self.user.show_user_services }
set { can :read_services }
# read_services says this person has permission to see what web services this enrollment has linked to their account
given {|user, session| self.course.grants_right?(user, session, :manage_students) && self.user.show_user_services }
set { can :read and can :read_services }
given { |user, session| self.course.students_visible_to(user, true).map(&:id).include?(self.user_id) && self.course.grants_right?(user, session, :manage_grades) }#admins.include? user }
set { can :read and can :read_grades }
given { |user| !!Enrollment.active.find_by_user_id_and_associated_user_id(user.id, self.user_id) }
set { can :read and can :read_grades and can :read_services}
end
named_scope :before, lambda{|date|
{:conditions => ['enrollments.created_at < ?', date]}
}
named_scope :for_user, lambda{|user|
{:conditions => ['enrollments.user_id = ?', user.id] }
}
named_scope :for_courses_with_user_name, lambda{|courses|
{
:conditions => {:course_id => courses.map(&:id)},
:joins => :user,
:select => 'user_id, course_id, users.name AS user_name'
}
}
named_scope :accepted, lambda{
{:conditions => ['enrollments.workflow_state != ?', 'invited'] }
}
named_scope :active_or_pending, lambda{
{:conditions => {:workflow_state => ['invited', 'creation_pending', 'active']}}
}
named_scope :currently_online, lambda{
{:joins => :pseudonyms, :conditions => ['pseudonyms.last_request_at > ?', 5.minutes.ago] }
}
def assign_uuid
self.uuid ||= UUIDSingleton.instance.generate
end
protected :assign_uuid
def uuid
if !read_attribute(:uuid)
self.update_attribute(:uuid, UUIDSingleton.instance.generate)
end
read_attribute(:uuid)
end
def self.limit_priveleges_to_course_section!(course, user, limit)
Enrollment.update_all({:limit_priveleges_to_course_section => !!limit}, {:course_id => course.id, :user_id => user.id})
user.touch
end
def self.course_user_state(course, uuid)
Rails.cache.fetch(['user_state', course, uuid].cache_key) do
enrollment = course.enrollments.find_by_uuid(uuid)
if enrollment
{
:enrollment_state => enrollment.workflow_state,
:user_state => enrollment.user.state
}
else
nil
end
end
end
# this is just used to get a pseudonym_id in the email_lists.js stuff.
def users_pseudonym_id
self.user.pseudonym.id
end
# this is also just used to get a communication_channel_id in the email_lists.js stuff
def communication_channel_id
self.user.communication_channel.id rescue nil
end
def self.serialization_excludes; [:uuid,:computed_final_score, :computed_current_score]; end
end