mumuki-domain 9.8.0 → 9.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/application_record.rb +1 -1
  3. data/app/models/assignment.rb +8 -13
  4. data/app/models/concerns/onomastic.rb +37 -0
  5. data/app/models/concerns/with_discussion_creation/subscription.rb +1 -1
  6. data/app/models/concerns/with_messages.rb +1 -1
  7. data/app/models/concerns/with_reminders.rb +1 -1
  8. data/app/models/concerns/with_responsible_moderator.rb +1 -1
  9. data/app/models/concerns/with_soft_deletion.rb +1 -1
  10. data/app/models/concerns/with_terms_acceptance.rb +1 -2
  11. data/app/models/concerns/with_timed_enablement.rb +1 -1
  12. data/app/models/course.rb +2 -2
  13. data/app/models/discussion.rb +12 -6
  14. data/app/models/exam.rb +3 -3
  15. data/app/models/exam_authorization.rb +1 -1
  16. data/app/models/exam_authorization_request.rb +1 -1
  17. data/app/models/exam_registration.rb +6 -2
  18. data/app/models/exam_registration/authorization_criterion.rb +7 -3
  19. data/app/models/exercise.rb +0 -8
  20. data/app/models/exercise/playground.rb +2 -1
  21. data/app/models/exercise/problem.rb +1 -1
  22. data/app/models/message.rb +47 -16
  23. data/app/models/notification.rb +13 -1
  24. data/app/models/organization.rb +5 -1
  25. data/app/models/user.rb +11 -14
  26. data/app/models/user_stats.rb +1 -1
  27. data/db/migrate/20210707143002_add_assignment_id_to_message.rb +5 -0
  28. data/db/migrate/20210719145706_add_new_fields_to_notifications.rb +8 -0
  29. data/db/migrate/20210803175124_add_ignored_notifications_to_users.rb +5 -0
  30. data/lib/mumuki/domain/factories/exercise_factory.rb +9 -1
  31. data/lib/mumuki/domain/helpers/user.rb +0 -6
  32. data/lib/mumuki/domain/organization/profile.rb +1 -1
  33. data/lib/mumuki/domain/organization/settings.rb +2 -2
  34. data/lib/mumuki/domain/submission/persistent_submission.rb +1 -1
  35. data/lib/mumuki/domain/submission/query.rb +1 -1
  36. data/lib/mumuki/domain/submission/question.rb +0 -1
  37. data/lib/mumuki/domain/submission/try.rb +1 -0
  38. data/lib/mumuki/domain/version.rb +1 -1
  39. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 801f11c820f31c39323edff06d235e0c48804b1454f5dbd118363a34f153808f
4
- data.tar.gz: daaa6a61637d5d52412fee38276de7c2ebad10cb696f28b912cbe4ef3ca49247
3
+ metadata.gz: 641d60f1b417f2fa2a9d05fbdd0357b3415a42a222b8a38ab989c63baf5b064b
4
+ data.tar.gz: db353f1f369e0ae15f0349c2448d476e302921ef36479236408d74bada703f29
5
5
  SHA512:
6
- metadata.gz: 4b0370b955ddf9ae29c02dde36613b1bde7fcd3c3711e55f20744bcdcd283e711f461ad6ab2766b0b560fa7e845a401a2cf0172d4a04d7223eb93671ea85e95f
7
- data.tar.gz: 1ea134ed3fe1ac847c524eff4479463f7a75c6d96ef2ecdcb6ef2c62162c38ab0effaae3e76da02c57666418747fd01753c3b369f5a0dae1058b794dac22c067
6
+ metadata.gz: b8d1d6c40137d84dd7bf3d4c12c406ce88914efb9cce0a231a4ec1758e9b30ac61c881d940c0890a58dfaec3f69eefad432c67de3c88bef11950be1953f71ded
7
+ data.tar.gz: 6e784be9dc46359358a831aae422ebadca1840cc9516d664aaa2d16b3100350dd8887a6dba2b1fa064a697b7a2443848cba238f5692caec72113c45a01ffd035
@@ -152,7 +152,7 @@ class ApplicationRecord < ActiveRecord::Base
152
152
  def self.active_between(start_date_field, end_date_field, **options)
