mumuki-domain 7.3.1 → 7.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/assignment.rb +11 -3
  3. data/app/models/avatar.rb +5 -0
  4. data/app/models/concerns/contextualization.rb +63 -8
  5. data/app/models/concerns/disabling.rb +37 -0
  6. data/app/models/concerns/guide_container.rb +7 -1
  7. data/app/models/concerns/submittable/solvable.rb +1 -1
  8. data/app/models/concerns/with_editor.rb +1 -1
  9. data/app/models/exam.rb +4 -0
  10. data/app/models/exercise.rb +15 -4
  11. data/app/models/guide.rb +1 -1
  12. data/app/models/stats.rb +2 -2
  13. data/app/models/user.rb +32 -9
  14. data/db/migrate/20200312181842_add_results_hidden_for_choices_to_exam.rb +5 -0
  15. data/db/migrate/20200508191543_create_avatars.rb +8 -0
  16. data/db/migrate/20200518135658_add_avatar_to_users.rb +5 -0
  17. data/db/migrate/20200527180729_add_disabled_at_to_users.rb +6 -0
  18. data/lib/mumuki/domain/exceptions.rb +1 -0
  19. data/lib/mumuki/domain/exceptions/disabled_error.rb +2 -0
  20. data/lib/mumuki/domain/extensions/hash.rb +11 -2
  21. data/lib/mumuki/domain/extensions/string.rb +44 -0
  22. data/lib/mumuki/domain/factories/book_factory.rb +2 -2
  23. data/lib/mumuki/domain/factories/chapter_factory.rb +2 -2
  24. data/lib/mumuki/domain/factories/exam_factory.rb +1 -1
  25. data/lib/mumuki/domain/factories/exercise_factory.rb +7 -0
  26. data/lib/mumuki/domain/factories/guide_factory.rb +2 -2
  27. data/lib/mumuki/domain/factories/invitation_factory.rb +1 -1
  28. data/lib/mumuki/domain/factories/topic_factory.rb +2 -2
  29. data/lib/mumuki/domain/factories/user_factory.rb +1 -0
  30. data/lib/mumuki/domain/helpers/user.rb +1 -1
  31. data/lib/mumuki/domain/status/submission/skipped.rb +0 -4
  32. data/lib/mumuki/domain/status/submission/submission.rb +5 -3
  33. data/lib/mumuki/domain/version.rb +1 -1
  34. metadata +11 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 427357d23facceb8e41d0f3a934f5c03e5ee095ef03dbb50af96338d158030d7
4
- data.tar.gz: 9e4fd1602971d3e83ca97a56adcecb8507f11d96a4ec9d882b7cfee4552dce9c
3
+ metadata.gz: 5ef144a131abd81dec63a202e154365daa60a369f157f3dacd86fae7b6b078c7
4
+ data.tar.gz: f3b58dde606295d1e3c5edc5c901259bfff933570927e66fb112c58f062fcc7d
5
5
  SHA512:
6
- metadata.gz: e7c2c976a4ae3d4be45e8c06764e0c39518bd5fb09478cb63f3af7710b88d405bf53f13b9e9d57aad0a2e1105a97a7414e0c6a4fe89543b433eedc1f10d529d8
7
- data.tar.gz: 02e47399510cca76f79dc992e8894f12b9bedf2b1eb7f5b9f0f1413bbc824b44d8e276a953eb3bd3b59bd562dd486337e64d3468d81d91ddb681118506169aaa
6
+ metadata.gz: c6d190e5ebdb654e7be93ddd9b5b3938e67fc334d8dfe0ac769aa62cb44ec8e8acc1fefde7a5418a0b9fc01f07d2956ab5464025e0b7cf953d1c983a1c6a942c
7
+ data.tar.gz: 647aa6b8fbd360d2cc5a306dcc58ad148316aa5c6ed3a893fe08e11462e732075d9775dc6c038f9dca03c7dac3ad2236f81f74b05a06473fa93f3ea53331a953
@@ -18,7 +18,7 @@ class Assignment < Progress
18
18
  validates_presence_of :exercise, :submitter
19
19
 
20
20
  delegate :language, :name, :navigable_parent, :settings,
21
- :limited?, :input_kids?, :choice?, to: :exercise
21
+ :limited?, :input_kids?, :choice?, :results_hidden?, to: :exercise
22
22
 
