mumuki-domain 8.3.1 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/application_record.rb +6 -0
  3. data/app/models/assignment.rb +4 -0
  4. data/app/models/book.rb +1 -1
  5. data/app/models/certificate.rb +29 -0
  6. data/app/models/certificate_program.rb +42 -0
  7. data/app/models/concerns/guide_container.rb +2 -1
  8. data/app/models/concerns/with_assignments.rb +2 -2
  9. data/app/models/concerns/with_assignments_batch.rb +31 -0
  10. data/app/models/concerns/with_generated_code.rb +19 -0
  11. data/app/models/concerns/with_notifications.rb +17 -0
  12. data/app/models/concerns/with_preferences.rb +7 -0
  13. data/app/models/course.rb +5 -1
  14. data/app/models/discussion.rb +4 -0
  15. data/app/models/exam_authorization_request.rb +7 -0
  16. data/app/models/exam_registration.rb +9 -1
  17. data/app/models/guide.rb +2 -1
  18. data/app/models/invitation.rb +6 -10
  19. data/app/models/message.rb +4 -0
  20. data/app/models/notification.rb +4 -0
  21. data/app/models/organization.rb +4 -0
  22. data/app/models/preferences.rb +17 -0
  23. data/app/models/stats.rb +4 -0
  24. data/app/models/topic.rb +8 -7
  25. data/app/models/user.rb +29 -5
  26. data/app/models/user_stats.rb +32 -0
  27. data/app/models/with_stats.rb +1 -1
  28. data/db/migrate/20210111125810_add_uppercase_mode_to_user.rb +5 -0
  29. data/db/migrate/20210119174504_create_certificate_programs.rb +13 -0
  30. data/db/migrate/20210119174835_create_certificates.rb +13 -0
  31. data/lib/mumuki/domain/factories.rb +3 -0
  32. data/lib/mumuki/domain/factories/book_factory.rb +13 -0
  33. data/lib/mumuki/domain/factories/certificate_factory.rb +8 -0
  34. data/lib/mumuki/domain/factories/certificate_program_factory.rb +7 -0
  35. data/lib/mumuki/domain/factories/notification_factory.rb +5 -0
  36. data/lib/mumuki/domain/factories/organization_factory.rb +2 -1
  37. data/lib/mumuki/domain/helpers/course.rb +1 -1
  38. data/lib/mumuki/domain/incognito.rb +4 -1
  39. data/lib/mumuki/domain/version.rb +1 -1
  40. metadata +21 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05e92b25906e2bdad81e9af37f88d03423ce78e18e60e911883e1fd9933056d4
4
- data.tar.gz: 3fc2d20e7199cd604ba3462a5755084ed1ed9bbcf4b434d71bd3ed340677d650
3
+ metadata.gz: 05b62ff1728ff0d8fd4a807ce4a8de4b7f70c532a6748c91af12db484582fad0
4
+ data.tar.gz: 25810325f5fa2fb294b0c189bd20f77e85e4722e2557d50b8a760df07edd56bf
5
5
  SHA512:
6
- metadata.gz: 8b75c52aeec6f2403888816619740882acf061c684d09f71fd52094c9cc2b13561203433b13eaca1499a4d7c6f7046e596d3e01d95ac0e02e983bf1107fc2923
7
- data.tar.gz: 56ed033125bb4eea0d39ba693dca8e142532846159dea3e68329f3b79984ac201a3852ec6019c9428f2696d1e900583c09cd55f8b80c38215fe02ebefd014ed0
6
+ metadata.gz: f5795882f6b1ed8f8e7b4b66e3f85767c58f72eda7131aae3034a5abe48a27d27b2789e2f97e135f4713da0667769c58baec5ea2b9408ceacf012470439e3dbd
7
+ data.tar.gz: 34514f8787088f9c721fafae973af54a1163d27c00d47029bac4544e78c31d4e8726320ef9d3a81f453090e465c32cd5f10ebb25bad2b4ea9ab6f1227118f270
@@ -129,6 +129,12 @@ class ApplicationRecord < ActiveRecord::Base
129
129
  end
