mumuki-domain 7.9.2 → 7.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/app/models/application_record.rb +10 -0
  4. data/app/models/assignment.rb +13 -2
  5. data/app/models/avatar.rb +2 -0
  6. data/app/models/concerns/awardee.rb +27 -0
  7. data/app/models/concerns/contextualization.rb +6 -2
  8. data/app/models/concerns/with_description.rb +2 -5
  9. data/app/models/concerns/with_medal.rb +7 -0
  10. data/app/models/concerns/with_progress.rb +4 -0
  11. data/app/models/concerns/with_terms_acceptance.rb +57 -0
  12. data/app/models/content.rb +3 -2
  13. data/app/models/discussion.rb +8 -1
  14. data/app/models/indicator.rb +14 -1
  15. data/app/models/medal.rb +3 -0
  16. data/app/models/organization.rb +51 -4
  17. data/app/models/term.rb +40 -0
  18. data/app/models/usage.rb +5 -0
  19. data/app/models/user.rb +17 -10
  20. data/db/migrate/20200828152812_create_medals.rb +8 -0
  21. data/db/migrate/20200828162829_add_medal_to_content.rb +7 -0
  22. data/db/migrate/20200915123020_add_polymorphic_avatars_to_users.rb +6 -0
  23. data/db/migrate/20200915131621_add_once_completed_to_indicators.rb +5 -0
  24. data/db/migrate/20201007143455_add_terms_for_user.rb +14 -0
  25. data/db/migrate/20201009193949_add_status_updated_fields_to_discussion.rb +6 -0
  26. data/db/migrate/20201019191036_add_misplaced_flag_to_submission.rb +5 -0
  27. data/db/migrate/20201026222942_add_display_name_and_description_to_organizations.rb +6 -0
  28. data/db/migrate/20201026225312_add_organization_wins_page_flag.rb +5 -0
  29. data/db/migrate/20201027134205_add_immersible_to_organization.rb +5 -0
  30. data/db/migrate/20201027152806_create_terms.rb +12 -0
  31. data/lib/mumuki/domain/extensions/array.rb +13 -0
  32. data/lib/mumuki/domain/factories.rb +3 -1
  33. data/lib/mumuki/domain/factories/medal_factory.rb +6 -0
  34. data/lib/mumuki/domain/factories/organization_factory.rb +1 -0
  35. data/lib/mumuki/domain/factories/term_factory.rb +7 -0
  36. data/lib/mumuki/domain/helpers/organization.rb +4 -0
  37. data/lib/mumuki/domain/helpers/user.rb +18 -1
  38. data/lib/mumuki/domain/incognito.rb +28 -0
  39. data/lib/mumuki/domain/locales/activerecord/en.yml +2 -0
  40. data/lib/mumuki/domain/locales/activerecord/es.yml +2 -0
  41. data/lib/mumuki/domain/locales/defaults/en.yml +4 -0
  42. data/lib/mumuki/domain/locales/defaults/es-CL.yml +4 -0
  43. data/lib/mumuki/domain/locales/defaults/es.yml +4 -0
  44. data/lib/mumuki/domain/locales/defaults/pt.yml +4 -0
  45. data/lib/mumuki/domain/organization/profile.rb +2 -1
  46. data/lib/mumuki/domain/submission/base.rb +4 -2
  47. data/lib/mumuki/domain/submission/solution.rb +4 -0
  48. data/lib/mumuki/domain/version.rb +1 -1
  49. metadata +28 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ebe5d37552a117a8d4ee657170a55eefdea340126a6b44f4a31b670420029b4
4
- data.tar.gz: 8efe4d2b2e478ee887d7c346031e2b0fc87ea3c243991e6cd8d42909c1b361aa
3
+ metadata.gz: 14e176a8030ca77ad3bfe8319f3cbad4eccef1f66b5f18ed293261a2b64bde1b
4
+ data.tar.gz: 6cedd7c76389861d2bbf2f850d4a3f1f66b57f2277ecd6ebe3fb1dcd12b1cf84
5
5
  SHA512:
6
- metadata.gz: ac6768c2f693a49e9d9d63e4c5c55100d98705edd6e4141b49c69c63078a7a72d7b843be81f289f1ea1770abc464bfa365f4895efa79e7caae2504abadac0db8
7
- data.tar.gz: 12b89e6ebf0e13bcdb0634eb18abfb96bb816296a482503fe504ad54860993a87b93337593fb9e77a7437907dc88ae1d30916dc104e3d8f4f250ab8272fbef7a
6
+ metadata.gz: 3bf0726d9a3b8b0fb2e3a869a6db125e98a67934285780566e903994a16c0693c7fb879fd044a94807cce5ea03102955a9b69096fb78d6599aa39842153c2866
7
+ data.tar.gz: aed09dedbfe35d98c18f3d6c290b96650e95973f549990402a2e02af831fb29b7d040c1ad8d79d6dff0343d81006c686694cc4330bd4a7e6992b8894e0d11139
data/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ [![Build Status](https://travis-ci.com/mumuki/mumuki-domain.svg?branch=master)](https://travis-ci.com/mumuki/mumuki-domain)
2
+ [![Code Climate](https://codeclimate.com/github/mumuki/mumuki-domain/badges/gpa.svg)](https://codeclimate.com/github/mumuki/mumuki-domain)
3
+ [![Test Coverage](https://codeclimate.com/github/mumuki/mumuki-domain/badges/coverage.svg)](https://codeclimate.com/github/mumuki/mumuki-domain)
4
+ [![Issue Count](https://codeclimate.com/github/mumuki/mumuki-domain/badges/issue_count.svg)](https://codeclimate.com/github/mumuki/mumuki-domain)
5
+
6
+
1
7
  # Resource Hashes
2
8
 
3
9
  ## Runner Hashes
@@ -3,6 +3,16 @@ class ApplicationRecord < ActiveRecord::Base
3
3
 
4
4
  delegate :whitelist_attributes, to: :class
5
5
 
6
+ def self.teaser_on(*args)
7
+ args.each do |selector|
8
+ teaser_selector = "#{selector}_teaser"
9
+ define_method teaser_selector do
10
+ send(selector)&.markdown_paragraphs&.first
11
+ end
12
+ markdown_on teaser_selector, skip_sanitization: true
13
+ end
14
+ end
15
+
6
16
  def self.defaults(&block)
7
17
  after_initialize :defaults, if: :new_record?
8
18
  define_method :defaults, &block
@@ -91,7 +91,14 @@ class Assignment < Progress
91
91
  end
92
92
 
93
93
  def notify!
94
- Mumukit::Nuntius.notify! 'submissions', to_resource_h unless Organization.silenced?
94
+ unless Organization.silenced?
95
+ update_misplaced!(current_notification_contexts.size > 1)
96
+ Mumukit::Nuntius.notify! 'submissions', to_resource_h
97
+ end
98
+ end
99
+
100
+ def current_notification_contexts
101
+ [Organization.current, submitter.current_immersive_context_at(exercise)].uniq
95
102
  end
96
103
 
97
104
  def notify_to_accessible_organizations!
@@ -169,7 +176,7 @@ class Assignment < Progress
169
176
 
170
177
  def to_resource_h
171
178
  excluded_fields = %i(created_at exercise_id id organization_id parent_id solution submission_id
172
- submission_status submitted_at submitter_id top_submission_status updated_at)
179
+ submission_status submitted_at submitter_id top_submission_status updated_at misplaced)
173
180
 
174
181
  as_json(except: excluded_fields,
175
182
  include: {
@@ -238,6 +245,10 @@ class Assignment < Progress
238
245
  self.top_submission_status = submission_status unless submission_status.improved_by?(top_submission_status)
239
246
  end
240
247
 
248
+ def update_misplaced!(value)
249
+ update! misplaced: value if value != misplaced?
250
+ end
251
+
241
252
  private
242
253
 
243
254
  def update_submissions_count!
@@ -1,4 +1,6 @@
1
1
  class Avatar < ApplicationRecord
2
+ has_many :users, as: :avatar
3
+
2
4
  include WithTargetAudience
3
5
 
4
6
  def self.sample_for(user)
@@ -0,0 +1,27 @@
1
+ module Awardee
2
+ def acquired_medals(organization=Organization.current)
3
+ @acquired_medals ||= medals_for awarded_contents_in(organization)
4
+ end
5
+
6
+ def unacquired_medals(organization=Organization.current)
7
+ @unacquired_medals ||= medals_for unawarded_contents_in(organization)
8
+ end
9
+
10
+ private
11
+
12
+ def medals_for(content)
13
+ content.map(&:medal)
14
+ end
15
+
16
+ def awarded_contents_in(organization)
17
+ awardable_contents_in(organization).first
18
+ end
19
+
20
+ def unawarded_contents_in(organization)
21
+ awardable_contents_in(organization).second
22
+ end
23
+
24
+ def awardable_contents_in(organization)
25
+ @awardable_contents_in ||= organization.awardable_contents.partition { |c| c.once_completed_for? self, organization }
26
+ end
27
+ end
@@ -51,7 +51,7 @@ module Contextualization
51
51
  end
52
52
 
53
53
  def single_visible_test_result?
54
- test_results.size == 1 && visible_success_output?
54
+ test_results.single? && visible_success_output?
55
55
  end
56
56
 
57
57
  def first_test_result
@@ -131,8 +131,12 @@ module Contextualization
131
131
  .compact
132
132
  .merge(
133
133
  title: it[:title].affable,
134
- result: it[:result].sanitized,
134
+ result: it[:result],
135
135
  status: it[:status])
136
136
  end
137
137
  end
138
+
139
+ def sanitized_affable_test_results
140
+ affable_test_results.each { |it| it[:result] = it[:result]&.sanitized }
141
+ end
138
142
  end
@@ -2,11 +2,8 @@ module WithDescription
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- markdown_on :description, :description_teaser, skip_sanitization: true
5
+ markdown_on :description, skip_sanitization: true
6
+ teaser_on :description
6
7
  validates_presence_of :description
7
8
  end
8
-
9
- def description_teaser
10
- description.markdown_paragraphs.first
11
- end
12
9
  end
@@ -0,0 +1,7 @@
1
+ module WithMedal
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ belongs_to :medal, optional: true
6
+ end
7
+ end
@@ -23,6 +23,10 @@ module WithProgress
23
23
  progress_for(user, organization).completed?
24
24
  end
25
25
 
26
+ def once_completed_for?(user, organization)
27
+ progress_for(user, organization).once_completed?
28
+ end
29
+
26
30
  private
27
31
 
28
32
  def structural_children_changed?(old_structural_children)
@@ -0,0 +1,57 @@
1
+ module WithTermsAcceptance
2
+ def has_forum_terms_to_accept?
3
+ !has_accepted_all?(forum_terms)
4
+ end
5
+
6
+ def has_profile_terms_to_accept?
7
+ !has_accepted_all?(profile_terms)
8
+ end
9
+
10
+ def forum_terms
11
+ @forum_terms ||= Term.forum_related_terms
12
+ end
13
+
14
+ def profile_terms
15
+ @profile_terms ||= Term.profile_terms_for(self)
16
+ end
17
+
18
+ def accept_profile_terms!
19
+ accept_terms! profile_terms
20
+ end
21
+
22
+ def accept_forum_terms!
23
+ accept_terms! forum_terms
24
+ end
25
+
26
+ def has_accepted?(term)
27
+ term_accepted_at_for(term.scope).try { |it| it > term.updated_at }.present?
28
+ end
29
+
30
+ private
31
+
32
+ def unaccepted_terms_in(terms)
33
+ terms.reject { |term| has_accepted? term}
34
+ end
35
+
36
+ def unaccepted_terms_scopes_in(terms)
37
+ unaccepted_terms_in(terms).map(&:scope)
38
+ end
39
+
40
+ def has_accepted_all?(terms)
41
+ unaccepted_terms_in(terms).blank?
42
+ end
43
+
44
+ def term_accepted_at_for(role)
45
+ send term_acceptance_field_for(role)
46
+ end
47
+
48
+ def term_acceptance_field_for(role)
49
+ "#{role}_terms_accepted_at"
50
+ end
51
+
52
+ def accept_terms!(terms)
53
+ update! unaccepted_terms_scopes_in(terms).to_h { |scope| [term_acceptance_field_for(scope), Time.now] }
54
+ end
55
+
56
+ end
57
+
@@ -4,10 +4,11 @@ class Content < ApplicationRecord
4
4
  include Mumuki::Domain::Syncable
5
5
  include WithDescription
6
6
  include WithLocale
7
- include WithSlug
8
- include WithUsages
7
+ include WithMedal
9
8
  include WithName
10
9
  include WithProgress
10
+ include WithSlug
11
+ include WithUsages
11
12
 
12
13
  def to_resource_h(*args)
13
14
  to_expanded_resource_h(*args).compact
@@ -4,7 +4,10 @@ class Discussion < ApplicationRecord
4
4
  belongs_to :item, polymorphic: true
5
5
  has_many :messages, -> { order(:created_at) }, dependent: :destroy
6
6
  belongs_to :initiator, class_name: 'User'
7
+
7
8
  belongs_to :last_moderator_access_by, class_name: 'User', optional: true
9
+ belongs_to :status_updated_by, class_name: 'User', optional: true
10
+
8
11
  belongs_to :exercise, foreign_type: :exercise, foreign_key: 'item_id'
9
12
  belongs_to :organization
10
13
  has_many :subscriptions
@@ -106,7 +109,11 @@ class Discussion < ApplicationRecord
106
109
  end
107
110
 
108
111
  def update_status!(status, user)
109
- update!(status: status) if reachable_status_for?(user, status)
112
+ if reachable_status_for?(user, status)
113
+ update! status: status,
114
+ status_updated_by: user,
115
+ status_updated_at: Time.now
116
+ end
110
117
  end
111
118
 
112
119
  def has_messages?
@@ -37,6 +37,7 @@ class Indicator < Progress
37
37
  def clean!
38
38
  self.dirty_by_submission = false
39
39
  self.dirty_by_content_change = false
40
+ self.once_completed ||= all_children_passed?
40
41
  end
41
42
 
42
43
  def refresh_children_count!
@@ -54,7 +55,15 @@ class Indicator < Progress
54
55
 
55
56
  def completed?
56
57
  rebuild!
57
- children_passed_count == children_count
58
+ all_children_passed?
59
+ end
60
+
61
+ def once_completed?
62
+ self.once_completed || completed?
63
+ end
64
+
65
+ def self.delete_all_for!(content, organization)
66
+ where(content: content, organization: organization).delete_all
58
67
  end
59
68
 
60
69
  private
@@ -74,4 +83,8 @@ class Indicator < Progress
74
83
  def parent_content
75
84
  content.usage_in_organization(organization).structural_parent
76
85
  end
86
+
87
+ def all_children_passed?
88
+ children_passed_count == children_count
89
+ end
77
90
  end
@@ -0,0 +1,3 @@
1
+ class Medal < ApplicationRecord
2
+ has_many :users, as: :avatar
3
+ end
@@ -11,10 +11,12 @@ class Organization < ApplicationRecord
11
11
  serialize :settings, Mumuki::Domain::Organization::Settings
12
12
  serialize :theme, Mumuki::Domain::Organization::Theme
13
13
 
14
- markdown_on :description
14
+ markdown_on :description, :display_description, :page_description
15
+ teaser_on :display_description
15
16
 
16
17
  validate :ensure_consistent_public_login
17
18
  validate :ensure_valid_activity_range
19
+ validate :ensure_not_immersive_and_immersible
18
20
 
19
21
  belongs_to :book
20
22
  has_many :usages
@@ -97,11 +99,13 @@ class Organization < ApplicationRecord
97
99
  end
98
100
 
99
101
  def title_suffix
100
- central? ? '' : " - #{book.name}"
102
+ warn "Don't use title_suffix. Use page_name instead"
103
+ " - #{page_name}"
101
104
  end
102
105
 
103
106
  def site_name
104
- central? ? 'mumuki' : name
107
+ warn "Don't use site_name. Use display_name instead"
108
+ name
105
109
  end
106
110
 
107
111
  # Tells if the given user can
@@ -130,12 +134,55 @@ class Organization < ApplicationRecord
130
134
  update! progressive_display_lookahead: lookahead
131
135
  end
132
136
 
137
+ def progressive_display_lookahead=(lookahead)
138
+ self[:progressive_display_lookahead] = lookahead.to_i.positive? ? lookahead : nil
139
+ end
140
+
141
+ # ==============
142
+ # Display fields
143
+ # ==============
144
+
133
145
  def display_name
134
- name.gsub(/\W/, ' ').titleize
146
+ self[:display_name].presence || name.try { |it| it.gsub(/\W/, ' ').titleize }
147
+ end
148
+
149
+ def display_description
150
+ self[:display_description].presence || I18n.t('defaults.organization.display_description', name: name)
151
+ end
152
+
153
+ # ===========
154
+ # Page fields
155
+ # ===========
156
+
157
+ # Since an organization has a single book, both concepts may be merged
158
+ # when describing a site. In such contexts, wins_page?
159
+ # control whether the book or the organization header fields are
160
+ # more important
161
+
162
+ def page_name
163
+ wins_page? ? display_name : book.name
164
+ end
165
+
166
+ def page_description
167
+ wins_page? ? display_description : book.description
168
+ end
169
+
170
+ def all_contents
171
+ Usage.where(organization: self)
172
+ .group_by(&:item_type)
173
+ .flat_map { |item_type, item| item_type.constantize.where(id: item.map(&:item_id)) }
174
+ end
175
+
176
+ def awardable_contents
177
+ gamification_enabled? ? all_contents.select(&:medal_id) : []
135
178
  end
136
179
 
137
180
  private
138
181
 
182
+ def ensure_not_immersive_and_immersible
183
+ errors.add(:immersible, :cannot_be_immersive) if immersible? && immersive?
184
+ end
185
+
139
186
  def ensure_consistent_public_login
140
187
  errors.add(:base, :consistent_public_login) if settings.customized_login_methods? && public?
141
188
  end
@@ -0,0 +1,40 @@
1
+ class Term < ApplicationRecord
2
+ attribute :locale, :string, default: 'es'
3
+ markdown_on :content
4
+
5
+ GENERAL = %w(legal privacy student)
6
+ ROLE_SPECIFIC = %w(headmaster janitor teacher moderator)
7
+ FORUM_RELATED = %w(forum)
8
+
9
+ validates :locale, uniqueness: { scope: :scope }
10
+ validates :content, :scope, :header, presence: true
11
+
12
+ def self.terms_for(scope, locale)
13
+ where(scope: scope, locale: locale)
14
+ end
15
+
16
+ def self.profile_terms_for(user, locale = I18n.locale)
17
+ general_terms(locale) + role_specific_terms_for(user, locale)
18
+ end
19
+
20
+ def self.role_specific_terms_for(user, locale = I18n.locale)
21
+ terms_for(current_role_terms_for(user), locale)
22
+ end
23
+
24
+ def self.general_terms(locale = I18n.locale)
25
+ terms_for(GENERAL, locale)
26
+ end
27
+
28
+ def self.forum_related_terms(locale = I18n.locale)
29
+ terms_for(FORUM_RELATED, locale)
30
+ end
31
+
32
+ def self.current_role_terms_for(user)
33
+ return [] unless user.present?
34
+ (user.any_granted_roles & ROLE_SPECIFIC).to_a
35
+ end
36
+
37
+ def accepted_by?(user)
38
+ user.term_accepted_at_for(scope).try { |it| it > updated_at }.present?
39
+ end
40
+ end
@@ -8,6 +8,7 @@ class Usage < ApplicationRecord
8
8
 
9
9
  before_save :set_slug
10
10
  before_destroy :destroy_children_usages!
11
+ before_destroy :delete_associated_indicators!
11
12
 
12
13
  def self.destroy_all_where(query)
13
14
  where(query).destroy_all
@@ -27,6 +28,10 @@ class Usage < ApplicationRecord
27
28
 
28
29
  private
29
30
 
31
+ def delete_associated_indicators!
32
+ Indicator.delete_all_for!(item, organization)
33
+ end
34
+
30
35
  def set_slug
31
36
  self.slug = item.slug
32
37
  end
@@ -4,13 +4,15 @@ class User < ApplicationRecord
4
4
  WithUserNavigation,
5
5
  WithReminders,
6
6
  WithDiscussionCreation,
7
+ Awardee,
7
8
  Disabling,
9
+ WithTermsAcceptance,
8
10
  Mumuki::Domain::Helpers::User
9
11
 
10
12
  serialize :permissions, Mumukit::Auth::Permissions
11
13
 
12
- has_many :assignments, foreign_key: :submitter_id
13
14
 
15
+ has_many :assignments, foreign_key: :submitter_id
14
16
  has_many :messages, -> { order(created_at: :desc) }, through: :assignments
15
17
 
16
18
  has_many :submitted_exercises, through: :assignments, class_name: 'Exercise', source: :exercise
@@ -31,11 +33,12 @@ class User < ApplicationRecord
31
33
  has_many :exams, through: :exam_authorizations
32
34
 
33
35
  enum gender: %i(female male other unspecified)
34
- belongs_to :avatar, optional: true
36
+ belongs_to :avatar, polymorphic: true, optional: true
35
37
 
36
38
  before_validation :set_uid!
37
39
  validates :uid, presence: true
38
40
 
41
+ validates :terms_of_service, acceptance: true
39
42
  after_save :welcome_to_new_organizations!, if: :gained_access_to_new_orga?
40
43
  after_initialize :init
41
44
  PLACEHOLDER_IMAGE_URL = 'user_shape.png'.freeze
@@ -236,6 +239,18 @@ class User < ApplicationRecord
236
239
  end
237
240
  end
238
241
 
242
+ def current_organic_context
243
+ Organization.current? ? Organization.current : main_organization
244
+ end
245
+
246
+ def current_immersive_context_at(exercise)
247
+ if Organization.current?
248
+ immersive_organization_at(exercise) || Organization.current
249
+ else
250
+ main_organization
251
+ end
252
+ end
253
+
239
254
  private
240
255
 
241
256
  def welcome_to_new_organizations!
@@ -289,12 +304,4 @@ class User < ApplicationRecord
289
304
  def self.buried_profile
290
305
  (@buried_profile || {}).slice(:first_name, :last_name, :email)
291
306
  end
292
-
293
- def current_organic_context
294
- if Organization.current?
295
- Organization.current
296
- else
297
- main_organization
298
- end
299
- end
300
307
  end
@@ -0,0 +1,8 @@
1
+ class CreateMedals < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :medals do |t|
4
+ t.string :image_url
5
+ t.string :description
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class AddMedalToContent < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :books, :medal, index: false
4
+ add_reference :guides, :medal, index: false
5
+ add_reference :topics, :medal, index: false
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class AddPolymorphicAvatarsToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :avatar_type, :string, default: 'Avatar'
4
+ add_index :users, [:avatar_type, :avatar_id]
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddOnceCompletedToIndicators < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :indicators, :once_completed, :boolean
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ class AddTermsForUser < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :headmaster_terms_accepted_at, :datetime
4
+ add_column :users, :janitor_terms_accepted_at, :datetime
5
+ add_column :users, :moderator_terms_accepted_at, :datetime
6
+ add_column :users, :student_terms_accepted_at, :datetime
7
+ add_column :users, :teacher_terms_accepted_at, :datetime
8
+
9
+ add_column :users, :privacy_terms_accepted_at, :datetime
10
+ add_column :users, :legal_terms_accepted_at, :datetime
11
+
12
+ add_column :users, :forum_terms_accepted_at, :datetime
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ class AddStatusUpdatedFieldsToDiscussion < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :discussions, :status_updated_by, index: true
4
+ add_column :discussions, :status_updated_at, :datetime
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddMisplacedFlagToSubmission < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :assignments, :misplaced, :boolean
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddDisplayNameAndDescriptionToOrganizations < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :display_name, :text
4
+ add_column :organizations, :display_description, :text
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddOrganizationWinsPageFlag < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :wins_page, :boolean
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddImmersibleToOrganization < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :immersible, :boolean
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ class CreateTerms < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :terms do |t|
4
+ t.string :locale
5
+ t.string :scope
6
+ t.text :content
7
+ t.text :header
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -2,6 +2,19 @@ class Array
2
2
  def insert_last(element)
3
3
  self + [element]
4
4
  end
5
+
6
+ def single
7
+ first if single?
8
+ end
9
+
10
+ def single!
11
+ raise 'There is more than one element' unless single?
12
+ first
13
+ end
14
+
15
+ def single?
16
+ size == 1
17
+ end
5
18
  end
6
19
 
7
20
  class NilClass
@@ -12,8 +12,10 @@ require_relative './factories/guide_factory'
12
12
  require_relative './factories/invitation_factory'
13
13
  require_relative './factories/lesson_factory'
14
14
  require_relative './factories/login_settings_factory'
15
+ require_relative './factories/medal_factory'
15
16
  require_relative './factories/message_factory'
16
17
  require_relative './factories/organization_factory'
18
+ require_relative './factories/term_factory'
17
19
  require_relative './factories/topic_factory'
18
- require_relative './factories/user_factory'
19
20
  require_relative './factories/usage_factory'
21
+ require_relative './factories/user_factory'
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :medal do
3
+ image_url { Faker::Internet.url }
4
+ end
5
+ end
6
+
@@ -25,6 +25,7 @@ FactoryBot.define do
25
25
 
26
26
  factory :test_organization, parent: :public_organization do
27
27
  name { 'test' }
28
+ immersible { true }
28
29
  book { create(:book, name: 'test', slug: 'mumuki/mumuki-the-book') }
29
30
  end
30
31
 
@@ -0,0 +1,7 @@
1
+ FactoryBot.define do
2
+ factory :term do
3
+ content { Faker::Lorem.sentence(word_count: 2000) }
4
+ header { Faker::Lorem.sentence(word_count: 5) }
5
+ end
6
+ end
7
+
@@ -28,6 +28,10 @@ module Mumuki::Domain::Helpers::Organization
28
28
  name == 'base'
29
29
  end
30
30
 
31
+ def immersed_in?(other)
32
+ immersible? && other.immersive? && target_audience == other.target_audience
33
+ end
34
+
31
35
  def switch!
32
36
  Mumukit::Platform::Organization.switch! self
33
37
  end
@@ -15,6 +15,7 @@ module Mumuki::Domain::Helpers::User
15
15
  :protect_permissions_assignment!,
16
16
  :student_granted_organizations,
17
17
  :any_granted_organizations,
18
+ :any_granted_roles,
18
19
  to: :permissions
19
20
 
20
21
  def platform_class_name
@@ -87,8 +88,24 @@ module Mumuki::Domain::Helpers::User
87
88
  student_granted_organizations.first || any_granted_organizations.first
88
89
  end
89
90
 
91
+ # Deprecated: use `immersive_organization_at` which
92
+ # properly looks for a single immersive organization taking
93
+ # current organization and path into account
90
94
  def has_immersive_main_organization?
91
- !!main_organization.try(&:immersive?)
95
+ main_organization.try(&:immersive?).present?
96
+ end
97
+
98
+ def immersive_organization_at(path_item, current = Organization.current)
99
+ immersive_organizations_at(path_item, current).single
100
+ end
101
+
102
+ def immersive_organizations_at(path_item, current = Organization.current)
103
+ return [] unless current.immersible?
104
+
105
+ usage_filter = path_item ? lambda { |it| path_item.used_in?(it) } : lambda { |_| true }
106
+ student_granted_organizations
107
+ .select { |it| current.immersed_in?(it) }
108
+ .select(&usage_filter)
92
109
  end
93
110
 
94
111
  ## API Exposure
@@ -48,6 +48,34 @@ module Mumuki::Domain
48
48
  false
49
49
  end
50
50
 
51
+ def current_immersive_context_at(_)
52
+ nil
53
+ end
54
+
55
+ def immersive_organizations_at(_)
56
+ []
57
+ end
58
+
59
+ def any_granted_roles
60
+ []
61
+ end
62
+
63
+ # ========
64
+ # Terms
65
+ # ========
66
+
67
+ def accepted_profile_terms?
68
+ true
69
+ end
70
+
71
+ def accepted_forum_terms?
72
+ true
73
+ end
74
+
75
+ def has_accepted?(term)
76
+ false
77
+ end
78
+
51
79
  # ========
52
80
  # Visiting
53
81
  # ========
@@ -28,3 +28,5 @@ en:
28
28
  base:
29
29
  consistent_public_login: 'A public organization can not restrict login methods'
30
30
  invalid_activity_range: 'The organization activity range are not valid'
31
+ immersible:
32
+ cannot_be_immersive: 'An organization cannot be immersible and immersive at the same time'
@@ -45,6 +45,8 @@ es:
45
45
  base:
46
46
  consistent_public_login: 'Una organización pública no puede restringir los métodos de login'
47
47
  invalid_activity_range: 'La fecha de deshabilitación no puede ser anterior a la de inicio del recorrido'
48
+ immersible:
49
+ cannot_be_immersive: 'Una organización no puede ser inmersible e inmersiva a la vez'
48
50
  models:
49
51
  exercise:
50
52
  one: Ejercicio
@@ -0,0 +1,4 @@
1
+ en:
2
+ defaults:
3
+ organization:
4
+ display_description: "In this site you will find programming contents about %{name}"
@@ -0,0 +1,4 @@
1
+ es-CL:
2
+ defaults:
3
+ organization:
4
+ display_description: "En este sitio encontrarás el contenido sobre programación de %{name}"
@@ -0,0 +1,4 @@
1
+ es:
2
+ defaults:
3
+ organization:
4
+ display_description: "En este sitio encontrarás el contenido sobre programación de %{name}"
@@ -0,0 +1,4 @@
1
+ pt:
2
+ defaults:
3
+ organization:
4
+ display_description: "Neste site você encontrará conteúdos de programação sobre %{name}"
@@ -12,7 +12,8 @@ class Mumuki::Domain::Organization::Profile < Mumukit::Platform::Model
12
12
  :terms_of_service,
13
13
  :community_link,
14
14
  :errors_explanations,
15
- :welcome_email_template
15
+ :welcome_email_template,
16
+ :welcome_email_sender
16
17
 
17
18
  def locale_json
18
19
  locale_h.to_json
@@ -27,8 +27,10 @@ class Mumuki::Domain::Submission::Base
27
27
  results
28
28
  end
29
29
 
30
- def dry_run!(assignment, evaluation)
31
- evaluation.evaluate! assignment, self
30
+ # By default evaluation strategy is ignored.
31
+ # Subclasses may override this behaviour
32
+ def dry_run!(assignment, _evaluation)
33
+ evaluate! assignment
32
34
  end
33
35
 
34
36
  def with_client_result(result)
@@ -1,6 +1,10 @@
1
1
  class Mumuki::Domain::Submission::Solution < Mumuki::Domain::Submission::PersistentSubmission
2
2
  attr_accessor :content
3
3
 
4
+ def dry_run!(assignment, evaluation)
5
+ evaluation.evaluate! assignment, self
6
+ end
7
+
4
8
  def try_evaluate!(assignment)
5
9
  assignment
6
10
  .run_tests!({client_result: client_result}.compact.merge(content: content))
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '7.9.2'
3
+ VERSION = '7.12.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: 7.9.2
4
+ version: 7.12.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: 2020-09-17 00:00:00.000000000 Z
11
+ date: 2020-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '7.8'
33
+ version: '7.9'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '7.8'
40
+ version: '7.9'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mumukit-assistant
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.13'
89
+ version: '1.18'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.13'
96
+ version: '1.18'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: mumukit-directives
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -252,6 +252,7 @@ files:
252
252
  - app/models/chapter.rb
253
253
  - app/models/complement.rb
254
254
  - app/models/concerns/assistable.rb
255
+ - app/models/concerns/awardee.rb
255
256
  - app/models/concerns/container.rb
256
257
  - app/models/concerns/contextualization.rb
257
258
  - app/models/concerns/disabling.rb
@@ -281,6 +282,7 @@ files:
281
282
  - app/models/concerns/with_language.rb
282
283
  - app/models/concerns/with_layout.rb
283
284
  - app/models/concerns/with_locale.rb
285
+ - app/models/concerns/with_medal.rb
284
286
  - app/models/concerns/with_messages.rb
285
287
  - app/models/concerns/with_name.rb
286
288
  - app/models/concerns/with_number.rb
@@ -294,6 +296,7 @@ files:
294
296
  - app/models/concerns/with_scoped_queries/sort.rb
295
297
  - app/models/concerns/with_slug.rb
296
298
  - app/models/concerns/with_target_audience.rb
299
+ - app/models/concerns/with_terms_acceptance.rb
297
300
  - app/models/concerns/with_usages.rb
298
301
  - app/models/concerns/with_user_navigation.rb
299
302
  - app/models/content.rb
@@ -315,11 +318,13 @@ files:
315
318
  - app/models/invitation.rb
316
319
  - app/models/language.rb
317
320
  - app/models/lesson.rb
321
+ - app/models/medal.rb
318
322
  - app/models/message.rb
319
323
  - app/models/organization.rb
320
324
  - app/models/progress.rb
321
325
  - app/models/stats.rb
322
326
  - app/models/subscription.rb
327
+ - app/models/term.rb
323
328
  - app/models/topic.rb
324
329
  - app/models/upvote.rb
325
330
  - app/models/usage.rb
@@ -620,6 +625,17 @@ files:
620
625
  - db/migrate/20200730221001_add_trusted_for_forum_to_user.rb
621
626
  - db/migrate/20200731081757_add_last_moderator_access_fields_to_discussion.rb
622
627
  - db/migrate/20200804191643_add_incognito_mode_enabled_to_organization.rb
628
+ - db/migrate/20200828152812_create_medals.rb
629
+ - db/migrate/20200828162829_add_medal_to_content.rb
630
+ - db/migrate/20200915123020_add_polymorphic_avatars_to_users.rb
631
+ - db/migrate/20200915131621_add_once_completed_to_indicators.rb
632
+ - db/migrate/20201007143455_add_terms_for_user.rb
633
+ - db/migrate/20201009193949_add_status_updated_fields_to_discussion.rb
634
+ - db/migrate/20201019191036_add_misplaced_flag_to_submission.rb
635
+ - db/migrate/20201026222942_add_display_name_and_description_to_organizations.rb
636
+ - db/migrate/20201026225312_add_organization_wins_page_flag.rb
637
+ - db/migrate/20201027134205_add_immersible_to_organization.rb
638
+ - db/migrate/20201027152806_create_terms.rb
623
639
  - lib/mumuki/domain.rb
624
640
  - lib/mumuki/domain/area.rb
625
641
  - lib/mumuki/domain/engine.rb
@@ -656,8 +672,10 @@ files:
656
672
  - lib/mumuki/domain/factories/invitation_factory.rb
657
673
  - lib/mumuki/domain/factories/lesson_factory.rb
658
674
  - lib/mumuki/domain/factories/login_settings_factory.rb
675
+ - lib/mumuki/domain/factories/medal_factory.rb
659
676
  - lib/mumuki/domain/factories/message_factory.rb
660
677
  - lib/mumuki/domain/factories/organization_factory.rb
678
+ - lib/mumuki/domain/factories/term_factory.rb
661
679
  - lib/mumuki/domain/factories/topic_factory.rb
662
680
  - lib/mumuki/domain/factories/usage_factory.rb
663
681
  - lib/mumuki/domain/factories/user_factory.rb
@@ -675,6 +693,10 @@ files:
675
693
  - lib/mumuki/domain/locales/console_submission/es-CL.yml
676
694
  - lib/mumuki/domain/locales/console_submission/es.yml
677
695
  - lib/mumuki/domain/locales/console_submission/pt.yml
696
+ - lib/mumuki/domain/locales/defaults/en.yml
697
+ - lib/mumuki/domain/locales/defaults/es-CL.yml
698
+ - lib/mumuki/domain/locales/defaults/es.yml
699
+ - lib/mumuki/domain/locales/defaults/pt.yml
678
700
  - lib/mumuki/domain/organization.rb
679
701
  - lib/mumuki/domain/organization/profile.rb
680
702
  - lib/mumuki/domain/organization/settings.rb