23
23
  alias_attribute :status, :submission_status
24
24
  alias_attribute :attempts_count, :attemps_count
@@ -59,6 +59,14 @@ class Assignment < Progress
59
59
  update! status: teacher_evaluation[:status], manual_evaluation_comment: teacher_evaluation[:manual_evaluation]
60
60
  end
61
61
 
62
+ def visible_status
63
+ if results_hidden? && !pending?
64
+ :manual_evaluation_pending.to_submission_status
65
+ else
66
+ super
67
+ end
68
+ end
69
+
62
70
  def persist_submission!(submission)
63
71
  transaction do
64
72
  messages.destroy_all if submission_id.present?
@@ -160,7 +168,7 @@ class Assignment < Progress
160
168
  language: {only: [:name]}},
161
169
  },
162
170
  exercise: {only: [:name, :number]},
163
- submitter: {only: [:email, :image_url, :social_id, :uid], methods: [:name]}}).
171
+ submitter: {only: [:email, :social_id, :uid], methods: [:name, :profile_picture]}}).
164
172
  deep_merge(
165
173
  'organization' => Organization.current.name,
166
174
  'sid' => submission_id,
@@ -190,7 +198,7 @@ class Assignment < Progress
190
198
  navigable_parent.attempts_left_for(self)
191
199
  end
192
200
 
193
- # Tells wether the submitter of this
201
+ # Tells whether the submitter of this
194
202
  # assignment can keep on sending submissions
195
203
  # which is true for non limited or for assignments
196
204
  # that have not reached their submissions limit
@@ -0,0 +1,5 @@
1
+ class Avatar < ApplicationRecord
2
+ def self.sample
3
+ Avatar.order('RANDOM()').first
4
+ end
5
+ end
@@ -25,7 +25,7 @@ module Contextualization
25
25
 
26
26
  delegate :visible_success_output?, to: :exercise
27
27
  delegate :output_content_type, to: :language
28
- delegate :should_retry?, :to_submission_status, :completed?, *Mumuki::Domain::Status::Submission.test_selectors, to: :submission_status
28
+ delegate :should_retry?, :to_submission_status, :completed?, :solved?, *Mumuki::Domain::Status::Submission.test_selectors, to: :submission_status
29
29
  delegate :inspection_keywords, to: :exercise
30
30
  end
31
31
 
@@ -35,20 +35,46 @@ module Contextualization
35
35
  end
36
36
  end
37
37
 
38
+ # deprecated: this method does hidden assumptions about the UI not wanting
39
+ # non-empty titles to not be displayed. Also it incorrectly uses the term `visual` instead of `visible`
38
40
  def single_visual_result?
39
- test_results.size == 1 && test_results.first[:title].blank? && visible_success_output?
41
+ warn 'use single_visible_test_result? instead'
42
+ single_visible_test_result? && first_test_result[:title].blank?
40
43
  end
41
44
 
45
+ # deprecated: this method does not validate nor depends on any `visible` condition
46
+ # Also, it incorrectly uses the term `visual` instead of `visible`
42
47
  def single_visual_result_html
43
- output_content_type.to_html test_results.first[:result]
48
+ warn 'use first_test_result_html intead'
49
+ first_test_result_html
44
50
  end
45
51
 
46
- def results_visible?
47
- (visible_success_output? || !passed?) && !exercise.choices? && !manual_evaluation_pending?
52
+ def single_visible_test_result?
53
+ test_results.size == 1 && visible_success_output?
48
54
  end
49
55
 
50
- def result_preview
51
- result.truncate(100) unless passed?
56
+ def first_test_result
57
+ test_results.first
58
+ end
59
+
60
+ def first_test_result_html
61
+ test_result_html first_test_result
62
+ end
63
+
64
+ def test_result_html(test_result)
65
+ output_content_type.to_html test_result[:result]
66
+ end
67
+
68
+ def results_body_hidden?
69
+ (passed? && !visible_success_output?) || exercise.choice? || manual_evaluation_pending? || skipped?
70
+ end
71
+
72
+ def visible_status
73
+ status
74
+ end
75
+
76
+ def iconize
77
+ visible_status.iconize
52
78
  end
53
79
 
54
80
  def result_html
@@ -60,7 +86,7 @@ module Contextualization
60
86
  end
61
87
 
62
88
  def failed_expectation_results
63
- expectation_results.to_a.select { |it| it[:result].failed? }
89
+ expectation_results.to_a.select { |it| it[:result].failed? }.uniq
64
90
  end
65
91
 
66
92
  def expectation_results_visible?
@@ -72,6 +98,7 @@ module Contextualization
72
98
  end
73
99
 
74
100
  def humanized_expectation_results
101
+ warn "Don't use humanized_expectation_results. Use affable_expectation_results, which also handles markdown and sanitization"
75
102
  visible_expectation_results.map do |it|
76
103
  {
77
104
  result: it[:result],
@@ -79,4 +106,32 @@ module Contextualization
79
106
  }
80
107
  end
81
108
  end
109
+
110
+ ####################
111
+ ## Affable results
112
+ ####################
113
+
114
+ def affable_expectation_results
115
+ visible_expectation_results.map do |it|
116
+ {
117
+ result: it[:result],
118
+ explanation: Mulang::Expectation.parse(it).translate(inspection_keywords).affable
119
+ }
120
+ end
121
+ end
122
+
123
+ def affable_tips
124
+ tips.map(&:affable)
125
+ end
126
+
127
+ def affable_test_results
128
+ test_results.to_a.map do |it|
129
+ { summary: it.dig(:summary, :message).affable }
130
+ .compact
131
+ .merge(
132
+ title: it[:title].affable,
133
+ result: it[:result].sanitized,
134
+ status: it[:status])
135
+ end
136
+ end
82
137
  end
@@ -0,0 +1,37 @@
1
+ # The disposable module is a soft-delete helper that:
2
+ #
3
+ # * adds `disable!` method that set a `disabled_at` attribute and then _buries_ the object
4
+ # * adds a `bury!` hook method that allows further modification when disabling
5
+ # * aliases `destroy!` and `destroy` to `disable!`, but still keeps `delete` and friends
6
+ #
7
+ module Disabling
8
+ extend ActiveSupport::Concern
9
+
10
+ def disable!
11
+ transaction do
12
+ update_attribute :disabled_at, Time.current
13
+ bury!
14
+ end
15
+ end
16
+
17
+ def disabled?
18
+ disabled_at.present?
19
+ end
20
+
21
+ def enabled?
22
+ !disabled?
23
+ end
24
+
25
+ # override to perform additional
26
+ # post-disable actions
27
+ def bury!
28
+ end
29
+
30
+ def ensure_enabled!
31
+ raise Mumuki::Domain::DisabledError if disabled?
32
+ end
33
+
34
+ alias_method :destroy!, :disable!
35
+ alias_method :destroy, :disable!
36
+ end
37
+
@@ -38,7 +38,13 @@ module GuideContainer
38
38
  # Tells if this guide container
39
39
  # imposes any kind of limit to the number of submission
40
40
  # to its assignments, which may depend on the exercise's type
41
- def limited_for?(exercise)
41
+ def limited_for?(_exercise)
42
+ false
43
+ end
44
+
45
+ # Tells if this guide container
46
+ # hides the results for students
47
+ def results_hidden_for?(_exercise)
42
48
  false
43
49
  end
44
50
 
@@ -1,7 +1,7 @@
1
1
  module Solvable
2
2
  def submit_solution!(user, attributes={})
3
3
  assignment, _ = find_assignment_and_submit! user, attributes[:content].to_mumuki_solution(language)
4
- try_solve_discussions(user) if assignment.passed?
4
+ try_solve_discussions(user) if assignment.solved?
5
5
  assignment
6
6
  end
7
7
 
@@ -19,7 +19,7 @@ module WithEditor
19
19
  struct id: "content_choice_#{index}",
20
20
  index: index,
21
21
  value: choice,
22
- text: Mumukit::ContentType::Markdown.to_html(choice_text(choice))
22
+ text: choice_text(choice).markdownified
23
23
  end
24
24
  end
25
25
 
@@ -161,6 +161,10 @@ class Exam < ApplicationRecord
161
161
  max_attempts_for(exercise).present?
162
162
  end
163
163
 
164
+ def results_hidden_for?(exercise)
165
+ exercise.choice? && results_hidden_for_choices?
166
+ end
167
+
164
168
  def resettable?
165
169
  false
166
170
  end
@@ -55,6 +55,10 @@ class Exercise < ApplicationRecord
55
55
  guide.done_for?(user)
56
56
  end
57
57
 
58
+ def choice?
59
+ false
60
+ end
61
+
58
62
  def previous
59
63
  sibling_at number.pred
60
64
  end
@@ -128,7 +132,7 @@ class Exercise < ApplicationRecord
128
132
  .merge(settings: self[:settings])
129
133
  .merge(RANDOMIZED_FIELDS.map { |it| [it, self[it]] }.to_h)
130
134
  .symbolize_keys
131
- .tap { |it| it.markdownify!(:hint, :corollary, :description, :teacher_info) if options[:markdownified] }
135
+ .tap { |it| it.markdownified!(:hint, :corollary, :description, :teacher_info) if options[:markdownified] }
132
136
  end
133
137
 
134
138
  def reset!
@@ -162,7 +166,7 @@ class Exercise < ApplicationRecord
162
166
  end
163
167
 
164
168
  def description_context
165
- Mumukit::ContentType::Markdown.to_html splitted_description.first
169
+ splitted_description.first.markdownified
166
170
  end
167
171
 
168
172
  def splitted_description
@@ -170,7 +174,7 @@ class Exercise < ApplicationRecord
170
174
  end
171
175
 
172
176
  def description_task
173
- Mumukit::ContentType::Markdown.to_html splitted_description.drop(1).join("\n")
177
+ splitted_description.drop(1).join("\n").markdownified
174
178
  end
175
179
 
176
180
  def custom?
@@ -192,8 +196,15 @@ class Exercise < ApplicationRecord
192
196
  end
193
197
  end
194
198
 
199
+ # An exercise with hidden results cannot be limited
200
+ # as those exercises can be submitted as many times as the
201
+ # student wants because no result output is given
195
202
  def limited?
196
- navigable_parent.limited_for?(self)
203
+ !results_hidden? && navigable_parent.limited_for?(self)
204
+ end
205
+
206
+ def results_hidden?
207
+ navigable_parent&.results_hidden_for?(self)
197
208
  end
198
209
 
199
210
  def files_for(current_content)
@@ -104,7 +104,7 @@ class Guide < Content
104
104
  .merge(super)
105
105
  .merge(exercises: exercises.map { |it| it.to_resource_h(options) })
106
106
  .merge(language: language.to_embedded_resource_h)
107
- .tap { |it| it.markdownify!(:corollary, :description, :teacher_info) if options[:markdownified] }
107
+ .tap { |it| it.markdownified!(:corollary, :description, :teacher_info) if options[:markdownified] }
108
108
  end
109
109
 
110
110
  def to_markdownified_resource_h
@@ -1,7 +1,7 @@
1
1
  class Stats
2
2
  include ActiveModel::Model
3
3
 
4
- attr_accessor :passed, :passed_with_warnings, :failed, :pending
4
+ attr_accessor :passed, :passed_with_warnings, :failed, :pending, :skipped
5
5
 
6
6
  def submitted
7
7
  passed + passed_with_warnings + failed
@@ -16,7 +16,7 @@ class Stats
16
16
  end
17
17
 
18
18
  def self.from_statuses(statuses)
19
- Stats.new(statuses.inject({passed: 0, passed_with_warnings: 0, failed: 0, pending: 0}) do |accum, status|
19
+ Stats.new(statuses.inject({passed: 0, passed_with_warnings: 0, failed: 0, pending: 0, skipped: 0}) do |accum, status|
20
20
  accum[status.group.to_sym] += 1
21
21
  accum
22
22
  end)
@@ -4,6 +4,7 @@ class User < ApplicationRecord
4
4
  WithUserNavigation,
5
5
  WithReminders,
6
6
  WithDiscussionCreation,
7
+ Disabling,
7
8
  Mumuki::Domain::Helpers::User
8
9
 
9
10
  serialize :permissions, Mumukit::Auth::Permissions
@@ -31,12 +32,14 @@ class User < ApplicationRecord
31
32
 
32
33
  after_initialize :init
33
34
 
34
- enum gender: %i(female male other)
35
+ enum gender: %i(female male other unspecified)
36
+
37
+ belongs_to :avatar, optional: true
35
38
 
36
39
  before_validation :set_uid!
37
40
  validates :uid, presence: true
38
41
 
39
- resource_fields :uid, :social_id, :image_url, :email, :permissions, :verified_first_name, :verified_last_name, *profile_fields
42
+ resource_fields :uid, :social_id, :email, :permissions, :verified_first_name, :verified_last_name, *profile_fields
40
43
 
41
44
  def last_lesson
42
45
  last_guide.try(:lesson)
@@ -89,7 +92,7 @@ class User < ApplicationRecord
89
92
  make_student_of! invitation.course_slug
90
93
  end
91
94
 
92
- def copy_progress_to!(another)
95
+ def transfer_progress_to!(another)
93
96
  transaction do
94
97
  assignments.update_all(submitter_id: another.id)
95
98
  if another.never_submitted? || last_submission_date.try { |it| it > another.last_submission_date }
@@ -105,6 +108,10 @@ class User < ApplicationRecord
105
108
  update! self.class.slice_resource_h json
106
109
  end
107
110
 
111
+ def to_resource_h
112
+ super.merge(image_url: profile_picture)
113
+ end
114
+
108
115
  def verify_name!
109
116
  self.verified_first_name ||= first_name
110
117
  self.verified_last_name ||= last_name
@@ -134,16 +141,19 @@ class User < ApplicationRecord
134
141
  }
135
142
  end
136
143
 
137
- def resubmit!(organization = nil)
138
- organization = Organization.find_by_name(organization) || main_organization
139
- organization.switch!
140
- assignments.each { |it| it.notify! rescue nil }
141
- end
142
-
143
144
  def currently_in_exam?
144
145
  exams.any? { |e| e.in_progress_for? self }
145
146
  end
146
147
 
148
+ def profile_picture
149
+ avatar&.image_url || image_url
150
+ end
151
+
152
+ def bury!
153
+ # TODO change avatar
154
+ update! self.class.buried_profile.merge(accepts_reminders: false, gender: nil, birthdate: nil)
155
+ end
156
+
147
157
  private
148
158
 
149
159
  def set_uid!
@@ -151,6 +161,9 @@ class User < ApplicationRecord
151
161
  end
152
162
 
153
163
  def init
164
+ # Temporarily keep using image_url until avatars are created
165
+ # self.avatar = Avatar.sample unless profile_picture.present?
166
+
154
167
  self.image_url ||= "user_shape.png"
155
168
  end
156
169
 
@@ -166,4 +179,14 @@ class User < ApplicationRecord
166
179
  user[:uid] ||= user[:email]
167
180
  where(uid: user[:uid]).first_or_create(user)
168
181
  end
182
+
183
+ # Call this method once as part of application initialization
184
+ # in order to enable user profile override as part of disabling process
185
+ def self.configure_buried_profile!(profile)
186
+ @buried_profile = profile
187
+ end
188
+
189
+ def self.buried_profile
190
+ (@buried_profile || {}).slice(:first_name, :last_name, :email)
191
+ end
169
192
  end
@@ -0,0 +1,5 @@
1
+ class AddResultsHiddenForChoicesToExam < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :exams, :results_hidden_for_choices, :boolean, default: false
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class CreateAvatars < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :avatars do |t|
4
+ t.string :image_url
5
+ t.string :description
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddAvatarToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :users, :avatar, index: false
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddDisabledAtToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :disabled_at, :datetime
4
+ add_index :users, :disabled_at
5
+ end
6
+ end
@@ -2,4 +2,5 @@ require_relative './exceptions/forbidden_error'
2
2
  require_relative './exceptions/gone_error'
3
3
  require_relative './exceptions/not_found_error'
4
4
  require_relative './exceptions/unauthorized_error'
5
+ require_relative './exceptions/disabled_error'
5
6
  require_relative './exceptions/blocked_forum_error'
@@ -0,0 +1,2 @@
1
+ class Mumuki::Domain::DisabledError < StandardError
2
+ end
@@ -1,5 +1,14 @@
1
1
  class Hash
2
- def markdownify!(*keys)
3
- keys.each { |it| self[it] = Mumukit::ContentType::Markdown.to_html(self[it]) }
2
+ def markdownify!(*keys, **options)
3
+ warn "Don't use markdownify. Use markdownified! instead"
4
+ markdownified! *keys, **options
5
+ end
6
+
7
+ def markdownified!(*keys, **options)
8
+ keys.each { |it| self[it] = self[it].markdownified(**options) }
9
+ end
10
+
11
+ def markdownified(*keys, **options)
12
+ map { |k, v| key.in?(keys) ? v.markdownified(options) : v }.to_h
4
13
  end
5
14
  end
@@ -27,3 +27,47 @@ class String
27
27
  File.extname(self).delete '.'
28
28
  end
29
29
  end
30
+
31
+
32
+ # The nil-safe affable pipeline goes as follow:
33
+ #
34
+ # i18n > markdownified > sanitized > affable
35
+ #
36
+ # Where:
37
+ # * i18n: translates to current locale
38
+ # * markdownified: interpretes markdown in message and generates HTML
39
+ # * sanitized: sanitizes results HTML
40
+ # * affable: changes structure to hide low level details
41
+ #
42
+ # Other classes may polymorphically implement their own
43
+ # markdownified, sanitized and affable methods with similar semantics
44
+ # to extend this pipeline to non-strings
45
+ class String
46
+
47
+ # Creates a humman representation - but not necessary UI - representation
48
+ # of this string by interpreting its markdown as a one-liner and sanitizing it
49
+ def affable
50
+ markdownified(one_liner: true).sanitized
51
+ end
52
+
53
+ # Interprets the markdown on this string, and converts it into HTML
54
+ def markdownified(**options)
55
+ Mumukit::ContentType::Markdown.to_html self, options
56
+ end
57
+
58
+ # Sanitizes this string, escaping unsafe HTML sequences
59
+ def sanitized
60
+ Mumukit::ContentType::Sanitizer.sanitize self
61
+ end
62
+ end
63
+
64
+ class NilClass
65
+ def affable
66
+ end
67
+
68
+ def markdownified(**options)
69
+ end
70
+
71
+ def sanitized
72
+ end
73
+ end
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :book do
3
- name { Faker::Lorem.sentence(3) }
4
- description { Faker::Lorem.sentence(30) }
3
+ name { Faker::Lorem.sentence(word_count: 3) }
4
+ description { Faker::Lorem.sentence(word_count: 30) }
5
5
  slug { "mumuki/mumuki-test-book-#{SecureRandom.uuid}" }
6
6
  end
7
7
  end
@@ -1,12 +1,12 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :chapter do
4
- number { Faker::Number.between(1, 40) }
4
+ number { Faker::Number.between(from: 1, to: 40) }
5
5
  book { Organization.current.book rescue nil }
6
6
 
7
7
  transient do
8
8
  lessons { [] }
9
- name { Faker::Lorem.sentence(3) }
9
+ name { Faker::Lorem.sentence(word_count: 3) }
10
10
  slug { "mumuki/mumuki-test-topic-#{SecureRandom.uuid}" }
11
11
  end
12
12
 
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :exam, traits: [:guide_container] do
4
- duration { Faker::Number.between(10, 60).minutes }
4
+ duration { Faker::Number.between(from: 10, to:60).minutes }
5
5
  organization { Organization.current }
6
6
  start_time { 5.minutes.ago }
7
7
  end_time { 10.minutes.since }
@@ -51,6 +51,13 @@ FactoryBot.define do
51
51
  test { 'dont care' }
52
52
  end
53
53
 
54
+ factory :multiple_choice, parent: :problem do
55
+ name { 'A multiple choice problem' }
56
+ editor { :multiple_choice }
57
+ description { 'Simple multiple choice problem' }
58
+ choices { [{value: 'a', checked: true}, {value: 'b', checked: false }] }
59
+ end
60
+
54
61
  factory :interactive, class: Interactive, parent: :challenge do
55
62
  name { 'An interactive problem' }
56
63
  description { 'Simple interactive problem' }
@@ -11,8 +11,8 @@ FactoryBot.define do
11
11
  trait :guide_container do
12
12
  transient do
13
13
  exercises { [] }
14
- name { Faker::Lorem.sentence(3) }
15
- description { Faker::Lorem.sentence(10) }
14
+ name { Faker::Lorem.sentence(word_count: 3) }
15
+ description { Faker::Lorem.sentence(word_count: 10) }
16
16
  language { create(:language) }
17
17
  slug { "mumuki/mumuki-test-lesson-#{SecureRandom.uuid}" }
18
18
  end
@@ -1,6 +1,6 @@
1
1
  FactoryBot.define do
2
2
  factory :invitation do
3
- code { Faker::Lorem.sentence(6) }
3
+ code { Faker::Lorem.sentence(word_count: 6) }
4
4
  course { "foo/bar" }
5
5
  expiration_date { 5.minutes.since }
6
6
  end
@@ -1,8 +1,8 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :topic do
4
- name { Faker::Lorem::sentence(3) }
5
- description { Faker::Lorem.paragraph(2) }
4
+ name { Faker::Lorem::sentence(word_count: 3) }
5
+ description { Faker::Lorem.paragraph(sentence_count: 2) }
6
6
  slug { "mumuki/mumuki-sample-topic-#{SecureRandom.uuid}" }
7
7
  locale { :en }
8
8
  end
@@ -6,5 +6,6 @@ FactoryBot.define do
6
6
  last_name { Faker::Name.last_name }
7
7
  gender { 1 }
8
8
  birthdate { Date.today }
9
+ avatar { Avatar.new image_url: 'user_shape.png' }
9
10
  end
10
11
  end
@@ -58,7 +58,7 @@ module Mumuki::Domain::Helpers::User
58
58
  ## Profile
59
59
 
60
60
  def full_name
61
- "#{first_name} #{last_name}"
61
+ "#{first_name} #{last_name}".strip
62
62
  end
63
63
 
64
64
  alias_method :name, :full_name
@@ -5,10 +5,6 @@ module Mumuki::Domain::Status::Submission::Skipped
5
5
  true
6
6
  end
7
7
 
8
- def self.passed?
9
- true
10
- end
11
-
12
8
  def self.iconize
13
9
  {class: :success, type: 'check-circle'}
14
10
  end
@@ -23,8 +23,6 @@ module Mumuki::Domain::Status::Submission
23
23
  self
24
24
  end
25
25
 
26
- # Tells if a new, different submission should be tried.
27
- # True for `failed`, `errored` and `passed_with_warnings`
28
26
  def should_retry?
29
27
  false
30
28
  end
@@ -38,6 +36,10 @@ module Mumuki::Domain::Status::Submission
38
36
  end
39
37
 
40
38
  def completed?
41
- passed?
39
+ solved?
40
+ end
41
+
42
+ def solved?
43
+ passed? || skipped?
42
44
  end
43
45
  end
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '7.3.1'
3
+ VERSION = '7.5.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: 7.3.1
4
+ version: 7.5.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-03-25 00:00:00.000000000 Z
11
+ date: 2020-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '4.0'
61
+ version: '4.1'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '4.0'
68
+ version: '4.1'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: mumukit-content-type
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -247,11 +247,13 @@ files:
247
247
  - app/models/api_client.rb
248
248
  - app/models/application_record.rb
249
249
  - app/models/assignment.rb
250
+ - app/models/avatar.rb
250
251
  - app/models/book.rb
251
252
  - app/models/chapter.rb
252
253
  - app/models/complement.rb
253
254
  - app/models/concerns/assistable.rb
254
255
  - app/models/concerns/contextualization.rb
256
+ - app/models/concerns/disabling.rb
255
257
  - app/models/concerns/friendly_name.rb
256
258
  - app/models/concerns/guide_container.rb
257
259
  - app/models/concerns/navigation/parent_navigation.rb
@@ -598,6 +600,10 @@ files:
598
600
  - db/migrate/20191217184525_add_progress_fields_to_indicators.rb
599
601
  - db/migrate/20200127142401_add_private_to_topics_and_books.rb
600
602
  - db/migrate/20200213175736_add_verified_names_to_users.rb
603
+ - db/migrate/20200312181842_add_results_hidden_for_choices_to_exam.rb
604
+ - db/migrate/20200508191543_create_avatars.rb
605
+ - db/migrate/20200518135658_add_avatar_to_users.rb
606
+ - db/migrate/20200527180729_add_disabled_at_to_users.rb
601
607
  - lib/mumuki/domain.rb
602
608
  - lib/mumuki/domain/engine.rb
603
609
  - lib/mumuki/domain/evaluation.rb
@@ -605,6 +611,7 @@ files:
605
611
  - lib/mumuki/domain/evaluation/manual.rb
606
612
  - lib/mumuki/domain/exceptions.rb
607
613
  - lib/mumuki/domain/exceptions/blocked_forum_error.rb
614
+ - lib/mumuki/domain/exceptions/disabled_error.rb
608
615
  - lib/mumuki/domain/exceptions/forbidden_error.rb
609
616
  - lib/mumuki/domain/exceptions/gone_error.rb
610
617
  - lib/mumuki/domain/exceptions/not_found_error.rb