153
153
  define_singleton_method(:active) do |actually_filter=true|
154
154
  if actually_filter
155
- self.where("(#{start_date_field} IS NULL OR #{start_date_field} < :now) AND (#{end_date_field} IS NULL OR #{end_date_field} > :now)", now: Time.now)
155
+ self.where("(#{start_date_field} IS NULL OR #{start_date_field} < :now) AND (#{end_date_field} IS NULL OR #{end_date_field} > :now)", now: Time.current)
156
156
  else
157
157
  all
158
158
  end
@@ -7,11 +7,7 @@ class Assignment < Progress
7
7
 
8
8
  belongs_to :exercise
9
9
  has_one :guide, through: :exercise
10
- has_many :messages,
11
- -> { where.not(submission_id: nil).order(date: :desc) },
12
- foreign_key: :submission_id,
13
- primary_key: :submission_id,
14
- dependent: :destroy
10
+ has_many :messages, -> { order(date: :desc) }, dependent: :destroy
15
11
 
16
12
  belongs_to :organization
17
13
  belongs_to :submitter, class_name: 'User'
@@ -28,13 +24,13 @@ class Assignment < Progress
28
24
  alias_attribute :status, :submission_status
29
25
  alias_attribute :attempts_count, :attemps_count
30
26
 
31
- scope :by_exercise_ids, -> (exercise_ids) {
27
+ scope :by_exercise_ids, -> (exercise_ids) do
32
28
  where(exercise_id: exercise_ids) if exercise_ids
33
- }
29
+ end
34
30
 
35
- scope :by_usernames, -> (usernames) {
31
+ scope :by_usernames, -> (usernames) do
36
32
  joins(:submitter).where('users.name' => usernames) if usernames
37
- }
33
+ end
38
34
 
39
35
  defaults do
40
36
  self.query_results = []
@@ -86,11 +82,10 @@ class Assignment < Progress
86
82
  end
87
83
  end
88
84
 
89
- def persist_submission!(submission)
85
+ def save_submission!(submission)
90
86
  transaction do
91
- messages.destroy_all if submission_id.present?
92
87
  update! submission_id: submission.id
93
- update! submitted_at: DateTime.current
88
+ update! submitted_at: Time.current
94
89
  update_submissions_count!
95
90
  update_last_submission!
96
91
  end
@@ -286,6 +281,6 @@ class Assignment < Progress
286
281
  end
287
282
 
288
283
  def update_last_submission!
289
- submitter.update!(last_submission_date: DateTime.current, last_exercise: exercise)
284
+ submitter.update!(last_submission_date: Time.current, last_exercise: exercise)
290
285
  end
291
286
  end
@@ -0,0 +1,37 @@
1
+ module Onomastic
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ alias_method :name, :full_name
6
+ end
7
+
8
+ def formal_first_name
9
+ verified_first_name.presence || first_name
10
+ end
11
+
12
+ def formal_last_name
13
+ verified_last_name.presence || last_name
14
+ end
15
+
16
+ def has_verified_full_name?
17
+ verified_first_name? && verified_last_name?
18
+ end
19
+
20
+ def formal_full_name
21
+ join_names formal_first_name, formal_last_name
22
+ end
23
+
24
+ def full_name
25
+ join_names first_name, last_name
26
+ end
27
+
28
+ def verified_full_name
29
+ join_names verified_first_name, verified_last_name
30
+ end
31
+
32
+ private
33
+
34
+ def join_names(first, last)
35
+ "#{first} #{last}".strip
36
+ end
37
+ end
@@ -8,7 +8,7 @@ module WithDiscussionCreation::Subscription
8
8
  end
9
9
 
10
10
  def subscriptions_in_organization
11
- subscriptions.joins(:discussion).where(discussion: discussions_in_organization)
11
+ subscriptions.where(discussion: Organization.current.discussions)
12
12
  end
13
13
 
14
14
  def subscribed_to?(discussion)
@@ -9,7 +9,7 @@ module WithMessages
9
9
  end
10
10
 
11
11
  def build_message(body)
