Class: User

Constant Summary collapse

PLACEHOLDER_IMAGE_URL =
'user_shape.png'.freeze

Instance Method Summary collapse

Methods included from WithUserNavigation

#navigable_name, #navigation_end?

Methods included from WithReminders

#build_reminder, #remind!, #should_remind?, #try_remind_with_lock!

Methods included from WithNotifications

#read_notification!, #unread_messages, #unread_notifications

Methods included from Awardee

#acquired_medals, #unacquired_medals

Methods included from WithTermsAcceptance

#accept_forum_terms!, #accept_profile_terms!, #forum_terms, #has_accepted?, #has_forum_terms_to_accept?, #has_profile_terms_to_accept?, #has_role_terms_to_accept?, #profile_terms, #role_specific_terms

Methods included from Onomastic

#formal_first_name, #formal_full_name, #formal_last_name, #full_name, #has_verified_full_name?, #verified_full_name

Methods included from Mumuki::Domain::Helpers::User

#any_granted_organizations, #discusser_of?, #has_immersive_main_organization?, #has_role?, #has_student_granted_organizations?, #immersive_organization_at, #immersive_organization_with_content_at, #immersive_organizations_at, #immersive_organizations_with_content_at, #main_organization, #merge_permissions!, #platform_class_name, #profile_completed?, #to_param

Methods included from Mumuki::Domain::Syncable

#platform_class_name, #sync_key

Methods included from WithDeletedUser

#deleted_user?, prepended

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_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

Instance Method Details

#abbreviated_nameObject



236
237
238
# File 'app/models/user.rb', line 236

def abbreviated_name
  "#{first_name} #{last_name.first.capitalize + '.' if last_name.present?}".strip
end

#accept_invitation!(invitation) ⇒ Object



126
127
128
# File 'app/models/user.rb', line 126

def accept_invitation!(invitation)
  make_student_of! invitation.course_slug
end

#ageObject



272
273
274
275
276
# File 'app/models/user.rb', line 272

def age
  if birthdate.present?
    @age ||= Time.current.round_years_since(birthdate.in_time_zone)
  end
end

#attach!(role, course) ⇒ Object



165
166
167
168
# File 'app/models/user.rb', line 165

def attach!(role, course)
  add_permission! role, course.slug
  save_and_notify!
end

#build_assignment(exercise, organization) ⇒ Object



244
245
246
# File 'app/models/user.rb', line 244

def build_assignment(exercise, organization)
  Assignment.build_for(self, exercise, organization)
end

#bury!Object



196
197
198
199
# File 'app/models/user.rb', line 196

def bury!
  # TODO change avatar
  update! self.class.buried_profile.merge(accepts_reminders: false, gender: nil, birthdate: nil)
end

#can_access_teacher_info_in?(organization) ⇒ Boolean

Returns:

  • (Boolean)


228
229
230
# File 'app/models/user.rb', line 228

def can_access_teacher_info_in?(organization)
  teacher_of?(organization) || organization.teacher_training?
end

#can_discuss_here?Boolean

Returns:

  • (Boolean)


224
225
226
# File 'app/models/user.rb', line 224

def can_discuss_here?
  can_discuss_in? Organization.current
end

#can_discuss_in?(organization) ⇒ Boolean

Returns:

  • (Boolean)


216
217
218
# File 'app/models/user.rb', line 216

def can_discuss_in?(organization)
  organization.access_mode(self).discuss_here?
end

#certificate_in(certificate_program, certificate_h) ⇒ Object



323
324
325
326
327
# File 'app/models/user.rb', line 323

def certificate_in(certificate_program, certificate_h)
  return if certificated_in?(certificate_program)
  certificate = certificates.create certificate_h.merge(certificate_program: certificate_program)
  UserMailer.certificate(certificate).post!
end

#certificated_in?(certificate_program) ⇒ Boolean

Returns:

  • (Boolean)


319
320
321
# File 'app/models/user.rb', line 319

def certificated_in?(certificate_program)
  certificates.where(certificate_program: certificate_program).exists?
end

#certificates_in_organization(organization = Organization.current) ⇒ Object



315
316
317
# File 'app/models/user.rb', line 315

def certificates_in_organization(organization = Organization.current)
  certificates.where certificate_program: CertificateProgram.where(organization: organization)
end

#clean_belongings!Object



349
350
351
352
353
# File 'app/models/user.rb', line 349

def clean_belongings!
  discussions.update_all initiator_id: User.deleted_user.id
  forum_messages.update_all sender_id: User.deleted_user.id
  direct_messages.where(sender: self).delete_all
end

#clear_progress!Object



109
110
111
112
113
114
# File 'app/models/user.rb', line 109

def clear_progress!
  transaction do
    update! last_submission_date: nil, last_exercise: nil
    assignments.destroy_all
  end
end

