mumuki-domain 6.5.1 → 6.6.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: 3035243b0b927688b208101efda8b3be665d244cb4099faf8b8a1bf3a7aa89e1
4
- data.tar.gz: 417de725ebff9ebfd3687084ba41f66e9a44eb4899976ddc75923b4c80894107
3
+ metadata.gz: 0fe12401571cc46abe567d3337c252fcbf0d9a974f37ad207b3d424086b95b7c
4
+ data.tar.gz: dbc42a3ada461878dd43b5eb8dc14d0ec449349099b01a0b80dbf428f08edcdf
5
5
  SHA512:
6
- metadata.gz: 8e9088e52953f6d5fe90bfc66d8e8f793700166eb63c9ac7c298e17aab8bba59a5336af7aeeb9aad3c63be885f0a45b3f3cc546c38989da9b0c9bf21dbac4c87
7
- data.tar.gz: 81ecbff2d1327923403fe3e89a726519469790e70b82c337c4b91690be00aead66280cf6efe97c3d25bb0502667e9970c70ffbfd9b9600cff2a46d6d091e2c2d
6
+ metadata.gz: c228e0879bfc3c4e74013e21a5a8f5e5656e4c2da491b340fd89d24ab7bcc015735d42ad8ca66a2a54662a5ba0a15cf1c1a8ba53d145fbe6c8ce76da69815b81
7
+ data.tar.gz: 6bf1b18815eda0d228a78c6471b6e6d786358dbd1e94ba951d480e830eb03163c83182022bcdd4eeec15fb99ba2e774cfd6947d3f0562ba9277182d297e4a9c3
@@ -101,6 +101,14 @@ class ApplicationRecord < ActiveRecord::Base
101
101
  a_hash.with_indifferent_access.slice(*attributes).except(*options[:except])
102
102
  end
103
103
 
104
+ def self.organic_on(*selectors)
105
+ selectors.each do |selector|
106
+ define_method("#{selector}_in_organization") do |organization = Organization.current|
107
+ send(selector).where(organization: organization)
108
+ end
109
+ end
110
+ end
111
+
104
112
  private
105
113
 
106
114
  def raise_foreign_key_error!
@@ -16,7 +16,7 @@ class Assignment < ApplicationRecord
16
16
 
17
17
  validates_presence_of :exercise, :submitter
18
18
 
19
- delegate :language, :name, :navigable_parent,
19
+ delegate :language, :name, :navigable_parent, :settings,
20
20
  :limited?, :input_kids?, :choice?, to: :exercise
21
21
 
22
22
  alias_attribute :status, :submission_status
@@ -118,11 +118,11 @@ class Assignment < ApplicationRecord
118
118
 
119
119
  %w(query try).each do |key|
120
120
  name = "run_#{key}!"
121
- define_method(name) { |params| exercise.send name, params.merge(extra: extra) }
121
+ define_method(name) { |params| exercise.send name, params.merge(extra: extra, settings: settings) }
122
122
  end
123
123
 
124
124
  def run_tests!(params)
125
- exercise.run_tests! params.merge(extra: extra, test: test)
125
+ exercise.run_tests! params.merge(extra: extra, test: test, settings: settings)
126
126
  end
127
127
 
128
128
  def to_resource_h
data/app/models/book.rb CHANGED
@@ -7,6 +7,7 @@ class Book < Content
7
7
 
8
8
  has_many :exercises, through: :chapters
9
9
  has_many :discussions, through: :exercises
10
+ organic_on :discussions
10
11
 
11
12
  delegate :first_lesson, to: :first_chapter
12
13
 
@@ -59,11 +59,6 @@ module Syncable
59
59
  find_or_initialize_by sync_key_id_field => sync_key_id
60
60
  end
61
61
 
62
- # `locate_resource` is a helpful method that can be used
63
- # outside the `Mumukit::Sync` context, thus this mixin provide
64
- # a shorter alias
65
- alias_method :locate, :locate_resource
66
-
67
62
  # `locate!` is similar to `locate`, but fails instead of creating a
68
63
  # a new object when not found
69
64
  #
@@ -2,9 +2,13 @@ module WithDiscussionCreation::Subscription
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- has_many :discussions, foreign_key: 'initiator_id'
6
5
  has_many :subscriptions
7
6
  has_many :watched_discussions, through: :subscriptions, source: :discussion