12
- messages.build({date: DateTime.current}.merge(body))
12
+ messages.build({date: Time.current, submission_id: submission_id}.merge(body))
13
13
  end
14
14
 
15
15
  def has_messages?
@@ -11,7 +11,7 @@ module WithReminders
11
11
 
12
12
  def remind!
13
13
  build_reminder.deliver_now
14
- update! last_reminded_date: Time.now
14
+ update! last_reminded_date: Time.current
15
15
  end
16
16
 
17
17
  def should_remind?
@@ -34,7 +34,7 @@ module WithResponsibleModerator
34
34
  private
35
35
 
36
36
  def responsible!(moderator)
37
- update! responsible_moderator_at: Time.now, responsible_moderator_by: moderator
37
+ update! responsible_moderator_at: Time.current, responsible_moderator_by: moderator
38
38
  end
39
39
 
40
40
  def no_responsible!
@@ -7,7 +7,7 @@ module WithSoftDeletion
7
7
  end
8
8
 
9
9
  def soft_delete!(motive, deleter)
10
- update! deletion_motive: motive, deleted_by: deleter, deleted_at: Time.now
10
+ update! deletion_motive: motive, deleted_by: deleter, deleted_at: Time.current
11
11
  end
12
12
 
13
13
  def deleted?
@@ -58,8 +58,7 @@ module WithTermsAcceptance
58
58
  end
59
59
 
60
60
  def accept_terms!(terms)
61
- update! unaccepted_terms_scopes_in(terms).to_h { |scope| [term_acceptance_field_for(scope), Time.now] }
61
+ update! unaccepted_terms_scopes_in(terms).to_h { |scope| [term_acceptance_field_for(scope), Time.current] }
62
62
  end
63
63
 
64
64
  end
65
-
@@ -2,7 +2,7 @@ module WithTimedEnablement
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  def enabled?
5
- enabled_range.cover? DateTime.current
5
+ enabled_range.cover? Time.current
6
6
  end
7
7
 
8
8
  def enabled_range
data/app/models/course.rb CHANGED
@@ -14,7 +14,7 @@ class Course < ApplicationRecord
14
14
  resource_fields :slug, :shifts, :code, :days, :period, :description, :period_start, :period_end
15
15
 
16
16
  def current_invitation
17
- invitations.where('expiration_date > ?', Time.now).first
17
+ invitations.where('expiration_date > ?', Time.current).first
18
18
  end
19
19
 
20
20
  def import_from_resource_h!(resource_h)
@@ -49,7 +49,7 @@ class Course < ApplicationRecord
49
49
  period =~ /^(\d{4})?/
50
50
  year = $1.to_i
51
51
 
52
- return nil unless year.between? 2014, (DateTime.now.year + 1)
52
+ return nil unless year.between? 2014, (Time.current.year + 1)
53
53
 
54
54
  self.period_start = DateTime.new(year).beginning_of_year
55
55
  self.period_end = DateTime.new(year).end_of_year
@@ -21,6 +21,7 @@ class Discussion < ApplicationRecord
21
21
  scope :no_responsible_moderator, -> { where('responsible_moderator_at < ?', Time.now - MODERATOR_MAX_RESPONSIBLE_TIME)
22
22
  .or(where(responsible_moderator_at: nil)) }
23
23
  scope :pending_review, -> { where(status: :pending_review) }
24
+ scope :unread_first, -> { includes(:subscriptions).reorder('subscriptions.read', created_at: :desc) }
24
25
 
25
26
  after_create :subscribe_initiator!
26
27
 
@@ -42,7 +43,7 @@ class Discussion < ApplicationRecord
42
43
  end
43
44
 
44
45
  def visible_messages
45
- messages.where.not(deletion_motive: :self_deleted).or(messages.where(deletion_motive: nil))
46
+ messages.visible
46
47
  end
47
48
 
48
49
  def try_solve!
@@ -54,7 +55,7 @@ class Discussion < ApplicationRecord
54
55
  def navigable_content_in(_)
55
56
  nil
56
57
  end
57
-
58
+
58
59
  def target
59
60
  self
60
61
  end
