mumuki-domain 8.1.0 → 8.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -0
  3. data/app/models/assignment.rb +7 -2
  4. data/app/models/chapter.rb +3 -0
  5. data/app/models/concerns/with_expectations.rb +3 -7
  6. data/app/models/concerns/with_randomizations.rb +1 -2
  7. data/app/models/concerns/with_timed_enablement.rb +11 -0
  8. data/app/models/exam.rb +4 -8
  9. data/app/models/exam_authorization_request.rb +19 -0
  10. data/app/models/exam_registration.rb +38 -0
  11. data/app/models/exam_registration/authorization_criterion.rb +61 -0
  12. data/app/models/exercise.rb +8 -5
  13. data/app/models/exercise/challenge.rb +4 -2
  14. data/app/models/exercise/problem.rb +5 -10
  15. data/app/models/guide.rb +5 -1
  16. data/app/models/indicator.rb +35 -0
  17. data/app/models/notification.rb +5 -0
  18. data/app/models/organization.rb +1 -1
  19. data/app/models/progress.rb +35 -0
  20. data/app/models/topic.rb +8 -0
  21. data/app/models/user.rb +6 -1
  22. data/app/models/with_stats.rb +1 -0
  23. data/db/migrate/20201130163114_add_banned_from_forum_to_users.rb +5 -0
  24. data/db/migrate/20210114200545_create_exam_registrations.rb +14 -0
  25. data/db/migrate/20210118180941_create_exam_authorization_request.rb +13 -0
  26. data/db/migrate/20210118194904_create_notification.rb +13 -0
  27. data/db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb +5 -0
  28. data/db/migrate/20210119190204_create_exam_registration_exam_join_table.rb +8 -0
  29. data/lib/mumuki/domain.rb +1 -0
  30. data/lib/mumuki/domain/extensions.rb +2 -0
  31. data/lib/mumuki/domain/extensions/array.rb +2 -4
  32. data/lib/mumuki/domain/extensions/hash.rb +4 -0
  33. data/lib/mumuki/domain/extensions/nil.rb +17 -0
  34. data/lib/mumuki/domain/extensions/string.rb +2 -9
  35. data/lib/mumuki/domain/extensions/symbol.rb +5 -0
  36. data/lib/mumuki/domain/factories.rb +2 -0
  37. data/lib/mumuki/domain/factories/exam_authorization_request_factory.rb +6 -0
  38. data/lib/mumuki/domain/factories/exam_registration_factory.rb +8 -0
  39. data/lib/mumuki/domain/helpers/organization.rb +4 -0
  40. data/lib/mumuki/domain/locales/activerecord/en.yml +1 -1
  41. data/lib/mumuki/domain/locales/activerecord/es-CL.yml +1 -1
  42. data/lib/mumuki/domain/locales/activerecord/es.yml +1 -1
  43. data/lib/mumuki/domain/locales/activerecord/pt.yml +1 -1
  44. data/lib/mumuki/domain/progress_transfer.rb +8 -0
  45. data/lib/mumuki/domain/progress_transfer/base.rb +44 -0
  46. data/lib/mumuki/domain/progress_transfer/copy.rb +9 -0
  47. data/lib/mumuki/domain/progress_transfer/move.rb +14 -0
  48. data/lib/mumuki/domain/status/submission/manual_evaluation_pending.rb +1 -1
  49. data/lib/mumuki/domain/status/submission/submission.rb +4 -0
  50. data/lib/mumuki/domain/version.rb +1 -1
  51. metadata +22 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9075756cb0cd46b955959afe62c837fb11a293cf520f7e7a3e0666c88e9bc014
4
- data.tar.gz: a775162a5319a08a2a2953586bd59efd3cca7ebffddb9c72b236175c5dcbb1a8
3
+ metadata.gz: 05e92b25906e2bdad81e9af37f88d03423ce78e18e60e911883e1fd9933056d4
4
+ data.tar.gz: 3fc2d20e7199cd604ba3462a5755084ed1ed9bbcf4b434d71bd3ed340677d650
5
5
  SHA512:
6
- metadata.gz: '08190f559ca10836c7d2cfd9c98a864ea18d31fb1f13115727ffc9738a7913fe4af136571b0036c1701b166162e61d2d726653a0cb8f3f1711f9677bde815e03'
7
- data.tar.gz: ed6f365ac0c552ccd2f5136fd825052b51da29846a6ea042d27a57e991008d5cfe02265a5450eb2a9b62756ddfb96300c5be1f47dd699b26a8d47b57883c3758
6
+ metadata.gz: 8b75c52aeec6f2403888816619740882acf061c684d09f71fd52094c9cc2b13561203433b13eaca1499a4d7c6f7046e596d3e01d95ac0e02e983bf1107fc2923
7
+ data.tar.gz: 56ed033125bb4eea0d39ba693dca8e142532846159dea3e68329f3b79984ac201a3852ec6019c9428f2696d1e900583c09cd55f8b80c38215fe02ebefd014ed0
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -23,6 +23,8 @@ class Assignment < Progress
23
23
 
