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
2011-02-24 13:26:39 +08:00
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
2011-03-09 06:00:41 +08:00
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-03-09 06:00:41 +08:00
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 )
2011-03-27 11:30:53 +08:00
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
2011-02-24 13:26:39 +08:00
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
2011-02-24 13:26:39 +08:00
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?
2011-02-24 13:26:39 +08:00
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
2011-04-13 01:03:21 +08:00
def self . recompute_final_scores ( user_id )
2011-02-01 09:57:29 +08:00
user = User . find ( user_id )
user . student_enrollments . each do | enrollment |
2011-04-13 01:03:21 +08:00
send_later ( :recompute_final_score , user_id , enrollment . course_id )
2011-02-01 09:57:29 +08:00
end
end
2011-04-13 01:03:21 +08:00
def self . recompute_final_score ( user_ids , course_id )
GradeCalculator . recompute_final_score ( user_ids , course_id )
end
def computed_final_grade
raise " TODO "
2011-02-01 09:57:29 +08:00
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