@@ -95,15 +96,15 @@ class Discussion < ApplicationRecord
95
96
  upvotes.find_by(user: user)
96
97
  end
97
98
 
98
- def unread_subscriptions(user)
99
- subscriptions.where.not(user: user).map(&:unread!)
99
+ def mark_subscriptions_as_unread!(user)
100
+ subscriptions.where.not(user: user).each(&:unread!)
100
101
  end
101
102
 
102
103
  def submit_message!(message, user)
103
104
  message.merge!(sender: user.uid)
104
105
  messages.create(message)
105
106
  user.subscribe_to! self
106
- unread_subscriptions(user)
107
+ mark_subscriptions_as_unread!(user)
107
108
  no_responsible! if responsible?(user)
108
109
  end
109
110
 
@@ -128,7 +129,7 @@ class Discussion < ApplicationRecord
128
129
  if reachable_status_for?(user, status)
129
130
  update! status: status,
130
131
  status_updated_by: user,
131
- status_updated_at: Time.now
132
+ status_updated_at: Time.current
132
133
 
133
134
  no_responsible! if responsible?(user)
134
135
  end
@@ -182,6 +183,11 @@ class Discussion < ApplicationRecord
182
183
  klazz.constantize.find(debatable_id)
183
184
  end
184
185
 
186
+ # TODO remove this once discussions generate notifications
187
+ def subject
188
+ 'discussion'
189
+ end
190
+
185
191
  private
186
192
 
187
193
  def messages_by_updated_at(direction = :desc)
data/app/models/exam.rb CHANGED
@@ -32,7 +32,7 @@ class Exam < ApplicationRecord
32
32
  end
33
33
 
34
34
  def enabled_for?(user)
35
- enabled_range_for(user).cover? DateTime.current
35
+ enabled_range_for(user).cover? Time.current
36
36
  end
37
37
 
38
38
  def in_progress_for?(user)
@@ -179,8 +179,8 @@ class Exam < ApplicationRecord
179
179
  exam[:organization_id] = Organization.current.id
180
180
  exam[:course_id] = Course.locate!(exam[:course].to_s).id
181
181
  exam[:users] = User.where(uid: exam[:uids])
182
- exam[:start_time] = exam[:start_time].to_time
183
- exam[:end_time] = exam[:end_time].to_time
182
+ exam[:start_time] = exam[:start_time].in_time_zone
183
+ exam[:end_time] = exam[:end_time].in_time_zone
184
184
  exam[:classroom_id] = exam[:eid] if exam[:eid].present?
185
185
  end
186
186
 
@@ -4,7 +4,7 @@ class ExamAuthorization < ApplicationRecord
4
4
  belongs_to :exam
5
5
 
6
6
  def start!
7
- update!(started: true, started_at: Time.now) unless started?
7
+ update!(started: true, started_at: Time.current) unless started?
8
8
  end
9
9
 
10
10
  end
@@ -32,6 +32,6 @@ class ExamAuthorizationRequest < ApplicationRecord
32
32
  private
33
33
 
34
34
  def notify_user!
35
- Notification.create! organization: organization, user: user, target: self if saved_change_to_status?
35
+ Notification.create_and_notify_via_email! organization: organization, user: user, target: self, subject: 'exam_authorization_request_updated' if saved_change_to_status?
36
36
  end
37
37
  end
@@ -17,7 +17,7 @@ class ExamRegistration < ApplicationRecord
17
17
 
18
18
  alias_attribute :name, :description
19
19
 
20
- scope :should_process, -> { where(processed: false).where(arel_table[:end_time].lt(Time.now)) }
20
+ scope :should_process, -> { where(processed: false).where(arel_table[:end_time].lt(Time.current)) }
21
21
 
22
22
  def authorization_criterion
23
23
  @authorization_criterion ||= ExamRegistration::AuthorizationCriterion.parse(authorization_criterion_type, authorization_criterion_value)
@@ -73,9 +73,13 @@ class ExamRegistration < ApplicationRecord
73
73
  registrees.include? user
74
74
  end
75
75
 