24
24
  delegate :completed?, :solved?, to: :submission_status
25
25
 
26
+ delegate :content_available_in?, to: :parent
27
+
26
28
  alias_attribute :status, :submission_status
27
29
  alias_attribute :attempts_count, :attemps_count
28
30
 
@@ -126,7 +128,7 @@ class Assignment < Progress
126
128
  end
127
129
 
128
130
  def content=(content)
129
- unless exercise.reading?
131
+ if exercise.solvable?
130
132
  self.solution = exercise.single_choice? ? exercise.choice_index_for(content) : content
131
133
  end
132
134
  end
@@ -263,6 +265,10 @@ class Assignment < Progress
263
265
 
264
266
  private
265
267
 
268
+ def duplicates_key
269
+ { exercise: exercise, submitter: submitter }
270
+ end
271
+
266
272
  def update_submissions_count!
267
273
  self.class.connection.execute(
268
274
  "update public.exercises
@@ -278,5 +284,4 @@ class Assignment < Progress
278
284
  def update_last_submission!
279
285
  submitter.update!(last_submission_date: DateTime.current, last_exercise: exercise)
280
286
  end
281
-
282
287
  end
@@ -13,6 +13,9 @@ class Chapter < ApplicationRecord
13
13
 
14
14
  has_many :exercises, through: :topic
15
15
 
16
+ delegate :monolesson?, :monolesson, :first_lesson, to: :topic
17
+
18
+ delegate :next_exercise, :stats_for, to: :monolesson, allow_nil: true
16
19
 
17
20
  def used_in?(organization)
18
21
  organization.book == self.book
@@ -18,16 +18,12 @@ module WithExpectations
18
18
  self[:expectations] = expectations.map(&:stringify_keys)
19
19
  end
20
20
 
21
- def own_expectations
21
+ def raw_expectations
22
22
  self[:expectations]
23
23
  end
24
24
 
25
- def own_custom_expectations
26
- self[:custom_expectations]
27
- end
28
-
29
25
  def ensure_expectations_format
30
- errors.add :own_expectations,
31
- :invalid_format unless own_expectations.to_a.all? { |it| Mulang::Expectation.valid? it }
26
+ errors.add :raw_expectations,
27
+ :invalid_format unless raw_expectations.to_a.all? { |it| Mulang::Expectation.valid? it }
32
28
  end
33
29
  end
@@ -35,8 +35,7 @@ module WithRandomizations
35
35
 
36
36
  def randomize_field(selector)
37
37
  define_method(selector) do |*args|
38
- return unless super(*args)
39
- randomizer.randomize!(super(*args), seed)
38
+ super(*args).randomize_with randomizer, seed
40
39
  end
41
40
  end
42
41
  end
@@ -0,0 +1,11 @@
1
+ module WithTimedEnablement
2
+ extend ActiveSupport::Concern
3
+
4
+ def enabled?
5
+ enabled_range.cover? DateTime.current
6
+ end
7
+
8
+ def enabled_range
9
+ start_time..end_time
10
+ end
11
+ end
@@ -3,13 +3,17 @@ class Exam < ApplicationRecord
3
3
  include GuideContainer
4
4
  include FriendlyName
5
5
  include TerminalNavigation
6
+ include WithTimedEnablement
6
7
 
7
8
  belongs_to :organization
8
9
  belongs_to :course
9
10
 
10
11
  has_many :authorizations, class_name: 'ExamAuthorization', dependent: :destroy
12
+ has_many :authorization_requests, class_name: 'ExamAuthorizationRequest', dependent: :destroy
11
13
  has_many :users, through: :authorizations
12
14
 
15
+ has_and_belongs_to_many :exam_registrations
16
+
13
17
  enum passing_criterion_type: [:none, :percentage, :passed_exercises], _prefix: :passing_criterion
14
18
 
15
19
  validates_presence_of :start_time, :end_time
@@ -27,14 +31,6 @@ class Exam < ApplicationRecord
27
31
  organization == self.organization
28
32
  end
29
33
 
30
- def enabled?
31
- enabled_range.cover? DateTime.current
32
- end
33
-
34
- def enabled_range
35
- start_time..end_time
36
- end
37
-
38
34
  def enabled_for?(user)
39
35
  enabled_range_for(user).cover? DateTime.current
40
36
  end
@@ -0,0 +1,19 @@
1
+ class ExamAuthorizationRequest < ApplicationRecord
2
+ belongs_to :exam
3
+ belongs_to :user
4
+ belongs_to :organization
5
+
6
+ enum status: %i(pending approved rejected)
7
+
8
+ after_update :notify_user!
9
+
10
+ def try_authorize!
11
+ exam.authorize! user if approved?
12
+ end
13
+
14
+ private
15
+
16
+ def notify_user!
17
+ Notification.create! organization: organization, user: user, target: self if saved_change_to_status?
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ class ExamRegistration < ApplicationRecord
2
+ include WithTimedEnablement
3
+
4
+ belongs_to :organization
5
+ has_and_belongs_to_many :exams
6
+ has_many :authorization_requests, class_name: 'ExamAuthorizationRequest', through: :exams
7
+
8
+ enum authorization_criterion_type: %i(none passed_exercises), _prefix: :authorization_criterion
9
+
10
+ before_save :ensure_valid_authorization_criterion!
11
+
12
+ delegate :meets_authorization_criteria?, :process_request!, to: :authorization_criterion
13
+
14
+ def authorization_criterion
15
+ @authorization_criterion ||= ExamRegistration::AuthorizationCriterion.parse(authorization_criterion_type, authorization_criterion_value)
16
+ end
17
+
18
+ def ensure_valid_authorization_criterion!
19
+ authorization_criterion.ensure_valid!
20
+ end
21
+
22
+ def start!(users)
23
+ users.each &method(:notify_user!)
24
+ end
25
+
26
+ def process_requests!
27
+ authorization_requests.each do |it|
28
+ process_request! it
29
+ it.try_authorize!
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def notify_user!(user)
36
+ Notification.create! organization: organization, user: user, target: self
37
+ end
38
+ end
@@ -0,0 +1,61 @@
1
+ class ExamRegistration::AuthorizationCriterion
2
+ attr_reader :value
3
+
4
+ def initialize(value)
5
+ @value = value
6
+ end
7
+
8
+ def type
9
+ self.class.name.demodulize.underscore
10
+ end
11
+
12
+ def as_json
13
+ { type: type, value: value }
14
+ end
15
+
16
+ def ensure_valid!
17
+ raise "Invalid criterion value #{value} for #{type}" unless valid?
18
+ end
19
+
20
+ def process_request!(authorization_request)
21
+ authorization_request.update! status: authorization_status_for(authorization_request)
22
+ end
23
+
24
+ def authorization_status_for(authorization_request)
25
+ meets_authorization_criteria?(authorization_request) ? :approved : :rejected
26
+ end
27
+
28
+ def self.parse(type, value)
29
+ parse_criterion_type(type, value)
30
+ end
31
+
32
+ def self.parse_criterion_type(type, value)
33
+ "ExamRegistration::AuthorizationCriterion::#{type.camelize}".constantize.new(value)
34
+ rescue
35
+ raise "Invalid criterion type #{type}"
36
+ end
37
+ end
38
+
39
+ class ExamRegistration::AuthorizationCriterion::None < ExamRegistration::AuthorizationCriterion
40
+ def initialize(_)
41
+ @value = nil
42
+ end
43
+
44
+ def valid?
45
+ !value
46
+ end
47
+
48
+ def meets_authorization_criteria?(_authorization_request)
49
+ true
50
+ end
51
+ end
52
+
53
+ class ExamRegistration::AuthorizationCriterion::PassedExercises < ExamRegistration::AuthorizationCriterion
54
+ def valid?
55
+ value.positive?
56
+ end
57
+
58
+ def meets_authorization_criteria?(authorization_request)
59
+ authorization_request.user.passed_submissions_count_in(authorization_request.organization) >= value
60
+ end
61
+ end
@@ -1,5 +1,5 @@
1
1
  class Exercise < ApplicationRecord
2
- RANDOMIZED_FIELDS = [:default_content, :description, :extra, :hint, :test]
2
+ RANDOMIZED_FIELDS = [:default_content, :description, :extra, :hint, :test, :expectations, :custom_expectations]
3
3
  BASIC_RESOURCE_FIELDS = %i(
4
4
  name layout editor corollary teacher_info manual_evaluation locale
5
5
  choices assistance_rules randomizations tag_list extra_visible goal
@@ -28,6 +28,10 @@ class Exercise < ApplicationRecord
28
28
 
29
29
  defaults { self.submissions_count = 0 }
30
30
 
31
+ def self.default_scope
32
+ where(manual_evaluation: false) if Organization.safe_current&.prevent_manual_evaluation_content
33
+ end
34
+
31
35
  alias_method :progress_for, :assignment_for
32
36
 
33
37
  serialize :choices, Array
@@ -38,6 +42,7 @@ class Exercise < ApplicationRecord
38
42
 
39
43
  randomize(*RANDOMIZED_FIELDS)
40
44
  delegate :timed?, to: :navigable_parent
45
+ delegate :stats_for, to: :guide
41
46
 
42
47
  def console?
43
48
  queriable?
@@ -135,8 +140,6 @@ class Exercise < ApplicationRecord
135
140
  language_resource_h = language.to_embedded_resource_h if language != guide.language
136
141
  as_json(only: BASIC_RESOURCE_FIELDS)
137
142
  .merge(id: bibliotheca_id, language: language_resource_h, type: type.underscore)
138
- .merge(expectations: self[:expectations])
139
- .merge(custom_expectations: self[:custom_expectations])
140
143
  .merge(settings: self[:settings])
141
144
  .merge(RANDOMIZED_FIELDS.map { |it| [it, self[it]] }.to_h)
142
145
  .symbolize_keys
@@ -239,8 +242,8 @@ class Exercise < ApplicationRecord
239
242
  guide.pending_exercises(user)
240
243
  end
241
244
 
242
- def reading?
243
- is_a? Reading
245
+ def solvable?
246
+ is_a? ::Problem
244
247
  end
245
248
 
246
249
  private
@@ -8,8 +8,10 @@ class Challenge < Exercise
8
8
  self.layout = self.class.default_layout
9
9
  end
10
10
 
11
- def extra(*)
12
- [guide.extra, super]
11
+ alias_method :own_extra, :extra
12
+
13
+ def extra
14
+ [guide.extra, own_extra]
13
15
  .compact
14
16
  .join("\n")
15
17
  .strip
@@ -21,20 +21,15 @@ class Problem < QueriableChallenge
21
21
  self.expectations = []
22
22
  end
23
23
 
24
+ alias_method :own_expectations, :expectations
25
+ alias_method :own_custom_expectations, :custom_expectations
26
+
24
27
  def expectations
25
- own_expectations + guide_expectations
28
+ own_expectations + guide.expectations
26
29
  end
27
30
 
28
31
  def custom_expectations
29
- "#{own_custom_expectations}\n#{guide_custom_expectations}"
30
- end
31
-
32
- def guide_expectations
33
- guide.expectations
34
- end
35
-
36
- def guide_custom_expectations
37
- guide.custom_expectations
32
+ "#{own_custom_expectations}\n#{guide.custom_expectations}"
38
33
  end
39
34
 
40
35
  def evaluation_criteria?
@@ -42,7 +42,11 @@ class Guide < Content
42
42
  end
43
43
 
44
44
  def next_exercise(user)
45
- user.next_exercise_at(self)
45
+ if user.present?
46
+ user.next_exercise_at(self)
47
+ else
48
+ first_exercise
49
+ end
46
50
  end
47
51
 
48
52
  # TODO: Make use of pending_siblings logic
@@ -66,8 +66,43 @@ class Indicator < Progress
66
66
  where(content: content, organization: organization).delete_all
67
67
  end
68
68
 
69
+ def _move_to!(organization)
70
+ move_children_to!(organization)
71
+ super
72
+ end
73
+
74
+ def _copy_to!(organization)
75
+ progress_item = super
76
+ children.each { |it| it._copy_to! organization }
77
+ progress_item
78
+ end
79
+
80
+ def move_children_to!(organization)
81
+ children.update_all(organization_id: organization.id)
82
+
83
+ indicators.each { |it| it.move_children_to!(organization) }
84
+ end
85
+
86
+ def cascade_delete_children!
87
+ indicators.each(&:cascade_delete_children!)
88
+ children.delete_all(:delete_all)
89
+ end
90
+
91
+ def content_available_in?(organization)
92
+ content.usage_in_organization(organization).present?
93
+ end
94
+
95
+ def delete_duplicates_in!(organization)
96
+ duplicates_in(organization).each(&:cascade_delete_children!)
97
+ super
98
+ end
99
+
69
100
  private
70
101
 
102
+ def duplicates_key
103
+ { content: content, user: user }
104
+ end
105
+
71
106
  def children
72
107
  indicators.presence || assignments
73
108
  end
@@ -0,0 +1,5 @@
1
+ class Notification < ApplicationRecord
2
+ belongs_to :user
3
+ belongs_to :organization
4
+ belongs_to :target, polymorphic: true
5
+ end
@@ -207,7 +207,7 @@ class Organization < ApplicationRecord
207
207
  end
208
208
 
209
209
  def silenced?
210
- !Mumukit::Platform::Organization.current? || current.silent?
210
+ !current? || current.silent?
211
211
  end
212
212
 
213
213
  def sync_key_id_field
@@ -12,4 +12,39 @@ class Progress < ApplicationRecord
12
12
  def dirty_parent_by_submission!
13
13
  parent&.dirty_by_submission!
14
14
  end
15
+
16
+ def _copy_to!(organization)
17
+ dup.transfer_to!(organization)
18
+ end
19
+
20
+ def transfer_to!(organization)
21
+ update! organization: organization, parent: nil
22
+ self
23
+ end
24
+
25
+ alias_method :_move_to!, :transfer_to!
26
+
27
+ %w(copy move).each do |transfer_type|
28
+ define_method "#{transfer_type}_to!" do |organization|
29
+ "Mumuki::Domain::ProgressTransfer::#{transfer_type.camelize}".constantize.new(self, organization).execute!
30
+ end
31
+ end
32
+
33
+ def guide_indicator?
34
+ is_a?(Indicator) && content_type == 'Guide'
35
+ end
36
+
37
+ def has_duplicates_in?(organization)
38
+ duplicates_in(organization).present?
39
+ end
40
+
41
+ def delete_duplicates_in!(organization)
42
+ duplicates_in(organization).delete_all
43
+ end
44
+
45
+ private
46
+
47
+ def duplicates_in(organization)
48
+ self.class.where(duplicates_key.merge(organization: organization)).where.not(id: id)
49
+ end
15
50
  end
@@ -35,6 +35,14 @@ class Topic < Content
35
35
  Chapter.where(topic: self).map(&:book).each(&:reindex_usages!)
36
36
  end
37
37
 
38
+ def monolesson
39
+ @monolesson ||= lessons.to_a.single
40
+ end
41
+
42
+ def monolesson?
43
+ monolesson.present?
44
+ end
45
+
38
46
  ## Forking
39
47
 
40
48
  def fork_children_into!(dup, organization, syncer)
@@ -12,6 +12,7 @@ class User < ApplicationRecord
12
12
  serialize :permissions, Mumukit::Auth::Permissions
13
13
 
14
14
 
15
+ has_many :notifications
15
16
  has_many :assignments, foreign_key: :submitter_id
16
17
  has_many :messages, -> { order(created_at: :desc) }, through: :assignments
17
18
 
@@ -49,6 +50,10 @@ class User < ApplicationRecord
49
50
  last_guide.try(:lesson)
50
51
  end
51
52
 
53
+ def passed_submissions_count_in(organization)
54
+ assignments.where(top_submission_status: Mumuki::Domain::Status::Submission::Passed.to_i, organization: organization).count
55
+ end
56
+
52
57
  def submissions_count
53
58
  assignments.pluck(:submissions_count).sum
54
59
  end
@@ -182,7 +187,7 @@ class User < ApplicationRecord
182
187
  # This is true only when this organization has the forum enabled and the user
183
188
  # has the discusser pseudo-permission and the discusser is trusted
184
189
  def can_discuss_in?(organization)
185
- organization.forum_enabled? && discusser_of?(organization) && trusted_as_discusser_in?(organization)
190
+ organization.forum_enabled? && discusser_of?(organization) && trusted_as_discusser_in?(organization) && !banned_from_forum?
186
191
  end
187
192
 
188
193
  def trusted_as_discusser_in?(organization)
@@ -1,5 +1,6 @@
1
1
  module WithStats
2
2
  def stats_for(user)
3
+ return unless user.present?
3
4
  Stats.from_statuses exercises.map { |it| it.status_for(user) }
4
5
  end
5
6
 
@@ -0,0 +1,5 @@
1
+ class AddBannedFromForumToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :banned_from_forum, :boolean
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ class CreateExamRegistrations < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :exam_registrations do |t|
4
+ t.string :description
5
+ t.datetime :start_time, null: false
6
+ t.datetime :end_time, null: false
7
+ t.integer :authorization_criterion_type, default: 0
8
+ t.integer :authorization_criterion_value
9
+ t.references :organization, index: true
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ class CreateExamAuthorizationRequest < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :exam_authorization_requests do |t|
4
+ t.integer :status, default: 0
5
+ t.references :exam, index: true
6
+ t.references :exam_registration, index: true
7
+ t.references :user, index: true
8
+ t.references :organization, index: true
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreateNotification < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :notifications do |t|
4
+ t.integer :priority, default: 100
5
+ t.boolean :read, default: false
6
+ t.references :target, polymorphic: true
7
+ t.references :user, index: true
8
+ t.references :organization, index: true
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class AddPreventManualEvaluationContentToOrganizations < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :prevent_manual_evaluation_content, :boolean
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class CreateExamRegistrationExamJoinTable < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_join_table :exams, :exam_registrations do |t|
4
+ t.index :exam_id
5
+ t.index :exam_registration_id
6
+ end
7
+ end
8
+ end
@@ -38,6 +38,7 @@ require_relative './domain/workspace'
38
38
  require_relative './domain/helpers'
39
39
  require_relative './domain/syncable'
40
40
  require_relative './domain/store'
41
+ require_relative './domain/progress_transfer'
41
42
 
42
43
  class Mumukit::Assistant
43
44
  def self.valid?(rules)
@@ -1,5 +1,7 @@
1
1
  require_relative './extensions/string'
2
+ require_relative './extensions/symbol'
2
3
  require_relative './extensions/array'
3
4
  require_relative './extensions/module'
4
5
  require_relative './extensions/hash'
6
+ require_relative './extensions/nil'
5
7
  require_relative './extensions/time'
@@ -19,10 +19,8 @@ class Array
19
19
  def multiple?
20
20
  size > 1
21
21
  end
22
- end
23
22
 
24
- class NilClass
25
- def insert_last(element)
26
- [element]
23
+ def randomize_with(randomizer, seed)
24
+ map { |it| it.randomize_with randomizer, seed }
27
25
  end
28
26
  end
@@ -11,4 +11,8 @@ class Hash
11
11
  def markdownified(*keys, **options)
12
12
  map { |k, v| key.in?(keys) ? v.markdownified(options) : v }.to_h
13
13
  end
14
+
15
+ def randomize_with(randomizer, seed)
16
+ transform_values { |v| v.randomize_with randomizer, seed }
17
+ end
14
18
  end
@@ -0,0 +1,17 @@
1
+ class NilClass
2
+ def affable
3
+ end
4
+
5
+ def markdownified(**options)
6
+ end
7
+
8
+ def sanitized
9
+ end
10
+
11
+ def randomize_with(randomizer, seed)
12
+ end
13
+
14
+ def insert_last(element)
15
+ [element]
16
+ end
17
+ end
@@ -59,15 +59,8 @@ class String
59
59
  def sanitized
60
60
  Mumukit::ContentType::Sanitizer.sanitize self
61
61
  end
62
- end
63
-
64
- class NilClass
65
- def affable
66
- end
67
62
 
68
- def markdownified(**options)
69
- end
70
-
71
- def sanitized
63
+ def randomize_with(randomizer, seed)
64
+ randomizer.randomize!(self, seed)
72
65
  end
73
66
  end
@@ -0,0 +1,5 @@
1
+ class Symbol
2
+ def randomize_with(randomizer, seed)
3
+ self.to_s.randomize_with(randomizer, seed).to_sym
4
+ end
5
+ end
@@ -7,6 +7,8 @@ require_relative './factories/complement_factory'
7
7
  require_relative './factories/course_factory'
8
8
  require_relative './factories/discussion_factory'
9
9
  require_relative './factories/exam_factory'
10
+ require_relative './factories/exam_authorization_request_factory'
11
+ require_relative './factories/exam_registration_factory'
10
12
  require_relative './factories/exercise_factory'
11
13
  require_relative './factories/guide_factory'
12
14
  require_relative './factories/invitation_factory'
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :exam_authorization_request do
3
+ organization { Organization.current }
4
+ exam { create(:exam) }
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :exam_registration do
3
+ description { Faker::Lorem.sentence(word_count: 5) }
4
+ organization { Organization.current }
5
+ start_time { 5.minutes.ago }
6
+ end_time { 10.minutes.since }
7
+ end
8
+ end
@@ -74,6 +74,10 @@ module Mumuki::Domain::Helpers::Organization
74
74
  Mumukit::Platform::Organization.current?
75
75
  end
76
76
 
77
+ def safe_current
78
+ current if current?
79
+ end
80
+
77
81
  def parse(json)
78
82
  json
79
83
  .slice(:name)
@@ -15,7 +15,7 @@ en:
15
15
  attributes:
16
16
  randomizations:
17
17
  invalid_format: 'format is invalid'
18
- own_expectations:
18
+ raw_expectations:
19
19
  invalid_format: 'format is invalid'
20
20
  assistance_rules:
21
21
  invalid_format: 'format is invalid'
@@ -32,7 +32,7 @@ es-CL:
32
32
  attributes:
33
33
  randomizations:
34
34
  invalid_format: 'formato inválido'
35
- own_expectations:
35
+ raw_expectations:
36
36
  invalid_format: 'formato inválido'
37
37
  assistance_rules:
38
38
  invalid_format: 'formato inválido'
@@ -32,7 +32,7 @@ es:
32
32
  attributes:
33
33
  randomizations:
34
34
  invalid_format: 'formato inválido'
35
- own_expectations:
35
+ raw_expectations:
36
36
  invalid_format: 'formato inválido'
37
37
  assistance_rules:
38
38
  invalid_format: 'formato inválido'
@@ -15,7 +15,7 @@ pt:
15
15
  attributes:
16
16
  randomizations:
17
17
  invalid_format: 'o formato é inválido'
18
- own_expectations:
18
+ raw_expectations:
19
19
  invalid_format: 'o formato é inválido'
20
20
  assistance_rules:
21
21
  invalid_format: 'o formato é inválido'
@@ -0,0 +1,8 @@
1
+ module Mumuki::Domain
2
+ module ProgressTransfer
3
+ end
4
+ end
5
+
6
+ require_relative './progress_transfer/base'
7
+ require_relative './progress_transfer/move'
8
+ require_relative './progress_transfer/copy'
@@ -0,0 +1,44 @@
1
+ class Mumuki::Domain::ProgressTransfer::Base
2
+ attr_reader *%i(source_organization destination_organization progress_item transferred_item)
3
+
4
+ delegate :user, to: :progress_item
5
+
6
+ def initialize(progress_item, destination_organization)
7
+ @progress_item = progress_item
8
+ @destination_organization = destination_organization
9
+ end
10
+
11
+ def execute!
12
+ ActiveRecord::Base.transaction do
13
+ pre_transfer!
14
+ transfer!
15
+ post_transfer!
16
+ end
17
+ end
18
+
19
+ def pre_transfer!
20
+ validate_transferrable!
21
+ @source_organization = progress_item.organization
22
+ progress_item.delete_duplicates_in!(destination_organization)
23
+ end
24
+
25
+ def transfer!
26
+ @transferred_item = do_transfer!
27
+ end
28
+
29
+ def post_transfer!
30
+ transferred_item.dirty_parent_by_submission!
31
+ notify_transfer!
32
+ transferred_item
33
+ end
34
+
35
+ def validate_transferrable!
36
+ raise "Transferred progress' content must be available in destination!" unless progress_item.content_available_in?(destination_organization)
37
+ raise 'User must be student in destination organization' unless user.student_of?(destination_organization)
38
+ raise 'Transfer only supported for guide indicators' unless progress_item.guide_indicator?
39
+ end
40
+
41
+ def notify_transfer!
42
+ Mumukit::Nuntius.notify! 'progress-transfers', { from: source_organization.name, to: destination_organization.name, item_type: transferred_item.class.to_s, item_id: transferred_item.id, transfer_type: transfer_type }
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ class Mumuki::Domain::ProgressTransfer::Copy < Mumuki::Domain::ProgressTransfer::Base
2
+ def transfer_type
3
+ :copy
4
+ end
5
+
6
+ def do_transfer!
7
+ progress_item._copy_to!(destination_organization)
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ class Mumuki::Domain::ProgressTransfer::Move < Mumuki::Domain::ProgressTransfer::Base
2
+ def transfer_type
3
+ :move
4
+ end
5
+
6
+ def pre_transfer!
7
+ super
8
+ progress_item.dirty_parent_by_submission!
9
+ end
10
+
11
+ def do_transfer!
12
+ progress_item._move_to!(destination_organization)
13
+ end
14
+ end
@@ -10,6 +10,6 @@ module Mumuki::Domain::Status::Submission::ManualEvaluationPending
10
10
  end
11
11
 
12
12
  def self.iconize
13
- {class: :info, type: 'clock-o'}
13
+ {class: :info, type: 'clock'}
14
14
  end
15
15
  end
@@ -50,4 +50,8 @@ module Mumuki::Domain::Status::Submission
50
50
  def exp_given
51
51
  0
52
52
  end
53
+
54
+ def dup
55
+ self
56
+ end
53
57
  end
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '8.1.0'
3
+ VERSION = '8.3.1'
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: 8.1.0
4
+ version: 8.3.1
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: 2020-11-26 00:00:00.000000000 Z
11
+ date: 2021-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -297,6 +297,7 @@ files:
297
297
  - app/models/concerns/with_slug.rb
298
298
  - app/models/concerns/with_target_audience.rb
299
299
  - app/models/concerns/with_terms_acceptance.rb
300
+ - app/models/concerns/with_timed_enablement.rb
300
301
  - app/models/concerns/with_usages.rb
301
302
  - app/models/concerns/with_user_navigation.rb
302
303
  - app/models/content.rb
@@ -306,6 +307,9 @@ files:
306
307
  - app/models/exam.rb
307
308
  - app/models/exam/passing_criterion.rb
308
309
  - app/models/exam_authorization.rb
310
+ - app/models/exam_authorization_request.rb
311
+ - app/models/exam_registration.rb
312
+ - app/models/exam_registration/authorization_criterion.rb
309
313
  - app/models/exercise.rb
310
314
  - app/models/exercise/challenge.rb
311
315
  - app/models/exercise/interactive.rb
@@ -320,6 +324,7 @@ files:
320
324
  - app/models/lesson.rb
321
325
  - app/models/medal.rb
322
326
  - app/models/message.rb
327
+ - app/models/notification.rb
323
328
  - app/models/organization.rb
324
329
  - app/models/progress.rb
325
330
  - app/models/stats.rb
@@ -636,6 +641,12 @@ files:
636
641
  - db/migrate/20201026225312_add_organization_wins_page_flag.rb
637
642
  - db/migrate/20201027134205_add_immersible_to_organization.rb
638
643
  - db/migrate/20201027152806_create_terms.rb
644
+ - db/migrate/20201130163114_add_banned_from_forum_to_users.rb
645
+ - db/migrate/20210114200545_create_exam_registrations.rb
646
+ - db/migrate/20210118180941_create_exam_authorization_request.rb
647
+ - db/migrate/20210118194904_create_notification.rb
648
+ - db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb
649
+ - db/migrate/20210119190204_create_exam_registration_exam_join_table.rb
639
650
  - lib/mumuki/domain.rb
640
651
  - lib/mumuki/domain/area.rb
641
652
  - lib/mumuki/domain/engine.rb
@@ -655,7 +666,9 @@ files:
655
666
  - lib/mumuki/domain/extensions/array.rb
656
667
  - lib/mumuki/domain/extensions/hash.rb
657
668
  - lib/mumuki/domain/extensions/module.rb
669
+ - lib/mumuki/domain/extensions/nil.rb
658
670
  - lib/mumuki/domain/extensions/string.rb
671
+ - lib/mumuki/domain/extensions/symbol.rb
659
672
  - lib/mumuki/domain/extensions/time.rb
660
673
  - lib/mumuki/domain/factories.rb
661
674
  - lib/mumuki/domain/factories/api_client_factory.rb
@@ -666,7 +679,9 @@ files:
666
679
  - lib/mumuki/domain/factories/complement_factory.rb
667
680
  - lib/mumuki/domain/factories/course_factory.rb
668
681
  - lib/mumuki/domain/factories/discussion_factory.rb
682
+ - lib/mumuki/domain/factories/exam_authorization_request_factory.rb
669
683
  - lib/mumuki/domain/factories/exam_factory.rb
684
+ - lib/mumuki/domain/factories/exam_registration_factory.rb
670
685
  - lib/mumuki/domain/factories/exercise_factory.rb
671
686
  - lib/mumuki/domain/factories/guide_factory.rb
672
687
  - lib/mumuki/domain/factories/invitation_factory.rb
@@ -701,6 +716,10 @@ files:
701
716
  - lib/mumuki/domain/organization/profile.rb
702
717
  - lib/mumuki/domain/organization/settings.rb
703
718
  - lib/mumuki/domain/organization/theme.rb
719
+ - lib/mumuki/domain/progress_transfer.rb
720
+ - lib/mumuki/domain/progress_transfer/base.rb
721
+ - lib/mumuki/domain/progress_transfer/copy.rb
722
+ - lib/mumuki/domain/progress_transfer/move.rb
704
723
  - lib/mumuki/domain/seed.rb
705
724
  - lib/mumuki/domain/status.rb
706
725
  - lib/mumuki/domain/status/discussion/closed.rb
@@ -756,7 +775,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
756
775
  - !ruby/object:Gem::Version
757
776
  version: '0'
758
777
  requirements: []
759
- rubygems_version: 3.0.8
778
+ rubygems_version: 3.0.3
760
779
  signing_key:
761
780
  specification_version: 4
762
781
  summary: Mumuki Platform's Domain Model