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?, #has_progress_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

Methods included from WithPgLock

#with_pg_lock

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



220
221
222
# File 'app/models/exercise.rb', line 220

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

.locate!(slug_and_bibliotheca_id) ⇒ Object



224
225
226
227
# File 'app/models/exercise.rb', line 224

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

Returns:

  • (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

Returns:

  • (Boolean)


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

def console?
  queriable?
end

#content_used_in?(organization) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#custom?Boolean

Returns:

  • (Boolean)


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

def custom?
  false
end

#description_contextObject



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

def description_context
  splitted_description.first.markdownified
end

#description_taskObject



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

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



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

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

Returns:

  • (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



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

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

Returns:

  • (Boolean)


205
206
207
# File 'app/models/exercise.rb', line 205

def limited?
  !results_hidden? && navigable_parent.limited_for?(self)
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



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

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

Returns:

  • (Boolean)


209
210
211
# File 'app/models/exercise.rb', line 209

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



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

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

Returns:

  • (Boolean)


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

def solvable?
  is_a? ::Problem
end

#splitted_descriptionObject



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

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



193
194
195
196
197
198
199
200
# File 'app/models/exercise.rb', line 193

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

Returns:

  • (Boolean)


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

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