76
+ def meets_criterion?(user)
77
+ authorization_criterion.meets_criterion?(user, organization)
78
+ end
79
+
76
80
  private
77
81
 
78
82
  def notify_registree!(registree)
79
- Notification.create! organization: organization, user: registree, target: self
83
+ Notification.create_and_notify_via_email! organization: organization, user: registree, subject: :exam_registration, target: self
80
84
  end
81
85
  end
@@ -34,6 +34,10 @@ class ExamRegistration::AuthorizationCriterion
34
34
  rescue
35
35
  raise "Invalid criterion type #{type}"
36
36
  end
37
+
38
+ def meets_authorization_criteria?(authorization_request)
39
+ meets_criterion? authorization_request.user, authorization_request.organization
40
+ end
37
41
  end
38
42
 
39
43
  class ExamRegistration::AuthorizationCriterion::None < ExamRegistration::AuthorizationCriterion
@@ -45,7 +49,7 @@ class ExamRegistration::AuthorizationCriterion::None < ExamRegistration::Authori
45
49
  !value
46
50
  end
47
51
 
48
- def meets_authorization_criteria?(_authorization_request)
52
+ def meets_criterion?(_user, _organization)
49
53
  true
50
54
  end
51
55
  end
@@ -55,7 +59,7 @@ class ExamRegistration::AuthorizationCriterion::PassedExercises < ExamRegistrati
55
59
  value.positive?
56
60
  end
57
61
 
58
- def meets_authorization_criteria?(authorization_request)
59
- authorization_request.user.passed_submissions_count_in(authorization_request.organization) >= value
62
+ def meets_criterion?(user, organization)
63
+ user.passed_submissions_count_in(organization) >= value
60
64
  end
61
65
  end
@@ -168,14 +168,6 @@ class Exercise < ApplicationRecord
168
168
  Exercise.find(id)
169
169
  end
170
170
 
171
- def messages_path_for(user)
172
- "api/guides/#{guide.slug}/#{bibliotheca_id}/student/#{URI.escape user.uid}/messages?language=#{language}"
173
- end
174
-
175
- def messages_url_for(user)
176
- Mumukit::Platform.classroom_api.organic_url_for(Organization.current, messages_path_for(user))
177
- end
178
-
179
171
  def description_context
180
172
  splitted_description.first.markdownified
181
173
  end
@@ -3,8 +3,9 @@ class Playground < QueriableChallenge
3
3
 
4
4
  name_model_as Exercise
5
5
 
6
- def setup_query_assignment!(assignment)
6
+ def save_query_submission!(assignment, submission)
7
7
  assignment.running!
8
+ assignment.save_submission! submission
8
9
  end
9
10
 
10
11
  def save_query_results!(assignment)
@@ -9,7 +9,7 @@ class Problem < QueriableChallenge
9
9
 
10
10
  name_model_as Exercise
11
11
 
12
- def setup_query_assignment!(assignment)
12
+ def save_query_submission!(_assignment, _submission)
13
13
  end
14
14
 
15
15
  def save_query_results!(assignment)
@@ -2,18 +2,48 @@ class Message < ApplicationRecord
2
2
  include WithSoftDeletion
3
3
 
4
4
  belongs_to :discussion, optional: true
5
- belongs_to :assignment, foreign_key: :submission_id, primary_key: :submission_id, optional: true
5
+ belongs_to :assignment, optional: true
6
6
  belongs_to :approved_by, class_name: 'User', optional: true
7
7
 
8
8
  has_one :exercise, through: :assignment
9
9
 
10
10
  validates_presence_of :content, :sender
11
- validates_presence_of :submission_id, :unless => :discussion_id?
11
+ validate :ensure_contextualized
12
12
 
13
13
  after_save :update_counters_cache!
14
14
 
15
15
  markdown_on :content
16
16
 
