Class: Exercise

Direct Known Subclasses

Challenge, Reading

Constant Summary collapse

RANDOMIZED_FIELDS =
[:default_content, :description, :extra, :hint, :test, :expectations, :custom_expectations]
BASIC_RESOURCE_FIELDS =
i(
name layout editor corollary teacher_info manual_evaluation locale
choices assistance_rules randomizations tag_list extra_visible goal
free_form_editor_source initial_state final_state)

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SiblingsNavigation

#navigable_name, #next_for, #restart, #siblings

Methods included from ParentNavigation

#leave, #navigable_name, #navigable_parent, #navigation_end?

Methods included from Submittable

#find_assignment_and_submit!, #submit!

Methods included from Questionable

#submit_question!

Methods included from WithAssignments

#assignment_for, #find_assignment_for, #has_messages_for?, #messages_for, #status_for

Methods included from FriendlyName

#friendly_name, #to_param

Methods included from Assistable

#assist_with, #assistant

Methods included from WithRandomizations

#randomizer, #seed, #seed_with!

Methods included from WithDiscussions

#discuss!, #new_discussion_for, #try_solve_discussions!

Methods inherited from ApplicationRecord

active_between, aggregate_of, all_except, defaults, #delete, #destroy!, enum_prefixed_translations_for, numbered, organic_on, resource_fields, #save, #save_and_notify!, #save_and_notify_changes!, serialize_symbolized_hash_array, teaser_on, #update_and_notify!, update_or_create!, whitelist_attributes, with_pg_retry, with_temporary_token

Class Method Details

.default_scopeObject



31
32
33
# File 'app/models/exercise.rb', line 31

def self.default_scope
  where(manual_evaluation: false) if hide_manual_evaluation?
end

.find_transparently!(params) ⇒ Object



228
229
230
# File 'app/models/exercise.rb', line 228

def self.find_transparently!(params)
  Guide.find_transparently!(params).locate_exercise! params[:bibliotheca_id]
end

.locate!(slug_and_bibliotheca_id) ⇒ Object



232
233
234
235
# File 'app/models/exercise.rb', line 232

def self.locate!(slug_and_bibliotheca_id)
  slug, bibliotheca_id = slug_and_bibliotheca_id
  Guide.locate!(slug).locate_exercise! bibliotheca_id
end

Instance Method Details

#choice?Boolean



71
72
73
# File 'app/models/exercise.rb', line 71

def choice?
  false
end

#choice_index_for(value) ⇒ Object



129
130
131
# File 'app/models/exercise.rb', line 129

def choice_index_for(value)
  choice_values.index(value)
end

#choice_valuesObject



125
126
127
# File 'app/models/exercise.rb', line 125

def choice_values
  choices.map { |it| it.indifferent_get(:value) }
end

#console?Boolean



47
48
49
# File 'app/models/exercise.rb', line 47

def console?
  queriable?
end

#content_used_in?(organization) ⇒ Boolean



59
60
61
# File 'app/models/exercise.rb', line 59

def content_used_in?(organization)
  navigable_content_in(organization).present?
end

#custom?Boolean



191
192
193
# File 'app/models/exercise.rb', line 191

def custom?
  false
end

#description_contextObject



179
180
181
# File 'app/models/exercise.rb', line 179

def description_context
  splitted_description.first.markdownified
end

#description_taskObject



187
188
189
# File 'app/models/exercise.rb', line 187

def description_task
  splitted_description.drop(1).join("\n").markdownified
end

#ensure_type!(type) ⇒ Object



158
159
160
161
162
163
164
# File 'app/models/exercise.rb', line 158

def ensure_type!(type)
  if self.type != type
    reclassify! type
  else
    self
  end
end

#files_for(current_content) ⇒ Object



221
222
223
224
225
226
# File 'app/models/exercise.rb', line 221

def files_for(current_content)
  language
    .directives_sections
    .split_sections(current_content)
    .map { |name, content| Mumuki::Domain::File.new name, content }
end

#friendlyObject



96
97
98
# File 'app/models/exercise.rb', line 96

def friendly
  defaulting_name { "#{navigable_parent.friendly} - #{name}" }
end

#guide_done_for?(user) ⇒ Boolean



67
68
69
# File 'app/models/exercise.rb', line 67

def guide_done_for?(user)
  guide.done_for?(user)
end

#import_from_resource_h!(number, resource_h) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/models/exercise.rb', line 108

def import_from_resource_h!(number, resource_h)
  self.language = Language.for_name(resource_h.dig(:language, :name)) || guide.language
  self.locale = guide.locale

  reset!

  attrs = whitelist_attributes(resource_h, except: [:type, :id])
  attrs[:choices] = resource_h[:choices].to_a
  attrs[:bibliotheca_id] = resource_h[:id]
  attrs[:number] = number
  attrs[:manual_evaluation] ||= false
  attrs = attrs.except(:expectations, :custom_expectations) if type != 'Problem'

  assign_attributes(attrs)
  save!
end

#inspection_keywordsObject



195
196
197
# File 'app/models/exercise.rb', line 195

