mumuki-domain 7.8.1 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f501e48aa71e7422741f0223c9ac221b1536bf0171dfd58a5ab2158d4cc215a
4
- data.tar.gz: fe3c3520675d5b9e066dfeb7682d86dc42522afb88d48e3e81245b3f28a750ea
3
+ metadata.gz: e4d324f2eb4b3e06fb42a0aabbbd007c4c828569899c89a789ad681432bb97ba
4
+ data.tar.gz: 3f540fc72a7180e247ee11535316d977920872ecc888c77c7c68346eae0c276e
5
5
  SHA512:
6
- metadata.gz: 7925dac13f2ba1b0e5e5494bd13c167c521fee0dec2ffe5a1024cdc0fc4000ba2ad82786a2d5aa96f354ae147689cc2b763dc03cafa940cd77c1a8fd137e0a71
7
- data.tar.gz: 3f51ba4d74fbd60a6fe1a95e6602ec1539d5a63c185d9468b9a5915af14103d9f264cd0f1a0dc0d7084db444c6304c6129130deab4a5f80f7a6e264a52398070
6
+ metadata.gz: ac7828f09dec30a19e4f4b96bfcfb472fe7fcfc5b53aaeb81e4fb4f5b52533d10b462e9e923d2a2857cec7367cda7d40d6222bd8f8a105dfc84e394b35e552c5
7
+ data.tar.gz: 40cede7e7ff373b1a3a170fbd6bf55bb40faf5c2a814af0849a698f2205a15bff49bca7ba5737577620c48265bad4e42d68e8b4befcb4bbb5d78f9ec82bf23fe
@@ -1,6 +1,7 @@
1
1
  class Assignment < Progress
2
2
  include Contextualization
3
3
  include WithMessages
4
+ include Gamified
4
5
 
5
6
  markdown_on :extra_preview
6
7
 
@@ -41,6 +42,7 @@ class Assignment < Progress
41
42
  alias_method :parent_content, :guide
42
43
  alias_method :user, :submitter
43
44
 
45
+ before_save :award_experience_points!, :update_top_submission!, if: :submission_status_changed?
44
46
  after_save :dirty_parent_by_submission!, if: :completion_changed?
45
47
  before_validation :set_current_organization!, unless: :organization
46
48
 
@@ -161,7 +163,10 @@ class Assignment < Progress
161
163
  end
162
164
 
163
165
  def to_resource_h