130
130
  end
131
131
 
132
+ def self.enum_prefixed_translations_for(selector)
133
+ send(selector.to_s.pluralize).map do |key, _|
134
+ [I18n.t("#{selector}_#{key}", default: key.to_sym), key]
135
+ end
136
+ end
137
+
132
138
  private
133
139
 
134
140
  def raise_foreign_key_error!
@@ -263,6 +263,10 @@ class Assignment < Progress
263
263
  update! misplaced: value if value != misplaced?
264
264
  end
265
265
 
266
+ def self.build_for(user, exercise, organization)
267
+ Assignment.new submitter: user, exercise: exercise, organization: organization
268
+ end
269
+
266
270
  private
267
271
 
268
272
  def duplicates_key
data/app/models/book.rb CHANGED
@@ -18,7 +18,7 @@ class Book < Content
18
18
  end
19
19
 
20
20
  def discussions_in_organization(organization = Organization.current)
21
- Discussion.where(organization: organization).includes(exercise: [:language, :guide])
21
+ Discussion.where(organization: organization, item: organization.exercises).includes(exercise: [:language, :guide])
22
22
  end
23
23
 
24
24
  def first_chapter
@@ -0,0 +1,29 @@
1
+ class Certificate < ApplicationRecord
2
+ include WithGeneratedCode
3
+
4
+ belongs_to :user
5
+ belongs_to :certificate_program
6
+
7
+ has_one :organization, through: :certificate_program
8
+
9
+ delegate :title, :description, :template_html_erb, :background_image_url, to: :certificate_program
10
+
11
+ def self.code_size
12
+ 12
13
+ end
14
+
15
+ def filename
16
+ "#{title.parameterize.underscore}.pdf"
17
+ end
18
+
19
+ def template_locals
20
+ { user: user,
21
+ certificate_program: certificate_program,
22
+ organization: organization,
23
+ certificate: self }
24
+ end
25
+
26
+ def for_user?(user)
27
+ self.user == user
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ class CertificateProgram < ApplicationRecord
2
+ belongs_to :organization
3
+ has_many :certificates
4
+
5
+ def friendly
6
+ title
7
+ end
8
+
9
+ def template_html_erb
10
+ self[:template_html_erb] ||= <<HTML
11
+ <style>
12
+ .qr-code {
13
+ bottom: 5px;
14
+ right: 5px;
15
+ height: 15mm;
16
+ width: 15mm;
17
+ }
18
+ .name {
19
+ position: absolute;
20
+ width: 100%;
21
+ top: 380px;
22
+ text-align: center;
23
+ }
24
+ </style>
25
+ <!-- You can use interpolations like --
26
+ <%#= certificate.start_date %>
27
+ <%#= certificate.end_date %>
28
+ <%#= user.formal_first_name %>
29
+ <%#= user.formal_last_name %>
30
+ <%#= user.formal_full_name %>
31
+ <%#= certificate_program.title %>
32
+ <%#= certificate_program.description %>
33
+ <%#= organization.name %>
34
+ <%#= organization.display_name %>
35
+ -- -->
36
+ <section class="name">
37
+ <h1><%= user.formal_full_name %></h1>
38
+ </section>
39
+ HTML
40
+ end
41
+
42
+ end
@@ -13,7 +13,8 @@ module GuideContainer
13
13
  :first_exercise,
14
14
  :next_exercise,
15
15
  :stats_for,
16
- :exercises_count, to: :guide
16
+ :exercises_count,
17
+ :assignments_for, to: :guide
17
18
  end
18
19
 
19
20
  def index_usage!(organization = Organization.current)
@@ -19,8 +19,8 @@ module WithAssignments
19
19
  end
20
20
 
21
21
  # TODO: When the organization is used in this one, please change guide.pending_exercises
22
- def find_assignment_for(user, _organization)
23
- assignments.find_by(submitter: user)
22
+ def find_assignment_for(user, organization)
23
+ assignments.find_by(submitter: user, organization: organization)
24
24
  end