17
+ # Visible messages are those that can be publicly seen
18
+ # in forums. non-direct messages are never visible.
19
+ scope :visible, -> () do
20
+ where.not(deletion_motive: :self_deleted)
21
+ .or(where(deletion_motive: nil))
22
+ .where(assignment_id: nil)
23
+ end
24
+
25
+ def contextualization
26
+ direct? ? assignment : discussion
27
+ end
28
+
29
+ def contextualized?
30
+ assignment_id.present? ^ discussion_id.present?
31
+ end
32
+
33
+ # Whether this message is stale, that is, it
34
+ # targets a submission that is not the latest one.
35
+ #
36
+ # Only direct messages may become stale.
37
+ def stale?
38
+ direct? && assignment.submission_id != submission_id
39
+ end
40
+
41
+ # Whether this message is direct, that is, whether it comes from rise-hand feature.
42
+ # Forum messages are non-direct.
43
+ def direct?
44
+ submission_id.present?
45
+ end
46
+
17
47
  def notify!
18
48
  Mumukit::Nuntius.notify! 'student-messages', to_resource_h unless Organization.silenced?
19
49
  end
@@ -85,33 +115,34 @@ class Message < ApplicationRecord
85
115
  self
86
116
  end
87
117
 
88
- def self.parse_json(json)
89
- message = json.delete 'message'
90
- json
91
- .except('uid', 'exercise_id')
92
- .merge(message)
93
- end
94
-
95
118
  def self.read_all!
96
119
  update_all read: true
97
120
  end
98
121
 
99
- def self.import_from_resource_h!(json)
100
- message_data = parse_json json
101
- Organization.find_by!(name: message_data.delete('organization')).switch!
102
-
103
- if message_data['submission_id'].present?
104
- Assignment.find_by(submission_id: message_data.delete('submission_id'))&.receive_answer! message_data
122
+ def self.import_from_resource_h!(resource_h)
123
+ if resource_h['submission_id'].present?
124
+ assignment = Assignment.find_by(submission_id: resource_h['submission_id'])
125
+ assignment&.receive_answer! sender: resource_h['message']['sender'],
126
+ content: resource_h['message']['content']
105
127
  end
106
128
  end
107
129
 
130
+ # TODO remove this once messages generate notifications
131
+ def subject
132
+ 'message'
133
+ end
134
+
108
135
  private
109
136
 
110
137
  def approve!(user)
111
- update! approved: true, approved_at: Time.now, approved_by: user
138
+ update! approved: true, approved_at: Time.current, approved_by: user
112
139
  end
113
140
 
114
141
  def disapprove!
115
142
  update! approved: false, approved_at: nil, approved_by: nil
116
143
  end
144
+
145
+ def ensure_contextualized
146
+ errors.add(:base, :not_properly_contextualized) unless contextualized?
147
+ end
117
148
  end
@@ -1,8 +1,12 @@
1
1
  class Notification < ApplicationRecord
2
2
  belongs_to :user
3
3
  belongs_to :organization
4
- belongs_to :target, polymorphic: true
4
+ belongs_to :target, polymorphic: true, optional: true
5
5
 
6
+ enum subject: %i(
7
+ custom
8
+ exam_authorization_request_updated
9
+ exam_registration)
6
10
 
7
11
  scope :notified_users_ids_for, ->(target, organization=Organization.current) do
8
12
  where(target: target, organization: organization).pluck(:user_id)
@@ -11,4 +15,12 @@ class Notification < ApplicationRecord
11
15
  def mark_as_read!
12
16
  update read: true
13
17
  end
18
+
19
+ def self.create_and_notify_via_email!(args)
20
+ create!(args).tap(&:notify_via_email!)
21
+ end
22
+
23
+ def notify_via_email!
24
+ user.notify_via_email! self
25
+ end
14
26
  end
@@ -23,7 +23,7 @@ class Organization < ApplicationRecord
23
23
 
24
24
  has_many :certificate_programs
25
25
 
26
- validates_presence_of :contact_email, :locale
26
+ validates_presence_of :contact_email, :locale, :time_zone
27
27
  validates_presence_of :welcome_email_template, if: :greet_new_users?
28
28
  validates :name, uniqueness: true,
29
29
  presence: true,
@@ -148,6 +148,10 @@ class Organization < ApplicationRecord
148
148
  [default_date, in_preparation_until&.to_date].compact.max
149
149
  end
150
150
 
151
+ def discussions
152
+ book.discussions_in_organization(self)
153
+ end
154
+
151
155
  # ==============
