-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathassignment.rb
286 lines (230 loc) · 7.71 KB
/
assignment.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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
class Assignment < Progress
include Contextualization
include WithMessages
include Gamified
markdown_on :extra_preview
belongs_to :exercise
has_one :guide, through: :exercise
has_many :messages, -> { order(date: :desc) }, dependent: :destroy
belongs_to :organization
belongs_to :submitter, class_name: 'User'
validates_presence_of :exercise, :submitter
delegate :language, :name, :navigable_parent, :settings,
:limited?, :input_kids?, :choice?, :results_hidden?, to: :exercise
delegate :completed?, :solved?, to: :submission_status
delegate :content_available_in?, to: :parent
alias_attribute :status, :submission_status
alias_attribute :attempts_count, :attemps_count
scope :by_exercise_ids, -> (exercise_ids) do
where(exercise_id: exercise_ids) if exercise_ids
end
scope :by_usernames, -> (usernames) do
joins(:submitter).where('users.name' => usernames) if usernames
end
defaults do
self.query_results = []
self.expectation_results = []
end
alias_method :parent_content, :guide
alias_method :user, :submitter
after_initialize :set_default_top_submission_status
before_save :award_experience_points!, :update_top_submission!, if: :submission_status_changed?
after_save :dirty_parent_by_submission!, if: :completion_changed?
before_validation :set_current_organization!, unless: :organization
# TODO: Momentary as some assignments may not have an associated organization
def set_current_organization!
self.organization = Organization.current
end
def recontextualize!(new_organization = Organization.current)
if organization != new_organization
dirty_parent_by_submission! if organization.present? && exercise.used_in?(organization)
self.organization = new_organization
self.parent_id = nil
end
end
def set_default_top_submission_status
self.top_submission_status ||= 0
end
def completion_changed?
completed_before_last_save? != completed?
end
def completed_before_last_save?
status_before_last_save.completed?
end
def evaluate_manually!(teacher_evaluation)
update! status: teacher_evaluation[:status], manual_evaluation_comment: teacher_evaluation[:manual_evaluation]
end
def visible_status
if results_hidden? && !pending?
:manual_evaluation_pending.to_submission_status
else
super
end
end
def persist_submission!(submission)
transaction do
update! submission_id: submission.id
update! submitted_at: Time.current
update_submissions_count!
update_last_submission!
end
end
def extension
exercise.language.extension
end
def notify!
unless Organization.silenced?
update_misplaced!(current_notification_contexts.size > 1)
Mumukit::Nuntius.notify! 'submissions', to_resource_h
end
end
def current_notification_contexts
[Organization.current, submitter.current_immersive_context_at(exercise)].uniq
end
def notify_to_accessible_organizations!
warn "Don't use notify_to_accessible_organizations!. Use notify_to_student_granted_organizations! instead"
notify_to_student_granted_organizations!
end
def notify_to_student_granted_organizations!
submitter.student_granted_organizations.each do |organization|
organization.switch!
notify!
end
end
def self.evaluate_manually!(teacher_evaluation)
Assignment.find_by(submission_id: teacher_evaluation[:submission_id])&.evaluate_manually! teacher_evaluation
end
def content=(content)
if exercise.solvable?
self.solution = exercise.single_choice? ? exercise.choice_index_for(content) : content
end
end
def test
exercise.test && language.interpolate_references_for(self, exercise.test)
end
def extra
exercise.extra && language.interpolate_references_for(self, exercise.extra)
end
def extra_preview
Mumukit::ContentType::Markdown.highlighted_code(language.name, extra)
end
def run_update!
running!
begin
update! yield
rescue => e
errored! e.message
raise e
end
end
def manual_evaluation_pending!
update! submission_status: :manual_evaluation_pending
end
def passed!
update! submission_status: :passed
end
def skipped!
update! submission_status: :skipped
end
def running!
update! submission_status: :running,
result: nil,
test_results: nil,
expectation_results: [],
manual_evaluation_comment: nil
end
def errored!(message)
update! result: message, submission_status: :errored
end
%w(query try).each do |key|
name = "run_#{key}!"
define_method(name) { |params| exercise.send name, params.merge(extra: extra, settings: settings) }
end
def run_tests!(params)
exercise.run_tests! params.merge(extra: extra, test: test, settings: settings)
end
def to_resource_h
excluded_fields = %i(created_at exercise_id id organization_id parent_id solution submission_id
submission_status submitted_at submitter_id top_submission_status updated_at misplaced)
as_json(except: excluded_fields,
include: {
guide: {
only: [:slug, :name],
include: {
lesson: {only: [:number]},
language: {only: [:name]}},
},
exercise: {only: [:name, :number]},
submitter: {only: [:email, :social_id, :uid], methods: [:name, :profile_picture]}}).
deep_merge(
'organization' => Organization.current.name,
'sid' => submission_id,
'created_at' => submitted_at || updated_at,
'content' => solution,
'status' => submission_status,
'exercise' => {
'eid' => exercise.bibliotheca_id
},
'guide' => {'parent' => {
'type' => navigable_parent.class.to_s,
'name' => navigable_parent.name,
'position' => navigable_parent.try(:number),
'chapter' => guide.chapter.as_json(only: [:id], methods: [:name])
}})
end
def tips
@tips ||= exercise.assist_with(self)
end
def increment_attempts!
self.attempts_count += 1 if should_retry?
end
def attempts_left
navigable_parent.attempts_left_for(self)
end
# Tells whether the submitter of this
# assignment can keep on sending submissions
# which is true for non limited or for assignments
# that have not reached their submissions limit
def attempts_left?
!limited? || attempts_left > 0
end
def current_content
solution || default_content
end
def current_content_at(index)
exercise.sibling_at(index).assignment_for(submitter).current_content
end
def default_content
@default_content ||= language.interpolate_references_for(self, exercise.default_content)
end
def files
exercise.files_for(current_content)
end
def update_top_submission!
self.top_submission_status = submission_status unless submission_status.improved_by?(top_submission_status)
end
def update_misplaced!(value)
update! misplaced: value if value != misplaced?
end
def self.build_for(user, exercise, organization)
Assignment.new submitter: user, exercise: exercise, organization: organization
end
private
def duplicates_key
{ exercise: exercise, submitter: submitter }
end
def update_submissions_count!
self.class.connection.execute(
"update exercises
set submissions_count = submissions_count + 1
where id = #{exercise.id}")
self.class.connection.execute(
"update assignments
set submissions_count = submissions_count + 1
where id = #{id}")
exercise.reload
end
def update_last_submission!
submitter.update!(last_submission_date: Time.current, last_exercise: exercise)
end
end