25
25
 
26
26
  def status_for(user)
@@ -0,0 +1,31 @@
1
+ # WithAssignmentsBatch mirrors the WithAssignment mixin
2
+ # but implements operations in batches, so that they outperform
3
+ # their counterparts
4
+ module WithAssignmentsBatch
5
+ extend ActiveSupport::Concern
6
+
7
+ def find_assignments_for(user, organization = Organization.current, &block)
8
+ block = block_given? ? block : lambda { |it, _e| it }
9
+
10
+ return exercises.map { |it| block.call nil, it } unless user
11
+
12
+ pairs = exercises.map { |it| [it.id, [nil, it]] }.to_h
13
+ Assignment.where(submitter: user, organization: organization, exercise: exercises).each do |it|
14
+ pairs[it.exercise_id][0] = it
15
+ end
16
+
17
+ pairs.values.map { |assignment, exercise| block.call assignment, exercise }
18
+ end
19
+
20
+ def statuses_for(user, organization = Organization.current)
21
+ find_assignments_for user, organization do |it|
22
+ it&.status || Mumuki::Domain::Status::Submission::Pending
23
+ end
24
+ end
25
+
26
+ def assignments_for(user, organization = Organization.current)
27
+ find_assignments_for user, organization do |it, exercise|
28
+ it || Assignment.build_for(user, exercise, organization)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ module WithGeneratedCode
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ validates_uniqueness_of :code
6
+
7
+ defaults do
8
+ self.code ||= self.class.generate_code
9
+ end
10
+
11
+ required :code_size
12
+ end
13
+
14
+ class_methods do
15
+ def generate_code
16
+ SecureRandom.urlsafe_base64 code_size
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module WithNotifications
2
+ extend ActiveSupport::Concern
3
+
4
+ def unread_messages
5
+ messages_in_organization.where read: false
6
+ end
7
+
8
+ def unread_notifications
9
+ # TODO: message and discussion should trigger a notification instead of being one
10
+ all = notifications.where(read: false) + unread_messages + unread_discussions
11
+ all.sort_by(&:created_at).reverse
12
+ end
13
+
14
+ def read_notification!(target)
15
+ notifications.find_by(target: target)&.mark_as_read!
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module WithPreferences
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ composed_of :preferences, mapping: %w(uppercase_mode uppercase_mode), constructor: :from_attributes
6
+ end
7
+ end
data/app/models/course.rb CHANGED
@@ -3,7 +3,7 @@ class Course < ApplicationRecord
3
3
  include Mumuki::Domain::Helpers::Course
4
4
  include Mumuki::Domain::Area
5
5
 
6
- validates_presence_of :slug, :shifts, :code, :days, :period, :description, :organization_id
6
+ validates_presence_of :slug, :period, :code, :description, :organization_id
7
7
  validates_uniqueness_of :slug
8
8
  belongs_to :organization
9
9
 
@@ -35,6 +35,10 @@ class Course < ApplicationRecord
35
35
  end
36
36
  end
37
37
 
38
+ def canonical_code
39
+ "#{period}-#{code}".downcase
40
+ end
41
+
38
42
  def closed?
39
43
  current_invitation.blank? || current_invitation.expired?
40
44
  end
@@ -48,6 +48,10 @@ class Discussion < ApplicationRecord
48
48
  nil
49
49
  end
50
50
 
51
+ def target
52
+ self
53
+ end
54
+
51
55
  def used_in?(organization)
52
56
  organization == self.organization
53
57
  end
@@ -1,7 +1,10 @@
1
1
  class ExamAuthorizationRequest < ApplicationRecord
2
+ include TerminalNavigation
3
+
2
4
  belongs_to :exam
3
5
  belongs_to :user
4
6
  belongs_to :organization
7
+ belongs_to :exam_registration
5
8
 
6
9
  enum status: %i(pending approved rejected)
7
10
 
@@ -11,6 +14,10 @@ class ExamAuthorizationRequest < ApplicationRecord
11
14
  exam.authorize! user if approved?