152
156
  # Display fields
153
157
  # ==============
data/app/models/user.rb CHANGED
@@ -9,10 +9,11 @@ class User < ApplicationRecord
9
9
  Disabling,
10
10
  WithTermsAcceptance,
11
11
  WithPreferences,
12
+ Onomastic,
12
13
  Mumuki::Domain::Helpers::User
13
14
 
14
15
  serialize :permissions, Mumukit::Auth::Permissions
15
-
16
+ serialize :ignored_notifications, Array
16
17
 
17
18
  has_many :notifications
18
19
  has_many :assignments, foreign_key: :submitter_id
@@ -251,7 +252,7 @@ class User < ApplicationRecord
251
252
 
252
253
  def age
253
254
  if birthdate.present?
254
- @age ||= Time.now.round_years_since(birthdate.to_time)
255
+ @age ||= Time.current.round_years_since(birthdate.in_time_zone)
255
256
  end
256
257
  end
257
258
 
@@ -288,18 +289,6 @@ class User < ApplicationRecord
288
289
  end
289
290
  end
290
291
 
291
- def formal_first_name
292
- verified_first_name.presence || first_name
293
- end
294
-
295
- def formal_last_name
296
- verified_last_name.presence || last_name
297
- end
298
-
299
- def formal_full_name
300
- "#{formal_first_name} #{formal_last_name}"
301
- end
302
-
303
292
  def certificates_in_organization(organization = Organization.current)
304
293
  certificates.where certificate_program: CertificateProgram.where(organization: organization)
305
294
  end
@@ -326,6 +315,14 @@ class User < ApplicationRecord
326
315
  user_stats.where(location).delete_all
327
316
  end
328
317
 
318
+ def notify_via_email!(notification)
319
+ UserMailer.notification(notification).deliver_later unless ignores_notification? notification
320
+ end
321
+
322
+ def ignores_notification?(notification)
323
+ ignored_notifications.include? notification.subject
324
+ end
325
+
329
326
  private
330
327
 
331
328
  def welcome_to_new_organizations!
@@ -11,7 +11,7 @@ class UserStats < ApplicationRecord
11
11
  end
12
12
 
13
13
  def activity(date_range = nil)