164
- as_json(except: %i(exercise_id submission_id organization_id id submitter_id solution created_at updated_at submission_status submitted_at parent_id),
166
+ excluded_fields = %i(created_at exercise_id id organization_id parent_id solution submission_id
167
+ submission_status submitted_at submitter_id top_submission_status updated_at)
168
+
169
+ as_json(except: excluded_fields,
165
170
  include: {
166
171
  guide: {
167
172
  only: [:slug, :name],
@@ -224,6 +229,10 @@ class Assignment < Progress
224
229
  exercise.files_for(current_content)
225
230
  end
226
231
 
232
+ def update_top_submission!
233
+ self.top_submission_status = submission_status unless submission_status.improved_by?(top_submission_status)
234
+ end
235
+
227
236
  private
228
237
 
229
238
  def update_submissions_count!
@@ -1,5 +1,11 @@
1
1
  class Avatar < ApplicationRecord
2
+ include WithTargetAudience
3
+
4
+ def self.sample_for(user)
5
+ with_current_audience_for(user).sample
6
+ end
7
+
2
8
  def self.sample
3
- Avatar.order('RANDOM()').first
9
+ order('RANDOM()').first
4
10
  end
5
11
  end
@@ -16,6 +16,7 @@ module Contextualization
16
16
  end
17
17
 
18
18
  included do
19
+ serialize :top_submission_status, Mumuki::Domain::Status::Submission
19
20
  serialize :submission_status, Mumuki::Domain::Status::Submission
20
21
  validates_presence_of :submission_status
21
22
 
@@ -0,0 +1,15 @@
1
+ module Gamified
2
+ def award_experience_points!
3
+ points = net_experience
4
+
5
+ if points > 0
6
+ stats = UserStats.stats_for(submitter)
7
+ stats.add_exp!(points)
8
+ stats.save!
9
+ end
10
+ end
11
+
12
+ def net_experience
13
+ submission_status.exp_given - top_submission_status.exp_given
14
+ end
15
+ end
@@ -9,7 +9,7 @@ module WithReminders
9
9
  end
10
10
 
11
11
  def remind!
12
- build_reminder.deliver
12
+ build_reminder.deliver_now
13
13
  update! last_reminded_date: Time.now
14
14
  end
15
15
 
@@ -0,0 +1,13 @@
1
+ module WithTargetAudience
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ enum target_audience: [:grown_ups, :kids]
6
+ end
7
+
8
+ class_methods do
9
+ def with_current_audience_for(user)
10
+ where(target_audience: user.current_audience)
11
+ end
12
+ end
13
+ end
@@ -5,6 +5,8 @@ class Organization < ApplicationRecord
5
5
 
6
6
  include Mumukit::Login::OrganizationHelpers
7
7
 
8
+ include WithTargetAudience
9
+
8
10
  serialize :profile, Mumuki::Domain::Organization::Profile
9
11
  serialize :settings, Mumuki::Domain::Organization::Settings
10
12
  serialize :theme, Mumuki::Domain::Organization::Theme
@@ -30,16 +30,15 @@ class User < ApplicationRecord
30
30
 
31
31
  has_many :exams, through: :exam_authorizations
32
32
 
33
- after_initialize :init
34
-
35
33
  enum gender: %i(female male other unspecified)
36
-
37
34
  belongs_to :avatar, optional: true
38
35
 
39
36
  before_validation :set_uid!
40
37
  validates :uid, presence: true
41
38
 
42
39
  after_save :welcome_to_new_organizations!, if: :gained_access_to_new_orga?
40
+ after_initialize :init
41
+ PLACEHOLDER_IMAGE_URL = 'user_shape.png'.freeze
43
42
 
44
43
  resource_fields :uid, :social_id, :email, :permissions, :verified_first_name, :verified_last_name, *profile_fields
45
44
 
@@ -147,10 +146,14 @@ class User < ApplicationRecord
147
146
  exams.any? { |e| e.in_progress_for? self }
148
147
  end
149
148
 
150
- def profile_picture
149
+ def custom_profile_picture
151
150
  avatar&.image_url || image_url
152
151
  end
153
152
 
153
+ def profile_picture
154
+ custom_profile_picture || placeholder_image_url
155
+ end
156
+
154
157
  def bury!
155
158
  # TODO change avatar
156
159
  update! self.class.buried_profile.merge(accepts_reminders: false, gender: nil, birthdate: nil)
@@ -215,11 +218,25 @@ class User < ApplicationRecord
215
218
  false
216
219
  end
217
220
 
221
+ def current_audience
222
+ current_organic_context&.target_audience
223
+ end
224
+
225
+ def placeholder_image_url
226
+ PLACEHOLDER_IMAGE_URL
227
+ end
228
+
229
+ def age
230
+ if birthdate.present?
231
+ @age ||= Time.now.round_years_since(birthdate.to_time)
232
+ end
233
+ end
234
+
218
235
  private
219
236
 
220
237
  def welcome_to_new_organizations!
221
238
  new_accessible_organizations.each do |organization|
222
- UserMailer.welcome_email(self, organization).deliver_later if organization.greet_new_users?
239
+ UserMailer.welcome_email(self, organization).deliver_now rescue nil if organization.greet_new_users?
223
240
  end
224
241
  end
225
242
 
@@ -240,7 +257,10 @@ class User < ApplicationRecord
240
257
  end
241
258
 
242
259
  def init
243
- self.avatar = Avatar.sample unless profile_picture.present?
260
+ if custom_profile_picture.blank?
261
+ self.avatar = Avatar.sample_for(self)
262
+ save if persisted?
263
+ end
244
264
  end
245
265
 
246
266
  def self.sync_key_id_field
@@ -265,4 +285,12 @@ class User < ApplicationRecord
265
285
  def self.buried_profile
266
286
  (@buried_profile || {}).slice(:first_name, :last_name, :email)
267
287
  end
288
+
289
+ def current_organic_context
290
+ if Organization.current?
291
+ Organization.current
292
+ else
293
+ main_organization
294
+ end
295
+ end
268
296
  end
@@ -0,0 +1,16 @@
1
+ class UserStats < ApplicationRecord
2
+ belongs_to :organization
3
+ belongs_to :user
4
+
5
+ def self.stats_for(user)
6
+ UserStats.find_or_initialize_by(user: user, organization: Organization.current)
7
+ end
8
+
9
+ def self.exp_for(user)
10
+ self.stats_for(user).exp
11
+ end
12
+
13
+ def add_exp!(points)
14
+ self.exp += points
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ class CreateUserStats < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :user_stats do |t|
4
+ t.integer :exp, default: 0
5
+
6
+ t.references :user, index: true
7
+ t.references :organization, index: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class AddTopSubmissionStatusToAssignments < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :assignments, :top_submission_status, :integer, default: 0
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddTargetAudienceToOrganizationsAndAvatars < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :target_audience, :integer, default: 0
4
+ add_column :avatars, :target_audience, :integer, default: 0
5
+ end
6
+ end
@@ -2,3 +2,4 @@ require_relative './extensions/string'
2
2
  require_relative './extensions/array'
3
3
  require_relative './extensions/module'
4
4
  require_relative './extensions/hash'
5
+ require_relative './extensions/time'
@@ -0,0 +1,5 @@
1
+ class Time
2
+ def round_years_since(another_time)
3
+ (self.to_s(:number).to_i - another_time.to_s(:number).to_i) / 10e9.to_i
4
+ end
5
+ end
@@ -1,5 +1,6 @@
1
1
  require_relative './factories/api_client_factory'
2
2
  require_relative './factories/assignments_factory'
3
+ require_relative './factories/avatar_factory'
3
4
  require_relative './factories/book_factory'
4
5
  require_relative './factories/chapter_factory'
5
6
  require_relative './factories/complement_factory'
@@ -15,4 +16,4 @@ require_relative './factories/message_factory'
15
16
  require_relative './factories/organization_factory'
16
17
  require_relative './factories/topic_factory'
17
18
  require_relative './factories/user_factory'
18
- require_relative './factories/usage_factory'
19
+ require_relative './factories/usage_factory'
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :avatar do
3
+ image_url { Faker::Internet.url }
4
+ target_audience { :grown_ups }
5
+ end
6
+ end
@@ -6,6 +6,5 @@ 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' }
10
9
  end
11
10
  end
@@ -66,6 +66,10 @@ module Mumuki::Domain::Helpers::Organization
66
66
  Mumukit::Platform::Organization.current
67
67
  end
68
68
 
69
+ def current?
70
+ Mumukit::Platform::Organization.current?
71
+ end
72
+
69
73
  def parse(json)
70
74
  json
71
75
  .slice(:name)
@@ -13,6 +13,8 @@ module Mumuki::Domain::Helpers::User
13
13
  :protect!,
14
14
  :protect_delegation!,
15
15
  :protect_permissions_assignment!,
16
+ :student_granted_organizations,
17
+ :any_granted_organizations,
16
18
  to: :permissions
17
19
 
18
20
  def platform_class_name
@@ -71,12 +73,10 @@ module Mumuki::Domain::Helpers::User
71
73
  "#{full_name} <#{email}> [#{uid}]"
72
74
  end
73
75
 
74
- ## Accesible organizations
76
+ ## Accessible organizations
75
77
 
76
- def student_granted_organizations
77
- permissions.student_granted_organizations.map do |org|
78
- Mumukit::Platform::Organization.find_by_name!(org) rescue nil
79
- end.compact
78
+ revamp_accessor :any_granted_organizations, :student_granted_organizations do |_, _, result|
79
+ result.map { |org| Mumukit::Platform::Organization.find_by_name!(org) rescue nil }.compact
80
80
  end
81
81
 
82
82
  def has_student_granted_organizations?
@@ -84,11 +84,7 @@ module Mumuki::Domain::Helpers::User
84
84
  end
85
85
 
86
86
  def main_organization
87
- student_granted_organizations.first
88
- end
89
-
90
- def has_main_organization?
91
- student_granted_organizations.length == 1
87
+ student_granted_organizations.first || any_granted_organizations.first
92
88
  end
93
89
 
94
90
  def has_immersive_main_organization?
@@ -40,6 +40,10 @@ module Mumuki::Domain
40
40
  false
41
41
  end
42
42
 
43
+ def can_discuss_in?(*)
44
+ false
45
+ end
46
+
43
47
  # ========
44
48
  # Visiting
45
49
  # ========
@@ -88,6 +92,10 @@ module Mumuki::Domain
88
92
  raise "unknown attribute #{key}"
89
93
  end
90
94
 
95
+ def new_record?
96
+ false
97
+ end
98
+
91
99
  def self.primary_key
92
100
  'id'
93
101
  end
@@ -105,7 +113,6 @@ module Mumuki::Domain
105
113
  assignment.assign_attributes results
106
114
  results
107
115
  end
108
-
109
116
  end
110
117
 
111
118
  Incognito = IncognitoClass.new
@@ -8,4 +8,8 @@ module Mumuki::Domain::Status::Submission::Passed
8
8
  def self.iconize
9
9
  {class: :success, type: 'check-circle'}
10
10
  end
11
+
12
+ def self.exp_given
13
+ 100
14
+ end
11
15
  end
@@ -12,4 +12,8 @@ module Mumuki::Domain::Status::Submission::PassedWithWarnings
12
12
  def self.iconize
13
13
  {class: :warning, type: 'exclamation-circle'}
14
14
  end
15
+
16
+ def self.exp_given
17
+ 50
18
+ end
15
19
  end
@@ -8,4 +8,8 @@ module Mumuki::Domain::Status::Submission::Skipped
8
8
  def self.iconize
9
9
  {class: :success, type: 'check-circle'}
10
10
  end
11
+
12
+ def self.exp_given
13
+ 100
14
+ end
11
15
  end
@@ -42,4 +42,12 @@ module Mumuki::Domain::Status::Submission
42
42
  def solved?
43
43
  passed? || skipped?
44
44
  end
45
+
46
+ def improved_by?(status)
47
+ self.exp_given < status.exp_given
48
+ end
49
+
50
+ def exp_given
51
+ 0
52
+ end
45
53
  end
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '7.8.1'
3
+ VERSION = '7.9.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.8.1
4
+ version: 7.9.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-08-31 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -256,6 +256,7 @@ files:
256
256
  - app/models/concerns/contextualization.rb
257
257
  - app/models/concerns/disabling.rb
258
258
  - app/models/concerns/friendly_name.rb
259
+ - app/models/concerns/gamified.rb
259
260
  - app/models/concerns/guide_container.rb
260
261
  - app/models/concerns/navigation/parent_navigation.rb
261
262
  - app/models/concerns/navigation/siblings_navigation.rb
@@ -292,6 +293,7 @@ files:
292
293
  - app/models/concerns/with_scoped_queries/page.rb
293
294
  - app/models/concerns/with_scoped_queries/sort.rb
294
295
  - app/models/concerns/with_slug.rb
296
+ - app/models/concerns/with_target_audience.rb
295
297
  - app/models/concerns/with_usages.rb
296
298
  - app/models/concerns/with_user_navigation.rb
297
299
  - app/models/content.rb
@@ -322,6 +324,7 @@ files:
322
324
  - app/models/upvote.rb
323
325
  - app/models/usage.rb
324
326
  - app/models/user.rb
327
+ - app/models/user_stats.rb
325
328
  - app/models/with_stats.rb
326
329
  - db/migrate/20141120231135_create_exercises.rb
327
330
  - db/migrate/20141120231735_create_submissions.rb
@@ -608,7 +611,10 @@ files:
608
611
  - db/migrate/20200601203033_add_course_to_exam.rb
609
612
  - db/migrate/20200605161350_add_passing_criterions_to_exam.rb
610
613
  - db/migrate/20200608132959_add_progressive_display_lookahead_to_organizations.rb
614
+ - db/migrate/20200616160640_create_user_stats.rb
615
+ - db/migrate/20200617142217_add_top_submission_status_to_assignments.rb
611
616
  - db/migrate/20200702165503_add_messages_count_to_discussion.rb
617
+ - db/migrate/20200717143830_add_target_audience_to_organizations_and_avatars.rb
612
618
  - db/migrate/20200728162727_add_not_actually_a_question_field_to_messages.rb
613
619
  - db/migrate/20200728163038_add_requires_moderator_response_to_discussions.rb
614
620
  - db/migrate/20200730221001_add_trusted_for_forum_to_user.rb
@@ -634,9 +640,11 @@ files:
634
640
  - lib/mumuki/domain/extensions/hash.rb
635
641
  - lib/mumuki/domain/extensions/module.rb
636
642
  - lib/mumuki/domain/extensions/string.rb
643
+ - lib/mumuki/domain/extensions/time.rb
637
644
  - lib/mumuki/domain/factories.rb
638
645
  - lib/mumuki/domain/factories/api_client_factory.rb
639
646
  - lib/mumuki/domain/factories/assignments_factory.rb
647
+ - lib/mumuki/domain/factories/avatar_factory.rb
640
648
  - lib/mumuki/domain/factories/book_factory.rb
641
649
  - lib/mumuki/domain/factories/chapter_factory.rb
642
650
  - lib/mumuki/domain/factories/complement_factory.rb