Class: Exam

Inherits:
ApplicationRecord show all
Includes:
FriendlyName, GuideContainer, TerminalNavigation, WithTimedEnablement
Defined in:
app/models/exam.rb

Defined Under Namespace

Classes: PassingCriterion

Class Method Summary collapse

Instance Method Summary collapse

Methods included from WithTimedEnablement

#enabled?, #enabled_range

Methods included from TerminalNavigation

#friendly, #navigable_name, #navigation_end?, #next_for, #siblings, #structural_parent

Methods included from FriendlyName

#friendly_name, #to_param

Methods included from GuideContainer

#friendly, #index_usage!

Methods included from Container

#content_used_in?, #navigable_content_in, #progress_for

Methods inherited from ApplicationRecord

active_between, aggregate_of, all_except, defaults, #delete, #destroy!, enum_prefixed_translations_for, numbered, organic_on, resource_fields, #save, #save_and_notify!, #save_and_notify_changes!, serialize_symbolized_hash_array, teaser_on, #update_and_notify!, update_or_create!, whitelist_attributes, with_pg_retry, with_temporary_token

Methods included from WithPgLock

#with_pg_lock

Class Method Details

.adapt_json_values(exam) ⇒ Object



177
178
179
180
181
182
183
184
185
# File 'app/models/exam.rb', line 177

def self.adapt_json_values(exam)
  exam[:guide_id] = Guide.locate!(exam[:slug]).id
  exam[:organization_id] = Organization.current.id
  exam[:course_id] = Course.locate!(exam[:course].to_s).id
  exam[:users] = User.where(uid: exam[:uids])
  exam[:start_time] = exam[:start_time].in_time_zone
  exam[:end_time] = exam[:end_time].in_time_zone
  exam[:classroom_id] = exam[:eid] if exam[:eid].present?
end

.import_from_resource_h!(json) ⇒ Object



156
157
158
159
160
161
162
163
164
# File 'app/models/exam.rb', line 156

def self.import_from_resource_h!(json)
  exam_data = json.with_indifferent_access
  Organization.locate!(exam_data[:organization].to_s).switch!
  adapt_json_values exam_data
  remove_previous_version exam_data[:eid], exam_data[:guide_id]
  exam = where(classroom_id: exam_data[:eid]).update_or_create!(whitelist_attributes(exam_data))
  exam.process_users exam_data[:users]
  exam
end

.remove_previous_version(eid, guide_id) ⇒ Object



187
188
189
190
191
192
193
# File 'app/models/exam.rb', line 187

def self.remove_previous_version(eid, guide_id)
  Rails.logger.info "Looking for"
  where("guide_id=? and organization_id=? and classroom_id!=?", guide_id, Organization.current.id, eid).tap do |exams|
    Rails.logger.info "Deleting exams with ORG_ID:#{Organization.current.id} - GUIDE_ID:#{guide_id} - CLASSROOM_ID:#{eid}"
    exams.destroy_all
  end
end

.upsert_students!(json) ⇒ Object



166
167
168
169
170
171
172
173
174
175
# File 'app/models/exam.rb', line 166

def self.upsert_students!(json)
  data = json.with_indifferent_access
  exam = find_by(classroom_id: data[:eid])

  added_users = User.where(uid: data[:added])
  deleted_users = User.where(uid: data[:deleted])

  exam.authorize_users! added_users
  exam.unauthorize_users! deleted_users
end

Instance Method Details

#accessible_for?(user) ⇒ Boolean

Returns:

  • (Boolean)


51
52
53
# File 'app/models/exam.rb', line 51

def accessible_for?(user)
  authorized?(user) && enabled_for?(user)
end

#attempts_left_for(assignment) ⇒ Object



124
125
126
# File 'app/models/exam.rb', line 124

def attempts_left_for(assignment)
  max_attempts_for(assignment.exercise) - (assignment.attempts_count || 0)
end

#authorization_for(user) ⇒ Object



71
72
73
# File 'app/models/exam.rb', line 71

def authorization_for(user)
  authorizations.find_by(user_id: user.id)
end

#authorizations_for(users) ⇒ Object



75
76
77
# File 'app/models/exam.rb', line 75

def authorizations_for(users)
  authorizations.where(user_id: users.map(&:id))
end

#authorize!(user) ⇒ Object



59
60
61
# File 'app/models/exam.rb', line 59

def authorize!(user)
  users << user unless authorized?(user)
end

#authorize_users!(users) ⇒ Object



103
104
105
# File 'app/models/exam.rb', line 103