14
- date_filter = { updated_at: date_range }.compact
14
+ date_filter = { submitted_at: date_range }.compact
15
15
  {
16
16
  exercises: {
17
17
  solved_count: organization_exercises
@@ -0,0 +1,5 @@
1
+ class AddAssignmentIdToMessage < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :messages, :assignment, index: true
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class AddNewFieldsToNotifications < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :notifications, :subject, :integer
4
+ add_column :notifications, :custom_title, :text
5
+ add_column :notifications, :custom_content_plain_text, :text
6
+ add_column :notifications, :custom_content_html, :text
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddIgnoredNotificationsToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :ignored_notifications, :text
4
+ end
5
+ end
@@ -28,12 +28,20 @@ FactoryBot.define do
28
28
  end
29
29
 
30
30
  factory :exercise_base do
31
+ transient do
32
+ indexed { false }
33
+ end
34
+
31
35
  language { guide ? guide.language : create(:language) }
32
36
  sequence(:bibliotheca_id) { |n| n }
33
37
  sequence(:number) { |n| n }
34
38
 
35
39
  locale { :en }
36
40
  guide
41
+
42
+ after(:build) do |exercise, evaluator|
43
+ exercise.guide = create(:indexed_guide) if evaluator.indexed
44
+ end
37
45
  end
38
46
 
39
47
  factory :challenge, parent: :exercise_base do
@@ -73,7 +81,7 @@ FactoryBot.define do
73
81
  factory :exercise, parent: :problem
74
82
 
75
83
  factory :indexed_exercise, parent: :exercise do
76
- guide { create(:indexed_guide) }
84
+ indexed { true }
77
85
  end
78
86
 
79
87
  factory :x_equal_5_exercise, parent: :exercise do
@@ -61,12 +61,6 @@ module Mumuki::Domain::Helpers::User
61
61
 
62
62
  ## Profile
63
63
 
64
- def full_name
65
- "#{first_name} #{last_name}".strip
66
- end
67
-
68
- alias_method :name, :full_name
69
-
70
64
  def profile_completed?
71
65
  self.class.profile_fields.map { |it| self[it] }.all? &:present?
72
66
  end
@@ -41,7 +41,7 @@ class Mumuki::Domain::Organization::Profile < Mumukit::Platform::Model
41
41
  end
42
42
 
43
43
  def time_zone
44
- @time_zone || 'Buenos Aires'
44
+ @time_zone
45
45
  end
46
46
 
47
47
  end
@@ -32,11 +32,11 @@ class Mumuki::Domain::Organization::Settings < Mumukit::Platform::Model
32
32
  end
33
33
 
34
34
  def disabled_from=(disabled_from)
35
- @disabled_from = disabled_from&.to_time
35
+ @disabled_from = disabled_from&.in_time_zone
36
36
  end
37
37
 
38
38
  def in_preparation_until=(in_preparation_until)
39
- @in_preparation_until = in_preparation_until&.to_time
39
+ @in_preparation_until = in_preparation_until&.in_time_zone
40
40
  end
41
41
 
42
42
  def disabled?
@@ -2,6 +2,6 @@ class Mumuki::Domain::Submission::PersistentSubmission < Mumuki::Domain::Submiss
2
2
  def save_submission!(assignment)
3
3
  assignment.running!
4
4
  super
5
- assignment.persist_submission! self
5
+ assignment.save_submission! self
6
6
  end
7
7
  end
@@ -6,7 +6,7 @@ class Mumuki::Domain::Submission::Query < Mumuki::Domain::Submission::ConsoleSub
6
6
  end
7
7
 
8
8
  def save_submission!(assignment)
9
- assignment.exercise.setup_query_assignment!(assignment)
9
+ assignment.exercise.save_query_submission!(assignment, self)
10
10
  super
11
11
  end
12
12
 
@@ -5,7 +5,6 @@ class Mumuki::Domain::Submission::Question < Mumuki::Domain::Submission::Base
5
5
  end
6
6
 
7
7
  def save_submission!(assignment)
8
- assignment.persist_submission! self
9
8
  end
10
9
 
11
10
  def try_evaluate!(*)
@@ -8,6 +8,7 @@ class Mumuki::Domain::Submission::Try < Mumuki::Domain::Submission::ConsoleSubmi
8
8
  def save_submission!(assignment)
9
9
  assignment.query_results = [] if cookie.blank?
10
10
  assignment.queries = cookie.insert_last(query)
11
+ assignment.save_submission! self
11
12
  assignment.save!
12
13
  end
13
14
 
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '9.8.0'
3
+ VERSION = '9.11.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mumuki-domain
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.8.0
4
+ version: 9.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franco Leonardo Bulgarelli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-01 00:00:00.000000000 Z
11
+ date: 2021-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -264,6 +264,7 @@ files:
264
264
  - app/models/concerns/navigation/parent_navigation.rb
265
265
  - app/models/concerns/navigation/siblings_navigation.rb
266
266
  - app/models/concerns/navigation/terminal_navigation.rb
267
+ - app/models/concerns/onomastic.rb
267
268
  - app/models/concerns/submittable/confirmable.rb
268
269
  - app/models/concerns/submittable/queriable.rb
269
270
  - app/models/concerns/submittable/questionable.rb
@@ -670,6 +671,9 @@ files:
670
671
  - db/migrate/20210330175706_create_exam_registration_user_join_table.rb
671
672
  - db/migrate/20210512200453_add_processed_flag_to_exam_registration.rb
672
673
  - db/migrate/20210518100153_rename_last_moderator_access.rb
674
+ - db/migrate/20210707143002_add_assignment_id_to_message.rb
675
+ - db/migrate/20210719145706_add_new_fields_to_notifications.rb
676
+ - db/migrate/20210803175124_add_ignored_notifications_to_users.rb
673
677
  - lib/mumuki/domain.rb
674
678
  - lib/mumuki/domain/area.rb
675
679
  - lib/mumuki/domain/engine.rb