def inspection_keywords
  {}
end

#limited?Boolean

An exercise with hidden results cannot be limited as those exercises can be submitted as many times as the student wants because no result output is given



213
214
215
# File 'app/models/exercise.rb', line 213

def limited?
  !results_hidden? && navigable_parent.limited_for?(self)
end

#messages_path_for(user) ⇒ Object



171
172
173
# File 'app/models/exercise.rb', line 171

def messages_path_for(user)
  "api/guides/#{guide.slug}/#{bibliotheca_id}/student/#{URI.escape user.uid}/messages?language=#{language}"
end

#messages_url_for(user) ⇒ Object



175
176
177
# File 'app/models/exercise.rb', line 175

def messages_url_for(user)
  Mumukit::Platform.classroom_api.organic_url_for(Organization.current, messages_path_for(user))
end


55
56
57
# File 'app/models/exercise.rb', line 55

def navigable_content_in(organization = Organization.current)
  self if used_in?(organization)
end

#new_solutionObject



104
105
106
# File 'app/models/exercise.rb', line 104

def new_solution
  Mumuki::Domain::Submission::Solution.new(content: default_content)
end

#pending_siblings_for(user) ⇒ Object



241
242
243
# File 'app/models/exercise.rb', line 241

def pending_siblings_for(user)
  guide.pending_exercises(user)
end

#previousObject



75
76
77
# File 'app/models/exercise.rb', line 75

def previous
  sibling_at number.pred
end

#reclassify!(type) ⇒ Object



166
167
168
169
# File 'app/models/exercise.rb', line 166

def reclassify!(type)
  update!(type: type)
  Exercise.find(id)
end

#reset!Object



149
150
151
152
153
154
155
156
# File 'app/models/exercise.rb', line 149

def reset!
  self.name = nil
  self.description = nil
  self.corollary = nil
  self.hint = nil
  self.extra = nil
  self.tag_list = []
end

#results_hidden?Boolean



217
218
219
# File 'app/models/exercise.rb', line 217

def results_hidden?
  navigable_parent&.results_hidden_for?(self)
end

#search_tagsObject



84
85
86
# File 'app/models/exercise.rb', line 84

def search_tags
  [language&.name, *tag_list].compact
end

#settingsObject



237
238
239
# File 'app/models/exercise.rb', line 237

def settings
  guide.settings.deep_merge super
end

#sibling_at(index) ⇒ Object



79
80
81
82
# File 'app/models/exercise.rb', line 79

def sibling_at(index)
  index = number + index unless index.positive?
  guide.exercises.find_by(number: index)
end

#solvable?Boolean



245
246
247
# File 'app/models/exercise.rb', line 245

def solvable?
  is_a? ::Problem
end

#splitted_descriptionObject



183
184
185
# File 'app/models/exercise.rb', line 183

def splitted_description
  description.split('> ')
end

#structural_parentObject



63
64
65
# File 'app/models/exercise.rb', line 63

def structural_parent
  guide
end

#submission_for(user) ⇒ Object



100
101
102
# File 'app/models/exercise.rb', line 100

def submission_for(user)
  assignment_for(user).submission
end

#to_expanded_resource_h(options = {}) ⇒ Object

Keep this list up to date with Mumuki::Domain::Store::Github::ExerciseSchema



139
140
141
142
143
144
145
146
147
# File 'app/models/exercise.rb', line 139

def to_expanded_resource_h(options={})
  language_resource_h = language.to_embedded_resource_h if language != guide.language
  as_json(only: BASIC_RESOURCE_FIELDS)
    .merge(id: bibliotheca_id, language: language_resource_h, type: type.underscore)
    .merge(settings: self[:settings])
    .merge(RANDOMIZED_FIELDS.map { |it| [it, self[it]] }.to_h)
    .symbolize_keys
    .tap { |it| it.markdownified!(:hint, :corollary, :description, :teacher_info) if options[:markdownified] }
end

#to_resource_h(*args) ⇒ Object



133
134
135
# File 'app/models/exercise.rb', line 133

def to_resource_h(*args)
  to_expanded_resource_h(*args).compact
end

#transparent_idObject



88
89
90
# File 'app/models/exercise.rb', line 88

def transparent_id
  "#{guide.transparent_id}/#{bibliotheca_id}"
end

#transparent_paramsObject



92
93
94
# File 'app/models/exercise.rb', line 92

def transparent_params
  guide.transparent_params.merge(bibliotheca_id: bibliotheca_id)
end

#try_submit_solution!(user, solution = {}) ⇒ Object

Submits the user solution only if the corresponding assignment has attemps left



201
202
203
204
205
206
207
208
# File 'app/models/exercise.rb', line 201

def try_submit_solution!(user, solution={})
  assignment = assignment_for(user)
  if assignment.attempts_left?
    submit_solution!(user, solution)
  else
    assignment
  end
end

#used_in?(organization = Organization.current) ⇒ Boolean



51
52
53
# File 'app/models/exercise.rb', line 51

def used_in?(organization=Organization.current)
  guide.usage_in_organization(organization).present?
end