12
15
  end
13
16
 
17
+ def name
18
+ exam_registration.description
19
+ end
20
+
14
21
  private
15
22
 
16
23
  def notify_user!
@@ -1,9 +1,10 @@
1
1
  class ExamRegistration < ApplicationRecord
2
2
  include WithTimedEnablement
3
+ include TerminalNavigation
3
4
 
4
5
  belongs_to :organization
5
6
  has_and_belongs_to_many :exams
6
- has_many :authorization_requests, class_name: 'ExamAuthorizationRequest', through: :exams
7
+ has_many :authorization_requests, class_name: 'ExamAuthorizationRequest'
7
8
 
8
9
  enum authorization_criterion_type: %i(none passed_exercises), _prefix: :authorization_criterion
9
10
 
@@ -11,6 +12,8 @@ class ExamRegistration < ApplicationRecord
11
12
 
12
13
  delegate :meets_authorization_criteria?, :process_request!, to: :authorization_criterion
13
14
 
15
+ alias_attribute :name, :description
16
+
14
17
  def authorization_criterion
15
18
  @authorization_criterion ||= ExamRegistration::AuthorizationCriterion.parse(authorization_criterion_type, authorization_criterion_value)
16
19
  end
@@ -30,6 +33,11 @@ class ExamRegistration < ApplicationRecord
30
33
  end
31
34
  end
32
35
 
36
+ def authorization_request_for(user)
37
+ authorization_requests.find_by(user: user) ||
38
+ ExamAuthorizationRequest.new(exam_registration: self, organization: organization)
39
+ end
40
+
33
41
  private
34
42
 
35
43
  def notify_user!(user)
data/app/models/guide.rb CHANGED
@@ -6,7 +6,8 @@ class Guide < Content
6
6
 
7
7
  include WithStats,
8
8
  WithExpectations,
9
- WithLanguage
9
+ WithLanguage,
10
+ WithAssignmentsBatch
10
11
 
11
12
  markdown_on :corollary, :sources, :learn_more, :teacher_info
12
13
 
@@ -1,14 +1,10 @@
1
1
  class Invitation < ApplicationRecord
2
- include Mumuki::Domain::Syncable
2
+ include Mumuki::Domain::Syncable,
3
+ WithGeneratedCode
3
4
 
4
5
  belongs_to :course
5
6
 
6
7
  validate :ensure_not_expired, on: :create
7
- validates_uniqueness_of :code
8
-
9
- defaults do
10
- self.code ||= self.class.generate_code
11
- end
12
8
 
13
9
  def ensure_not_expired
14
10
  errors.add(:base, :invitation_expired) if expired?
@@ -51,12 +47,12 @@ class Invitation < ApplicationRecord
51
47
  self
52
48
  end
53
49
 
54
- def self.generate_code
55
- SecureRandom.urlsafe_base64 4
56
- end
57
-
58
50
  private
59
51
 
52
+ def self.code_size
53
+ 4
54
+ end
55
+
60
56
  def course_name
61
57
  course.name
62
58
  end
@@ -70,6 +70,10 @@ class Message < ApplicationRecord
70
70
  from_initiator? && !not_actually_a_question?
71
71
  end
72
72
 
73
+ def target
74
+ self
75
+ end
76
+
73
77
  def self.parse_json(json)
74
78
  message = json.delete 'message'
75
79
  json
@@ -2,4 +2,8 @@ class Notification < ApplicationRecord
2
2
  belongs_to :user
3
3
  belongs_to :organization
4
4
  belongs_to :target, polymorphic: true
5
+
6
+ def mark_as_read!
7
+ update read: true
8
+ end
5
9
  end
@@ -138,6 +138,10 @@ class Organization < ApplicationRecord
138
138
  self[:progressive_display_lookahead] = lookahead.to_i.positive? ? lookahead : nil
139
139
  end
140
140
 
141
+ def activity_start_date(default_date)
142
+ [default_date, in_preparation_until&.to_date].compact.max
143
+ end
144
+
141
145
  # ==============