7
+ organic_on :watched_discussions
8
+ end
9
+
10
+ def subscriptions_in_organization
11
+ subscriptions.joins(:discussion).where(discussion: discussions_in_organization)
8
12
  end
9
13
 
10
14
  def subscribed_to?(discussion)
@@ -28,6 +32,6 @@ module WithDiscussionCreation::Subscription
28
32
  end
29
33
 
30
34
  def unread_discussions
31
- subscriptions.where(read: false).map(&:discussion)
35
+ subscriptions_in_organization.where(read: false).map(&:discussion)
32
36
  end
33
37
  end
@@ -5,5 +5,6 @@ module WithDiscussionCreation
5
5
  has_many :discussions, foreign_key: 'initiator_id'
6
6
  include WithDiscussionCreation::Subscription
7
7
  include WithDiscussionCreation::Upvote
8
+ organic_on :discussions
8
9
  end
9
10
  end
@@ -3,10 +3,11 @@ module WithDiscussions
3
3
 
4
4
  included do
5
5
  has_many :discussions, as: :item, dependent: :destroy
6
+ organic_on :discussions
6
7
  end
7
8
 
8
- def discuss!(user, discussion)
9
- discussion.merge!(initiator_id: user.id)
9
+ def discuss!(user, discussion, organization = Organization.current)
10
+ discussion.merge!(initiator_id: user.id, organization: organization)
10
11
  discussion.merge!(submission: submission_for(user)) if submission_for(user).present?
11
12
  created_discussion = discussions.create discussion
12
13
  user.subscribe_to! created_discussion
@@ -8,11 +8,15 @@ module WithSlug
8
8
  before_create :normalize_slug!
9
9
  end
10
10
 
11
- def slug_parts
11
+ def transparent_params
12
12
  org, repo = slug.split('/')
13
13
  {organization: org, repository: repo}
14
14
  end
15
15
 
16
+ def transparent_id
17
+ slug
18
+ end
19
+
16
20
  def normalize_slug!
17
21
  self.slug = self.slug.to_mumukit_slug.normalize.to_s
18
22
  end
@@ -40,7 +44,7 @@ module WithSlug
40
44
  all.select { |it| !it.private? || permissions&.writer?(it.slug) }
41
45
  end
42
46
 
43
- def by_slug_parts!(args)
47
+ def find_transparently!(args)
44
48
  find_by!(slug: "#{args[:organization]}/#{args[:repository]}")
45
49
  end
46
50
 
@@ -4,10 +4,11 @@ module WithUsages
4
4
  included do
5
5
  has_many :usages, as: :item
6
6
  before_destroy :ensure_unused!
7
+ organic_on :usages
7
8
  end
8
9
 
9
10
  def usage_in_organization(organization = Organization.current)
10
- usages.in_organization(organization).first.try(:parent_item)
11
+ usages_in_organization(organization).first.try(:parent_item)
11
12
  end
12
13
 
13
14
  def usage_in_organization_of_type(type, organization = Organization.current)
data/app/models/course.rb CHANGED
@@ -15,7 +15,7 @@ class Course < ApplicationRecord
15
15
  end
16
16
 
17
17
  def import_from_resource_h!(resource_h)
18
- update! Mumukit::Platform::Course::Helpers.slice_platform_json(resource_h)
18
+ update! Mumukit::Platform::Course::Helpers.slice_resource_h(resource_h)
19
19
  end
20
20
 
21
21
  def slug=(slug)
@@ -5,6 +5,7 @@ class Discussion < ApplicationRecord
5
5
  has_many :messages, -> { order(:created_at) }, dependent: :destroy
6
6
  belongs_to :initiator, class_name: 'User'
7
7
  belongs_to :exercise, foreign_type: :exercise, foreign_key: 'item_id'
8
+ belongs_to :organization
8
9
  has_many :subscriptions
9
10
  has_many :upvotes
10
11
 
@@ -23,7 +24,7 @@ class Discussion < ApplicationRecord
23
24
  delegate :to_discussion_status, to: :status
24
25
 
25
26
  scope :for_user, -> (user) do
26
- if user.try(:moderator?)
27
+ if user.try(:moderator_here?)
27
28
  all
28
29
  else
29
30
  where.not(status: :closed).where.not(status: :pending_review).or(where(initiator: user))
@@ -41,11 +42,11 @@ class Discussion < ApplicationRecord
41
42
  end
42
43
 
43
44
  def used_in?(organization)
44
- item.used_in?(organization)
45
+ organization == self.organization
45
46
  end