#clear_progress_for!(organization) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
# File 'app/models/user.rb', line 329

def clear_progress_for!(organization)
  location = { organization: organization }.compact

  target_assignments = assignments.where(location)

  direct_messages.where(assignment: target_assignments).delete_all

  target_assignments.delete_all
  indicators.where(location).delete_all
  user_stats.where(location).delete_all
end

#completed_containers(sequence, organization) ⇒ Object

Takes a didactic - ordered - sequence of content containers and returns those that have been completed



203
204
205
# File 'app/models/user.rb', line 203

def completed_containers(sequence, organization)
  sequence.take_while { |it| it.content.completed_for?(self, organization) }
end

#completed_containers_with_lookahead(sequence, organization, lookahead: 1) ⇒ Object

Like ‘completed_containers`, returns a slice of the completed containers in the sequence, but adding a configurable number of trailing, non-completed contaienrs



209
210
211
212
213
214
# File 'app/models/user.rb', line 209

def completed_containers_with_lookahead(sequence, organization, lookahead: 1)
  raise 'invalid lookahead' if lookahead < 1

  count = completed_containers(sequence, organization).size
  sequence[0..count + lookahead - 1]
end

#current_audienceObject



264
265
266
# File 'app/models/user.rb', line 264

def current_audience
  current_organic_context&.target_audience
end

#current_immersive_context_and_content_at(path_item) ⇒ Object



309
310
311
312
313
# File 'app/models/user.rb', line 309

def current_immersive_context_and_content_at(path_item)
  immersive_organization_with_content_at(path_item).tap do |orga, _|
    return [Organization.current, path_item] unless orga.present?
  end
end

#current_immersive_context_at(path_item) ⇒ Object



282
283
284
285
286
287
288
# File 'app/models/user.rb', line 282

def current_immersive_context_at(path_item)
  if Organization.current?
    immersive_organization_at(path_item) || Organization.current
  else
    main_organization
  end
end

#current_organic_contextObject



278
279
280
# File 'app/models/user.rb', line 278

def current_organic_context
  Organization.current? ?  Organization.current : main_organization
end

#currently_in_exam?Boolean

Returns:

  • (Boolean)


184
185
186
# File 'app/models/user.rb', line 184

def currently_in_exam?
  exams.any? { |e| e.in_progress_for? self }
end

#custom_profile_pictureObject



188
189
190
# File 'app/models/user.rb', line 188

def custom_profile_picture
  avatar&.image_url || image_url
end

#detach!(role, course, deep: false) ⇒ Object



170
171
172
173
174
# File 'app/models/user.rb', line 170

def detach!(role, course, deep: false)
  make_ex_student_of! course.slug if !deep && student_of?(course.slug) && solved_any_exercises?(course.organization)
  remove_permission! role, course.slug
  save_and_notify!
end

#has_assignments_in_organization?(organization) ⇒ Boolean

Returns:

  • (Boolean)


122
123
124
# File 'app/models/user.rb', line 122

def has_assignments_in_organization?(organization)
  assignments_in_organization(organization).exists?
end

#ignores_notification?(notification) ⇒ Boolean

Returns:

  • (Boolean)


345
346
347
# File 'app/models/user.rb', line 345

def ignores_notification?(notification)
  ignored_notifications.include? notification.subject
end

#import_from_resource_h!(json) ⇒ Object



142
143
144
# File 'app/models/user.rb', line 142

def import_from_resource_h!(json)
  update! self.class.slice_resource_h json
end

#incognito?Boolean

Returns:

  • (Boolean)


260
261
262
# File 'app/models/user.rb', line 260

def incognito?
  false
end

#interpolationsObject



176
177
178
179
180
181
182
# File 'app/models/user.rb', line 176

def interpolations
  {
      'user_email' => email,
      'user_first_name' => first_name,
      'user_last_name' => last_name
  }
end

#last_lessonObject



65
66
67
# File 'app/models/user.rb', line 65

def last_lesson
  last_guide.try(:lesson)
end

#messages_in_organization(organization = Organization.current) ⇒ Object



69
70
71
# File 'app/models/user.rb', line 69

def messages_in_organization(organization = Organization.current)
  direct_messages.where('assignments.organization': organization)
end

#name_initialsObject



232
233
234
# File 'app/models/user.rb', line 232

def name_initials
  name.split.map(&:first).map(&:capitalize).join(' ')
end

#never_submitted?Boolean

Returns:

  • (Boolean)


105
106
107
# File 'app/models/user.rb', line 105

def 
  last_submission_date.nil?
end

#next_exercise_at(guide) ⇒ Object



252
253
254
# File 'app/models/user.rb', line 252

def next_exercise_at(guide)
  guide.pending_exercises(self).order('exercises.number asc').first
end

#notify_permissions_changed!Object



290
291
292
293
294
295
296
297
# File 'app/models/user.rb', line 290

def notify_permissions_changed!
  return if permissions_before_last_save == permissions
  Mumukit::Nuntius.notify! 'user-permissions-changed', user: {
    uid: uid,
    old_permissions: permissions_before_last_save.as_json,
    new_permissions: permissions.as_json
  }
end

#notify_via_email!(notification) ⇒ Object



341
342
343
# File 'app/models/user.rb', line 341

def notify_via_email!(notification)
  UserMailer.notification(notification).post! unless ignores_notification? notification
end

#passed_assignmentsObject



93
94
95
# File 'app/models/user.rb', line 93

def passed_assignments
  assignments.where(status: Mumuki::Domain::Status::Submission::Passed.to_i)
end

#passed_submissions_countObject



81
82
83
# File 'app/models/user.rb', line 81

def passed_submissions_count
  passed_assignments.count
end

#passed_submissions_count_in(organization) ⇒ Object



73
74
75
# File 'app/models/user.rb', line 73

def passed_submissions_count_in(organization)
  assignments.where(top_submission_status: Mumuki::Domain::Status::Submission::Passed.to_i, organization: organization).count
end

#pending_siblings_at(content) ⇒ Object



248
249
250
# File 'app/models/user.rb', line 248

def pending_siblings_at(content)
  content.pending_siblings_for(self)
end

#placeholder_image_urlObject



268
269
270
# File 'app/models/user.rb', line 268

def placeholder_image_url
  PLACEHOLDER_IMAGE_URL
end

#profile_pictureObject



192
193
194
# File 'app/models/user.rb', line 192

def profile_picture
  custom_profile_picture || placeholder_image_url
end

#progress_at(content, organization) ⇒ Object



240
241
242
# File 'app/models/user.rb', line 240

def progress_at(content, organization)
  Indicator.find_or_initialize_by(user: self, organization: organization, content: content)
end

#restore_organization_progress!(organization) ⇒ Object



116
117
118
119
120
# File 'app/models/user.rb', line 116

def restore_organization_progress!(organization)
  assignments_in_organization(organization).each do |assignment|
    assignment.tap(&:parent).save!
  end
end

#run_submission!(submission, assignment, evaluation) ⇒ Object



256
257
258
# File 'app/models/user.rb', line 256

def run_submission!(submission, assignment, evaluation)
  submission.run! assignment, evaluation
end

#save_and_notify!Object



303
304
305
306
307
# File 'app/models/user.rb', line 303

def save_and_notify!
  save!
  notify_permissions_changed!
  self
end

#solved_any_exercises?(organization = Organization.current) ⇒ Boolean

Returns:

  • (Boolean)


299
300
301
# File 'app/models/user.rb', line 299

def solved_any_exercises?(organization = Organization.current)
  Assignment.exists?(organization: organization, submitter: self)
end

#solved_exercises_countObject



89
90
91
# File 'app/models/user.rb', line 89

def solved_exercises_count
  solved_exercises.count
end

#submissions_countObject



77
78
79
# File 'app/models/user.rb', line 77

def submissions_count
  assignments.pluck(:submissions_count).sum
end

#submitted_exercises_countObject



85
86
87
# File 'app/models/user.rb', line 85

def 
  .count
end

#to_resource_hObject



146
147
148
# File 'app/models/user.rb', line 146

def to_resource_h
  super.merge(image_url: profile_picture)
end

#to_sObject



101
102
103
# File 'app/models/user.rb', line 101

def to_s
  "#{id}:#{name}:#{uid}"
end

#transfer_progress_to!(another) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/user.rb', line 130

def transfer_progress_to!(another)
  transaction do
    assignments.update_all(submitter_id: another.id)
    if another. || last_submission_date.try { |it| it > another.last_submission_date }
      another.update! last_submission_date: last_submission_date,
                      last_exercise: last_exercise,
                      last_organization: last_organization
    end
  end
  reload
end

#trusted_as_discusser_in?(organization) ⇒ Boolean

Returns:

  • (Boolean)


220
221
222
# File 'app/models/user.rb', line 220

def trusted_as_discusser_in?(organization)
  trusted_for_forum? || !organization.forum_only_for_trusted?
end

#unsubscribe_from_reminders!Object



161
162
163
# File 'app/models/user.rb', line 161

def unsubscribe_from_reminders!
  update! accepts_reminders: false
end

#verify_name!(force: false) ⇒ Object



150
151
152
153
154
155
156
157
158
159
# File 'app/models/user.rb', line 150

def verify_name!(force: false)
  if force
    self.verified_first_name = first_name
    self.verified_last_name = last_name
  else
    self.verified_first_name ||= first_name
    self.verified_last_name ||= last_name
  end
  save!
end

#visit!(organization) ⇒ Object



97
98
99
# File 'app/models/user.rb', line 97

def visit!(organization)
  update!(last_organization: organization) if organization != last_organization
end