Class: Exercise

Direct Known Subclasses

Challenge, Reading

Constant Summary collapse

RANDOMIZED_FIELDS =
[:default_content, :description, :extra, :hint, :test]
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 included from WithDescription

#description_teaser

Methods inherited from ApplicationRecord

aggregate_of, all_except, defaults, #delete, #destroy!, numbered, organic_on, resource_fields, #save, #save_and_notify!, #save_and_notify_changes!, serialize_symbolized_hash_array, #update_and_notify!, update_or_create!, whitelist_attributes

Class Method Details

.find_transparently!(params) ⇒ Object



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

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

.locate!(slug_and_bibliotheca_id) ⇒ Object



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

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)


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

def choice?
  false
end

#choice_index_for(value) ⇒ Object



116
117
118
# File 'app/models/exercise.rb', line 116

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

#choice_valuesObject



112
113
114
# File 'app/models/exercise.rb', line 112

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

#console?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'app/models/exercise.rb', line 42

def console?
  queriable?
end

#custom?Boolean

Returns:

  • (Boolean)


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

def custom?
  false
end

#description_contextObject



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

def description_context
  splitted_description.first.markdownified
end

#description_taskObject



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

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

#ensure_type!(type) ⇒ Object



147
148
149
150
151
152
153
# File 'app/models/exercise.rb', line 147

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

#files_for(current_content) ⇒ Object



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

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

#friendlyObject



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

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

#guide_done_for?(user) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#import_from_resource_h!(number, resource_h) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'app/models/exercise.rb', line 95

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



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

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)


202
203
204
# File 'app/models/exercise.rb', line 202

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

#messages_path_for(user) ⇒ Object



160
161
162
# File 'app/models/exercise.rb', line 160

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



164
165
166
# File 'app/models/exercise.rb', line 164

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

#new_solutionObject



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

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

#pending_siblings_for(user) ⇒ Object



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

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

#previousObject



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

def previous
  sibling_at number.pred
end

#reclassify!(type) ⇒ Object



155
156
157
158
# File 'app/models/exercise.rb', line 155

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

#reset!Object



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

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)


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

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

#search_tagsObject



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

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

#settingsObject



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

def settings
  guide.settings.deep_merge super
end

#sibling_at(index) ⇒ Object



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

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

#splitted_descriptionObject



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

def splitted_description
  description.split('> ')
end

#structural_parentObject



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

def structural_parent
  guide
end

#submission_for(user) ⇒ Object



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

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



126
127
128
129
130
131
132
133
134
135
136
# File 'app/models/exercise.rb', line 126

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(expectations: self[:expectations])
    .merge(custom_expectations: self[:custom_expectations])
    .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



120
121
122
# File 'app/models/exercise.rb', line 120

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

#transparent_idObject



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

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

#transparent_paramsObject



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

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



190
191
192
193
194
195
196
197
# File 'app/models/exercise.rb', line 190

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)


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

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