46
47
 
47
48
  def commentable_by?(user)
48
- user&.moderator? || (opened? && user.present?)
49
+ user&.moderator_here? || (opened? && user.present?)
49
50
  end
50
51
 
51
52
  def subscribable?
@@ -87,7 +88,7 @@ class Discussion < ApplicationRecord
87
88
  end
88
89
 
89
90
  def authorized?(user)
90
- initiator?(user) || user.try(:moderator?)
91
+ initiator?(user) || user.try(:moderator_here?)
91
92
  end
92
93
 
93
94
  def initiator?(user)
data/app/models/exam.rb CHANGED
@@ -73,7 +73,11 @@ class Exam < ApplicationRecord
73
73
  end
74
74
 
75
75
  def start!(user)
76
- authorization_for(user).start! unless user.teacher_here?
76
+ return if user.teacher_here?
77
+
78
+ authorization = authorization_for(user)
79
+ raise Mumuki::Domain::ForbiddenError unless authorization
80
+ authorization.start!
77
81
  end
78
82
 
79
83
  def started?(user)
@@ -25,6 +25,8 @@ class Exercise < ApplicationRecord
25
25
  defaults { self.submissions_count = 0 }
26
26
 
27
27
  serialize :choices, Array
28
+ serialize :settings, Hash
29
+
28
30
  validates_presence_of :submissions_count,
29
31
  :guide, :bibliotheca_id
30
32
 
@@ -64,12 +66,12 @@ class Exercise < ApplicationRecord
64
66
  [language&.name, *tag_list].compact
65
67
  end
66
68
 
67
- def slug
68
- "#{guide.slug}/#{bibliotheca_id}"
69
+ def transparent_id
70
+ "#{guide.transparent_id}/#{bibliotheca_id}"
69
71
  end
70
72
 
71
- def slug_parts
72
- guide.slug_parts.merge(bibliotheca_id: bibliotheca_id)
73
+ def transparent_params
74
+ guide.transparent_params.merge(bibliotheca_id: bibliotheca_id)
73
75
  end
74
76
 
75
77
  def friendly
@@ -116,6 +118,7 @@ class Exercise < ApplicationRecord
116
118
  free_form_editor_source initial_state final_state))
117
119
  .merge(id: bibliotheca_id, language: language_resource_h, type: type.underscore)
118
120
  .merge(expectations: self[:expectations])
121
+ .merge(settings: self[:settings])
119
122
  .merge(RANDOMIZED_FIELDS.map { |it| [it, self[it]] }.to_h)
120
123
  .symbolize_keys
121
124
  .compact
@@ -193,6 +196,19 @@ class Exercise < ApplicationRecord
193
196
  .map { |name, content| Mumuki::Domain::File.new name, content }
194
197
  end
195
198
 
199
+ def self.find_transparently!(params)
200
+ Guide.find_transparently!(params).locate_exercise! params[:bibliotheca_id]
201
+ end
202
+
203
+ def self.locate!(slug_and_bibliotheca_id)
204
+ slug, bibliotheca_id = slug_and_bibliotheca_id
205
+ Guide.locate!(slug).locate_exercise! bibliotheca_id
206
+ end
207
+
208
+ def settings
209
+ guide.settings.deep_merge super
210
+ end
211
+
196
212
  private
197
213
 
198
214
  def evaluation_class
data/app/models/guide.rb CHANGED
@@ -8,6 +8,8 @@ class Guide < Content
8
8
  numbered :exercises
9
9
  has_many :exercises, -> { order(number: :asc) }, dependent: :destroy
10
10
 
11
+ serialize :settings, Hash
12
+
11
13
  self.inheritance_column = nil
12
14
 
13
15
  enum type: [:learning, :practice]
@@ -57,6 +59,11 @@ class Guide < Content
57
59
  stats_for(user).done?
58
60
  end
59
61
 
62
+ # Finds an exercise by bibliotheca_id within this guide
63
+ def locate_exercise!(bibliotheca_id)
64
+ exercises.find_by!(bibliotheca_id: bibliotheca_id)
65
+ end
66
+
60
67
  def import_from_resource_h!(resource_h)
61
68
  self.assign_attributes whitelist_attributes(resource_h)
62
69
  self.language = Language.for_name(resource_h.dig(:language, :name))
@@ -80,7 +87,7 @@ class Guide < Content
80
87
  end
81
88
 
82
89
  def to_resource_h