142
146
  # Display fields
143
147
  # ==============
@@ -0,0 +1,17 @@
1
+ class Preferences
2
+ include ActiveModel::Model
3
+
4
+ def self.attributes
5
+ [:uppercase_mode]
6
+ end
7
+
8
+ attr_accessor *self.attributes
9
+
10
+ def self.from_attributes(*args)
11
+ new self.attributes.zip(args).to_h
12
+ end
13
+
14
+ def uppercase?
15
+ uppercase_mode
16
+ end
17
+ end
data/app/models/stats.rb CHANGED
@@ -11,6 +11,10 @@ class Stats
11
11
  failed + pending == 0
12
12
  end
13
13
 
14
+ def almost_done?
15
+ failed + pending <= 1
16
+ end
17
+
14
18
  def started?
15
19
  submitted > 0
16
20
  end
data/app/models/topic.rb CHANGED
@@ -50,18 +50,19 @@ class Topic < Content
50
50
  end
51
51
 
52
52
  def pending_lessons(user)
53
- guides.
54
- joins('left join public.exercises exercises
55
- on exercises.guide_id = guides.id').
56
- joins("left join public.assignments assignments
53
+ lessons
54
+ .includes(:guide)
55
+ .references(:guide)
56
+ .joins('left join exercises exercises on exercises.guide_id = guides.id')
57
+ .joins("left join assignments assignments
57
58
  on assignments.exercise_id = exercises.id
58
59
  and assignments.submitter_id = #{user.id}
59
60
  and assignments.submission_status in (
60
61
  #{Mumuki::Domain::Status::Submission::Passed.to_i},
61
62
  #{Mumuki::Domain::Status::Submission::ManualEvaluationPending.to_i}
62
- )").
63
- where('assignments.id is null').
64
- group('public.guides.id', 'lessons.number').map(&:lesson)
63
+ )")
64
+ .where('assignments.id is null')
65
+ .group('guides.id', 'lessons.number', 'lessons.id')
65
66
  end
66
67
 
67
68
  private
data/app/models/user.rb CHANGED
@@ -3,10 +3,12 @@ class User < ApplicationRecord
3
3
  include WithProfile,
4
4
  WithUserNavigation,
5
5
  WithReminders,
6
+ WithNotifications,
6
7
  WithDiscussionCreation,
7
8
  Awardee,
8
9
  Disabling,
9
10
  WithTermsAcceptance,
11
+ WithPreferences,
10
12
  Mumuki::Domain::Helpers::User
11
13
 
12
14
  serialize :permissions, Mumukit::Auth::Permissions
@@ -33,6 +35,8 @@ class User < ApplicationRecord
33
35
 
34
36
  has_many :exams, through: :exam_authorizations
35
37
 
38
+ has_many :certificates
39
+
36
40
  enum gender: %i(female male other unspecified)
37
41
  belongs_to :avatar, polymorphic: true, optional: true
38
42
 
@@ -50,6 +54,10 @@ class User < ApplicationRecord
50
54
  last_guide.try(:lesson)
51
55
  end
52
56
 
57
+ def messages_in_organization(organization = Organization.current)
58
+ messages.where('assignments.organization': organization)
59
+ end
60
+
53
61
  def passed_submissions_count_in(organization)
54
62
  assignments.where(top_submission_status: Mumuki::Domain::Status::Submission::Passed.to_i, organization: organization).count
55
63
  end
@@ -74,10 +82,6 @@ class User < ApplicationRecord
74
82
  assignments.where(status: Mumuki::Domain::Status::Submission::Passed.to_i)
75
83
  end
76
84
 
77
- def unread_messages
78
- messages.where read: false
79
- end
80
-
81
85
  def visit!(organization)
82
86
  update!(last_organization: organization) if organization != last_organization
83
87
  end
@@ -211,7 +215,7 @@ class User < ApplicationRecord
211
215
  end
212
216
 
213
217
  def build_assignment(exercise, organization)
214
- assignments.build(exercise: exercise, organization: organization)
218
+ Assignment.build_for(self, exercise, organization)
215
219
  end
216
220
 
217
221
  def pending_siblings_at(content)
@@ -277,6 +281,26 @@ class User < ApplicationRecord
277
281
  end
278
282
  end
279
283
 
284
+ def formal_first_name
285
+ verified_first_name || first_name
286
+ end
287
+
288
+ def formal_last_name
289
+ verified_last_name || last_name
290
+ end
291
+
292
+ def formal_full_name
293
+ "#{formal_first_name} #{formal_last_name}"
294
+ end
295
+
296
+ def certificates_in_organization(organization = Organization.current)
297
+ certificates.where certificate_program: CertificateProgram.where(organization: organization)
298
+ end
299
+
300
+ def certificated_in?(certificate_program)
301
+ certificates.where(certificate_program: certificate_program).exists?
302
+ end
303
+
280
304
  private
281
305
 
282
306
  def welcome_to_new_organizations!
@@ -10,7 +10,39 @@ class UserStats < ApplicationRecord
10
10
  self.stats_for(user).exp
11
11
  end
12
12
 
13
+ def activity(date_range = nil)
14
+ date_filter = { submitted_at: date_range }.compact
15
+ {
16
+ exercises: {
17
+ solved_count: organization_exercises
18
+ .joins(:assignments)
19
+ .where(assignments: { top_submission_status: [:passed, :skipped], submitter: user }.merge(date_filter))
20
+ .count,
21
+ count: organization_exercises.count},
22
+
23
+ messages: messages_in_discussions_count(date_range)
24
+ }
25
+ end
26
+
13
27
  def add_exp!(points)
14
28
  self.exp += points
15
29
  end
30
+
31
+ private
32
+
33
+ def messages_in_discussions_count(date_range = nil)
34
+ date_filter = { date: date_range }.compact
35
+ result = Message.joins(:discussion)
36
+ .where({sender: user.uid, discussions: { organization: organization }}.merge(date_filter))
37
+ .group(:approved)
38
+ .count
39
+ unapproved = result[false] || 0
40
+ approved = result[true] || 0
41
+
42
+ { count: unapproved + approved, approved: approved }
43
+ end
44
+
45
+ def organization_exercises
46
+ @organization_exercises ||= organization.exercises
47
+ end
16
48
  end
@@ -1,7 +1,7 @@
1
1
  module WithStats
2
2
  def stats_for(user)
3
3
  return unless user.present?
4
- Stats.from_statuses exercises.map { |it| it.status_for(user) }
4
+ Stats.from_statuses statuses_for(user)
5
5
  end
6
6
 
7
7
  def started?(user)
@@ -0,0 +1,5 @@
1
+ class AddUppercaseModeToUser < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :uppercase_mode, :boolean
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCertificatePrograms < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :certificate_programs do |t|
4
+ t.string :title
5
+ t.string :template_html_erb
6
+ t.text :description
7
+ t.string :background_image_url
8
+ t.references :organization, index: true
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCertificates < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :certificates do |t|
4
+ t.references :user, index: true
5
+ t.references :certificate_program, index: true
6
+ t.datetime :start_date
7
+ t.datetime :end_date
8
+ t.string :code
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -2,6 +2,8 @@ require_relative './factories/api_client_factory'
2
2
  require_relative './factories/assignments_factory'
3
3
  require_relative './factories/avatar_factory'
4
4
  require_relative './factories/book_factory'
5
+ require_relative './factories/certificate_factory'
6
+ require_relative './factories/certificate_program_factory'
5
7
  require_relative './factories/chapter_factory'
6
8
  require_relative './factories/complement_factory'
7
9
  require_relative './factories/course_factory'
@@ -16,6 +18,7 @@ require_relative './factories/lesson_factory'
16
18
  require_relative './factories/login_settings_factory'
17
19
  require_relative './factories/medal_factory'
18
20
  require_relative './factories/message_factory'
21
+ require_relative './factories/notification_factory'
19
22
  require_relative './factories/organization_factory'
20
23
  require_relative './factories/term_factory'
21
24
  require_relative './factories/topic_factory'
@@ -4,4 +4,17 @@ FactoryBot.define do
4
4
  description { Faker::Lorem.sentence(word_count: 30) }
5
5
  slug { "mumuki/mumuki-test-book-#{SecureRandom.uuid}" }
6
6
  end
7
+
8
+ factory :book_with_full_tree, parent: :book do
9
+ transient do
10
+ children_factor { 3 }
11
+ exercises { create_list(:exercise, children_factor) }
12
+ lessons { create_list(:lesson, children_factor, exercises: exercises) }
13
+ chapters { create_list(:chapter, children_factor, lessons: lessons) }
14
+ end
15
+
16
+ after(:build) do |book, evaluator|
17
+ book.chapters = evaluator.chapters
18
+ end
19
+ end
7
20
  end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :certificate do
3
+ start_date { 1.month.ago }
4
+ end_date { 1.minute.ago }
5
+ user { build :user, first_name: 'Jane', last_name: 'Doe' }
6
+ certificate_program { build :certificate_program }
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ FactoryBot.define do
2
+ factory :certificate_program do
3
+ title { 'Test' }
4
+ description { 'Certificate program to test' }
5
+ organization { Organization.current }
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ FactoryBot.define do
2
+ factory :notification do
3
+ organization { Organization.current }
4
+ end
5
+ end
@@ -29,7 +29,8 @@ FactoryBot.define do
29
29
  book { create(:book, name: 'test', slug: 'mumuki/mumuki-the-book') }
30
30
  end
31
31
 
32
- factory :another_test_organization, parent: :test_organization, traits: [:skip_unique_name_validation] do
32
+ factory :another_test_organization, parent: :test_organization do
33
+ name { 'another-test' }
33
34
  book { create(:book, name: 'another-test', slug: 'mumuki/mumuki-another-book') }
34
35
  end
35
36
 
@@ -8,6 +8,6 @@ module Mumuki::Domain::Helpers::Course
8
8
  ## API Exposure
9
9
 
10
10
  def to_param
11
- slug
11
+ canonical_code
12
12
  end
13
13
  end
@@ -90,6 +90,9 @@ module Mumuki::Domain
90
90
  def visit!(*)
91
91
  end
92
92
 
93
+ def currently_in_exam?
94
+ false
95
+ end
93
96
  # ========
94
97
  # Progress
95
98
  # ========
@@ -107,7 +110,7 @@ module Mumuki::Domain
107
110
  end
108
111
 
109
112
  def build_assignment(exercise, organization)
110
- Assignment.new exercise: exercise, organization: organization, submitter: self
113
+ Assignment.build_for(self, exercise, organization)
111
114
  end
112
115
 
113
116
  def pending_siblings_at(content)
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '8.3.1'
3
+ VERSION = '9.0.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: 8.3.1
4
+ version: 9.0.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-01-21 00:00:00.000000000 Z
11
+ date: 2021-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '6.1'
131
+ version: '7.0'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '6.1'
138
+ version: '7.0'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: mumukit-sync
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -170,28 +170,28 @@ dependencies:
170
170
  requirements:
171
171
  - - "~>"
172
172
  - !ruby/object:Gem::Version
173
- version: '5.0'
173
+ version: '6.0'
174
174
  type: :runtime
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
- version: '5.0'
180
+ version: '6.0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: mumukit-inspection
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - "~>"
186
186
  - !ruby/object:Gem::Version
187
- version: '5.1'
187
+ version: '6.0'
188
188
  type: :runtime
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - "~>"
193
193
  - !ruby/object:Gem::Version
194
- version: '5.1'
194
+ version: '6.0'
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: sprockets
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -249,6 +249,8 @@ files:
249
249
  - app/models/assignment.rb
250
250
  - app/models/avatar.rb
251
251
  - app/models/book.rb
252
+ - app/models/certificate.rb
253
+ - app/models/certificate_program.rb
252
254
  - app/models/chapter.rb
253
255
  - app/models/complement.rb
254
256
  - app/models/concerns/assistable.rb
@@ -270,6 +272,7 @@ files:
270
272
  - app/models/concerns/submittable/triable.rb
271
273
  - app/models/concerns/topic_container.rb
272
274
  - app/models/concerns/with_assignments.rb
275
+ - app/models/concerns/with_assignments_batch.rb
273
276
  - app/models/concerns/with_case_insensitive_search.rb
274
277
  - app/models/concerns/with_description.rb
275
278
  - app/models/concerns/with_discussion_creation.rb
@@ -279,13 +282,16 @@ files:
279
282
  - app/models/concerns/with_discussions.rb
280
283
  - app/models/concerns/with_editor.rb
281
284
  - app/models/concerns/with_expectations.rb
285
+ - app/models/concerns/with_generated_code.rb
282
286
  - app/models/concerns/with_language.rb
283
287
  - app/models/concerns/with_layout.rb
284
288
  - app/models/concerns/with_locale.rb
285
289
  - app/models/concerns/with_medal.rb
286
290
  - app/models/concerns/with_messages.rb
287
291
  - app/models/concerns/with_name.rb
292
+ - app/models/concerns/with_notifications.rb
288
293
  - app/models/concerns/with_number.rb
294
+ - app/models/concerns/with_preferences.rb
289
295
  - app/models/concerns/with_profile.rb
290
296
  - app/models/concerns/with_progress.rb
291
297
  - app/models/concerns/with_randomizations.rb
@@ -326,6 +332,7 @@ files:
326
332
  - app/models/message.rb
327
333
  - app/models/notification.rb
328
334
  - app/models/organization.rb
335
+ - app/models/preferences.rb
329
336
  - app/models/progress.rb
330
337
  - app/models/stats.rb
331
338
  - app/models/subscription.rb
@@ -642,10 +649,13 @@ files:
642
649
  - db/migrate/20201027134205_add_immersible_to_organization.rb
643
650
  - db/migrate/20201027152806_create_terms.rb
644
651
  - db/migrate/20201130163114_add_banned_from_forum_to_users.rb
652
+ - db/migrate/20210111125810_add_uppercase_mode_to_user.rb
645
653
  - db/migrate/20210114200545_create_exam_registrations.rb
646
654
  - db/migrate/20210118180941_create_exam_authorization_request.rb
647
655
  - db/migrate/20210118194904_create_notification.rb
648
656
  - db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb
657
+ - db/migrate/20210119174504_create_certificate_programs.rb
658
+ - db/migrate/20210119174835_create_certificates.rb
649
659
  - db/migrate/20210119190204_create_exam_registration_exam_join_table.rb
650
660
  - lib/mumuki/domain.rb
651
661
  - lib/mumuki/domain/area.rb
@@ -675,6 +685,8 @@ files:
675
685
  - lib/mumuki/domain/factories/assignments_factory.rb
676
686
  - lib/mumuki/domain/factories/avatar_factory.rb
677
687
  - lib/mumuki/domain/factories/book_factory.rb
688
+ - lib/mumuki/domain/factories/certificate_factory.rb
689
+ - lib/mumuki/domain/factories/certificate_program_factory.rb
678
690
  - lib/mumuki/domain/factories/chapter_factory.rb
679
691
  - lib/mumuki/domain/factories/complement_factory.rb
680
692
  - lib/mumuki/domain/factories/course_factory.rb
@@ -689,6 +701,7 @@ files:
689
701
  - lib/mumuki/domain/factories/login_settings_factory.rb
690
702
  - lib/mumuki/domain/factories/medal_factory.rb
691
703
  - lib/mumuki/domain/factories/message_factory.rb
704
+ - lib/mumuki/domain/factories/notification_factory.rb
692
705
  - lib/mumuki/domain/factories/organization_factory.rb
693
706
  - lib/mumuki/domain/factories/term_factory.rb
694
707
  - lib/mumuki/domain/factories/topic_factory.rb