def authorize_users!(users)
  users.each { |user| authorize! user }
end

#authorized?(user) ⇒ Boolean

Returns:

  • (Boolean)


63
64
65
# File 'app/models/exam.rb', line 63

def authorized?(user)
  users.include? user
end

#clean_authorizations(authorized_users) ⇒ Object



116
117
118
# File 'app/models/exam.rb', line 116

def clean_authorizations(authorized_users)
  unauthorize_users!(users.all_except(authorized_users))
end

#enabled_for?(user) ⇒ Boolean

Returns:

  • (Boolean)


34
35
36
# File 'app/models/exam.rb', line 34

def enabled_for?(user)
  enabled_range_for(user).cover? Time.current
end

#enabled_range_for(user) ⇒ Object



67
68
69
# File 'app/models/exam.rb', line 67

def enabled_range_for(user)
  start_time..real_end_time(user)
end

#ensure_valid_passing_criterion!Object



148
149
150
# File 'app/models/exam.rb', line 148

def ensure_valid_passing_criterion!
  passing_criterion.ensure_valid!
end

#in_progress_for?(user) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
# File 'app/models/exam.rb', line 38

def in_progress_for?(user)
  accessible_for?(user) && started?(user)
end

#limited_for?(exercise) ⇒ Boolean

Returns:

  • (Boolean)


128
129
130
# File 'app/models/exam.rb', line 128

def limited_for?(exercise)
  max_attempts_for(exercise).present?
end

#passing_criterionObject



144
145
146
# File 'app/models/exam.rb', line 144

def passing_criterion
  @passing_criterion ||= Exam::PassingCriterion.parse(passing_criterion_type, passing_criterion_value)
end

#process_users(users) ⇒ Object



111
112
113
114
# File 'app/models/exam.rb', line 111

def process_users(users)
  authorize_users!(users)
  clean_authorizations users
end

#real_end_time(user) ⇒ Object



91
92
93
94
95
96
97
# File 'app/models/exam.rb', line 91

def real_end_time(user)
  if duration.present? && started?(user)
    [started_at(user) + duration.minutes, end_time].min
  else
    end_time
  end
end

#reindex_usages!Object



120
121
122
# File 'app/models/exam.rb', line 120

def reindex_usages!
  index_usage! organization
end

#resettable?Boolean

Returns:

  • (Boolean)


136
137
138
# File 'app/models/exam.rb', line 136

def resettable?
  false
end

#results_hidden_for?(exercise) ⇒ Boolean

Returns:

  • (Boolean)


132
133
134
# File 'app/models/exam.rb', line 132

def results_hidden_for?(exercise)
  exercise.choice? && results_hidden_for_choices?
end

#set_classroom_id!Object



140
141
142
# File 'app/models/exam.rb', line 140

def set_classroom_id!
  self.classroom_id ||= SecureRandom.hex(8)
end

#set_default_criterion_type!Object



152
153
154
# File 'app/models/exam.rb', line 152

def set_default_criterion_type!
  self.passing_criterion_type ||= :none
end

#start!(user) ⇒ Object



79
80
81
82
83
84
85
# File 'app/models/exam.rb', line 79

def start!(user)
  return if user.teacher_here?

  authorization = authorization_for(user)
  raise Mumuki::Domain::ForbiddenError unless authorization
  authorization.start!
end

#started?(user) ⇒ Boolean

Returns:

  • (Boolean)


87
88
89
# File 'app/models/exam.rb', line 87

def started?(user)
  authorization_for(user).try(:started?)
end

#started_at(user) ⇒ Object



99
100
101
# File 'app/models/exam.rb', line 99

def started_at(user)
  authorization_for(user).started_at
end

#timed?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'app/models/exam.rb', line 55

def timed?
  duration.present?
end

#unauthorize_users!(users) ⇒ Object



107
108
109
# File 'app/models/exam.rb', line 107

def unauthorize_users!(users)
  authorizations_for(users).destroy_all
end

#used_in?(organization) ⇒ Boolean

Returns:

  • (Boolean)


30
31
32
# File 'app/models/exam.rb', line 30

def used_in?(organization)
  organization == self.organization
end

#validate_accessible_for!(user) ⇒ Object



42
43
44
45
46
47
48
49
# File 'app/models/exam.rb', line 42

def validate_accessible_for!(user)
  if user.present?
    raise Mumuki::Domain::ForbiddenError unless authorized?(user)
    raise Mumuki::Domain::GoneError unless enabled_for?(user)
  else
    raise Mumuki::Domain::UnauthorizedError
  end
end