83
- as_json(only: %i(beta type id_format private expectations corollary teacher_info sources learn_more authors collaborators extra))
90
+ as_json(only: %i(beta type id_format private expectations corollary teacher_info sources learn_more authors collaborators extra settings))
84
91
  .symbolize_keys
85
92
  .merge(super)
86
93
  .merge(exercises: exercises.map(&:to_resource_h))
@@ -41,7 +41,7 @@ class Invitation < ApplicationRecord
41
41
  end
42
42
 
43
43
  def unexpired
44
- raise RecordNotFound, "This invitation has already expired" if expired?
44
+ raise Mumuki::Domain::GoneError, "This invitation has already expired" if expired?
45
45
  self
46
46
  end
47
47
 
@@ -25,7 +25,7 @@ class Message < ApplicationRecord
25
25
  end
26
26
 
27
27
  def authorized?(user)
28
- from_user?(user) || user&.moderator?
28
+ from_user?(user) || user&.moderator_here?
29
29
  end
30
30
 
31
31
  def authorize!(user)
@@ -95,12 +95,25 @@ class Organization < ApplicationRecord
95
95
  central? ? 'mumuki' : name
96
96
  end
97
97
 
98
- def ask_for_help_enabled?
99
- report_issue_enabled? || community_link.present? || forum_enabled?
98
+ # Tells if the given user can
99
+ # ask for help in this organization
100
+ #
101
+ # Warning: this method does not strictly check user's permission
102
+ def ask_for_help_enabled?(user)
103
+ report_issue_enabled? || community_link.present? || can_create_discussions?(user)
104
+ end
105
+
106
+ # Tells if the given user can
107
+ # create discussion in this organization
108
+ #
109
+ # This is true only when this organization has a forum and the user
110
+ # has the discusser pseudo-permission
111
+ def can_create_discussions?(user)
112
+ forum_enabled? && user.discusser_of?(self)
100
113
  end
101
114
 
102
115
  def import_from_resource_h!(resource_h)
103
- attrs = Mumukit::Platform::Organization::Helpers.slice_platform_json resource_h
116
+ attrs = Mumukit::Platform::Organization::Helpers.slice_resource_h resource_h
104
117
  attrs[:book] = Book.locate! attrs[:book]
105
118
  update! attrs
106
119
  end
data/app/models/user.rb CHANGED
@@ -98,7 +98,7 @@ class User < ApplicationRecord
98
98
  end
99
99
 
100
100
  def import_from_resource_h!(json)
101
- update! Mumukit::Platform::User::Helpers.slice_platform_json json
101
+ update! Mumukit::Platform::User::Helpers.slice_resource_h json
102
102
  end
103
103
 
104
104
  def unsubscribe_from_reminders!
@@ -0,0 +1,6 @@
1
+ class AddSettingsToContent < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :exercises, :settings, :text
4
+ add_column :guides, :settings, :text
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddOrganizationToDiscussion < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :discussions, :organization, index: true
4
+ end
5
+ end
@@ -27,11 +27,11 @@ module Mumuki::Domain::Status::Discussion
27
27
  end
28
28
 
29
29
  def should_be_shown?(count, user)
30
- count > 0 || user&.moderator?
30
+ count > 0 || user&.moderator_here?
31
31
  end
32
32
 
33
33
  def reachable_statuses_for(user, discussion)
34
- if user.moderator?
34
+ if user.moderator_here?
35
35
  reachable_statuses_for_moderator(discussion)
36
36
  else
37
37
  reachable_statuses_for_initiator(discussion)
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '6.5.1'
3
+ VERSION = '6.6.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: 6.5.1
4
+ version: 6.6.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: 2019-03-14 00:00:00.000000000 Z
11
+ date: 2019-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '3.2'
145
+ version: '4.2'
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '3.2'
152
+ version: '4.2'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: mumukit-sync
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -535,6 +535,8 @@ files:
535
535
  - db/migrate/20190123180139_add_sources_section.rb
536
536
  - db/migrate/20190123180147_add_learn_more_section.rb
537
537
  - db/migrate/20190312152901_add_content_fk.rb
538
+ - db/migrate/20190326152631_add_settings_to_content.rb
539
+ - db/migrate/20190404181724_add_organization_to_discussion.rb
538
540
  - lib/mumuki/domain.rb
539
541
  - lib/mumuki/domain/engine.rb
540
542
  - lib/mumuki/domain/evaluation.rb