-
Notifications
You must be signed in to change notification settings - Fork 10
/
course.rb
179 lines (152 loc) · 5.42 KB
/
course.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# == Schema Information
#
# Table name: courses
#
# id :integer not null, primary key
# suffix :string(255) default("")
# prefix :string(255) default("")
# name :string(255)
# description :text
# created_at :datetime
# updated_at :datetime
# units :integer
# prereqs :text
# department_id :integer
# course_number :integer
# course_guide :text
# course_type_id :integer
#
class Course < ActiveRecord::Base
belongs_to :department
belongs_to :course_type
has_many :course_preferences, dependent: :destroy
has_many :tutors, -> { uniq }, through: :course_preferences
has_many :klasses, -> { order "semester DESC, section DESC" }, dependent: :destroy
has_many :coursesurveys, through: :klasses
#has_many :instructors, source: :klasses, conditions: ['klasses.course_id = id'], class_name: 'Klass'
has_many :instructorships, through: :klasses
has_many :course_prereqs, foreign_key: :course
has_many :post_classes, through: :course_prereqs, source: :prereq
has_one :course_chart
has_many :exams
validates :department_id, presence: true
validates :course_number, presence: true
validates_uniqueness_of :course_number, scope: [:department_id,:prefix,:suffix]
#scope :all, order("prefix, courses.course_number, suffix")
scope :ordered, -> { order("courses.course_number, prefix, suffix") }
scope :ordered_desc, -> { order("(prefix, courses.course_number, suffix) DESC") }
module Regex
PrefixNumSuffix = /\A([A-Z]*)(\d+)([A-Z]*)\z/
end
# Sunspot
searchable do
text :name, stored: true, boost: 2.0
text :description, stored: true
integer :course_number, stored: true
text :course_string, boost: 2.0 do |c|
[c.prefix, c.course_number.to_s, c.suffix].join
end
string :course_abbr
text :course_abbr
text :dept_abbrs do |c|
[c.department.name, c.department.abbr, c.department.nice_abbrs].join(' ')
end
integer :department_id, references: Department
boolean :invalid, using: :invalid?
end
# end sunspot
# A few notes about course numbers
# prefix refers to letters that appear before numbers, e.g. C for cross-listed, H for honors
# course_number refers to just the numbers, e.g. the 61 in 61A
# suffix refers to all letters that appear after the numbers, e.g. the A in 61A, the M in 145M, the AC in E130AC
# This is DIFFERENT from the old Django site's definitions
def instructors(conds={ta: false})
Instructor.find self.klasses.collect(&:instructor_ids).flatten.uniq
end
def tas
instructors(ta: true)
end
def classification
# is this an undergrad or grad class?
case course_number
when 0..199 then :undergrad
else :grad
end
end
def average_rating(semester=nil)
# BE CAREFUL, THIS IS KIND OF EXPENSIVE
r = SurveyAnswer.where(
survey_question_id: SurveyQuestion.find_by_keyword(:prof_eff),
id: self.instructorships.collect(&:survey_answer_ids).flatten
)
if semester
r = r.includes(instructorship: :klass).where(klasses: {semester: semester})
end
r = r.average(:mean)
return r
end
def latest_klass
# Return the latest klass for which there is survey data
k = klasses.ordered
k.drop_while {|k| not k.survey_answers.exists?}
k.first
end
def num_exams
exams.select("klass_id, exam_type, number").group([:klass_id, :exam_type, :number]).count.size
end
def invalid?
# Some courses are invalid, and shouldn't be listed.
!!(name =~ /INVALID/)
end
def dept_abbr
department.nice_abbrs.first
end
def dept_name
department.name
end
def course_name
# e.g. Electrical Engineering 20N
"#{dept_name} #{full_course_number}"
end
def course_abbr
# e.g. EE20N
"#{dept_abbr}#{full_course_number}"
end
def to_s
course_abbr
end
def full_course_number
"#{prefix}#{course_number}#{suffix}"
end
# @return [String] [dept.slug, full_course_number] for use in a course url. Explode it.
def slug
[department.slug, full_course_number]
end
def self.split_course_number(s, options={})
# Splits a course number string "C149" => "C", 149, nil
cn = {}
cn[:prefix], cn[:course_number], cn[:suffix] = s.upcase.scan(Regex::PrefixNumSuffix).first
cn[:course_number] = cn[:course_number].to_i
return [cn[:prefix], cn[:course_number], cn[:suffix]] if options[:hash] == false
return cn
end
# E.g. ("EE", "C149")
def Course.lookup_by_short_name(dept_abbr, full_course_number, section=nil)
(prefix, course_number, suffix) = full_course_number.scan(Regex::PrefixNumSuffix).first
department = Department.find_by_nice_abbr(dept_abbr)
#raise "Course abbreviation not well formatted: #{dept_abbr} #{full_course_number}" if course_number.blank? or department.nil?
return nil if course_number.blank? or department.nil?
Course.where(department_id: department.id, course_number: course_number, suffix: suffix, prefix: prefix).first
end
def Course.find_all_by_department_abbr(dept_abbr)
department = Department.find_by_nice_abbr(dept_abbr)
Course.find_all_by_department_id(department.id)
end
def Course.find_all_with_exams_by_department_abbr(dept_abbr)
@department = Department.find_by_nice_abbr(dept_abbr)
# TODO fix query to be more efficient
Course.where(department_id: @department.id).ordered.reject {|course| course.exams.empty?}
end
end
end