mumuki-domain 7.4.1 → 7.5.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 +4 -4
- data/app/models/assignment.rb +1 -1
- data/app/models/avatar.rb +5 -0
- data/app/models/concerns/contextualization.rb +53 -2
- data/app/models/concerns/disabling.rb +37 -0
- data/app/models/concerns/with_editor.rb +1 -1
- data/app/models/exercise.rb +3 -3
- data/app/models/guide.rb +1 -1
- data/app/models/user.rb +30 -1
- data/db/migrate/20200508191543_create_avatars.rb +8 -0
- data/db/migrate/20200518135658_add_avatar_to_users.rb +5 -0
- data/db/migrate/20200527180729_add_disabled_at_to_users.rb +6 -0
- data/lib/mumuki/domain/exceptions.rb +1 -0
- data/lib/mumuki/domain/exceptions/disabled_error.rb +2 -0
- data/lib/mumuki/domain/extensions/hash.rb +11 -2
- data/lib/mumuki/domain/extensions/string.rb +44 -0
- data/lib/mumuki/domain/factories/book_factory.rb +2 -2
- data/lib/mumuki/domain/factories/chapter_factory.rb +2 -2
- data/lib/mumuki/domain/factories/exam_factory.rb +1 -1
- data/lib/mumuki/domain/factories/guide_factory.rb +2 -2
- data/lib/mumuki/domain/factories/invitation_factory.rb +1 -1
- data/lib/mumuki/domain/factories/topic_factory.rb +2 -2
- data/lib/mumuki/domain/factories/user_factory.rb +1 -0
- data/lib/mumuki/domain/helpers/user.rb +1 -1
- data/lib/mumuki/domain/version.rb +1 -1
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e4bc7628cef1eb1a3d8ed748fc2fd8c49830ffd9814058360aa324aa6bcde35
|
|
4
|
+
data.tar.gz: c32ffaac2a2cef4a4e7f71ded9c4f81f233db86f55a1a0d7a67bdda0792c9489
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce2e45ade3b54b53eec50718f7e42ef1f6cc51d9aa39706651c3cbd6e9d156b043c300d9d19e333e4ecf5955b5d2e915fe4aa0162830983e92d37892bca5f952
|
|
7
|
+
data.tar.gz: f3b7a44f946871521a943efbccd3d3d24594cfda1eb2493f6be706ab65738ca90334da2b3c1c5521a9936ad9a58c2856bb132c2e71dd0a440a72ed9914832dd1
|
data/app/models/assignment.rb
CHANGED
|
@@ -168,7 +168,7 @@ class Assignment < Progress
|
|
|
168
168
|
language: {only: [:name]}},
|
|
169
169
|
},
|
|
170
170
|
exercise: {only: [:name, :number]},
|
|
171
|
-
submitter: {only: [:email, :
|
|
171
|
+
submitter: {only: [:email, :social_id, :uid], methods: [:name, :profile_picture]}}).
|
|
172
172
|
deep_merge(
|
|
173
173
|
'organization' => Organization.current.name,
|
|
174
174
|
'sid' => submission_id,
|
|
@@ -35,12 +35,34 @@ 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
|
-
|
|
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
|
-
|
|
48
|
+
warn 'use first_test_result_html intead'
|
|
49
|
+
first_test_result_html
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def single_visible_test_result?
|
|
53
|
+
test_results.size == 1 && visible_success_output?
|
|
54
|
+
end
|
|
55
|
+
|
|
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]
|
|
44
66
|
end
|
|
45
67
|
|
|
46
68
|
def results_body_hidden?
|
|
@@ -76,6 +98,7 @@ module Contextualization
|
|
|
76
98
|
end
|
|
77
99
|
|
|
78
100
|
def humanized_expectation_results
|
|
101
|
+
warn "Don't use humanized_expectation_results. Use affable_expectation_results, which also handles markdown and sanitization"
|
|
79
102
|
visible_expectation_results.map do |it|
|
|
80
103
|
{
|
|
81
104
|
result: it[:result],
|
|
@@ -83,4 +106,32 @@ module Contextualization
|
|
|
83
106
|
}
|
|
84
107
|
end
|
|
85
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
|
|
86
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
|
+
|
data/app/models/exercise.rb
CHANGED
|
@@ -132,7 +132,7 @@ class Exercise < ApplicationRecord
|
|
|
132
132
|
.merge(settings: self[:settings])
|
|
133
133
|
.merge(RANDOMIZED_FIELDS.map { |it| [it, self[it]] }.to_h)
|
|
134
134
|
.symbolize_keys
|
|
135
|
-
.tap { |it| it.
|
|
135
|
+
.tap { |it| it.markdownified!(:hint, :corollary, :description, :teacher_info) if options[:markdownified] }
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def reset!
|
|
@@ -166,7 +166,7 @@ class Exercise < ApplicationRecord
|
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
def description_context
|
|
169
|
-
|
|
169
|
+
splitted_description.first.markdownified
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
def splitted_description
|
|
@@ -174,7 +174,7 @@ class Exercise < ApplicationRecord
|
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
def description_task
|
|
177
|
-
|
|
177
|
+
splitted_description.drop(1).join("\n").markdownified
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def custom?
|
data/app/models/guide.rb
CHANGED
|
@@ -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.
|
|
107
|
+
.tap { |it| it.markdownified!(:corollary, :description, :teacher_info) if options[:markdownified] }
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def to_markdownified_resource_h
|
data/app/models/user.rb
CHANGED
|
@@ -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
|
|
@@ -33,10 +34,12 @@ class User < ApplicationRecord
|
|
|
33
34
|
|
|
34
35
|
enum gender: %i(female male other)
|
|
35
36
|
|
|
37
|
+
belongs_to :avatar, optional: true
|
|
38
|
+
|
|
36
39
|
before_validation :set_uid!
|
|
37
40
|
validates :uid, presence: true
|
|
38
41
|
|
|
39
|
-
resource_fields :uid, :social_id, :
|
|
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)
|
|
@@ -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
|
|
@@ -138,6 +145,15 @@ class User < ApplicationRecord
|
|
|
138
145
|
exams.any? { |e| e.in_progress_for? self }
|
|
139
146
|
end
|
|
140
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
|
+
|
|
141
157
|
private
|
|
142
158
|
|
|
143
159
|
def set_uid!
|
|
@@ -145,6 +161,9 @@ class User < ApplicationRecord
|
|
|
145
161
|
end
|
|
146
162
|
|
|
147
163
|
def init
|
|
164
|
+
# Temporarily keep using image_url until avatars are created
|
|
165
|
+
# self.avatar = Avatar.sample unless profile_picture.present?
|
|
166
|
+
|
|
148
167
|
self.image_url ||= "user_shape.png"
|
|
149
168
|
end
|
|
150
169
|
|
|
@@ -160,4 +179,14 @@ class User < ApplicationRecord
|
|
|
160
179
|
user[:uid] ||= user[:email]
|
|
161
180
|
where(uid: user[:uid]).first_or_create(user)
|
|
162
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
|
|
163
192
|
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'
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
class Hash
|
|
2
|
-
def markdownify!(*keys)
|
|
3
|
-
|
|
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 }
|
|
@@ -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,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
|
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.
|
|
4
|
+
version: 7.5.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-
|
|
11
|
+
date: 2020-06-02 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.
|
|
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.
|
|
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
|
|
@@ -599,6 +601,9 @@ files:
|
|
|
599
601
|
- db/migrate/20200127142401_add_private_to_topics_and_books.rb
|
|
600
602
|
- db/migrate/20200213175736_add_verified_names_to_users.rb
|
|
601
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
|
|
602
607
|
- lib/mumuki/domain.rb
|
|
603
608
|
- lib/mumuki/domain/engine.rb
|
|
604
609
|
- lib/mumuki/domain/evaluation.rb
|
|
@@ -606,6 +611,7 @@ files:
|
|
|
606
611
|
- lib/mumuki/domain/evaluation/manual.rb
|
|
607
612
|
- lib/mumuki/domain/exceptions.rb
|
|
608
613
|
- lib/mumuki/domain/exceptions/blocked_forum_error.rb
|
|
614
|
+
- lib/mumuki/domain/exceptions/disabled_error.rb
|
|
609
615
|
- lib/mumuki/domain/exceptions/forbidden_error.rb
|
|
610
616
|
- lib/mumuki/domain/exceptions/gone_error.rb
|
|
611
617
|
- lib/mumuki/domain/exceptions/not_found_error.rb
|