class Guide < Content BASIC_RESOURCE_FIELDS = %i( authors beta collaborators corollary custom_expectations expectations extra id_format learn_more private settings sources teacher_info type) include WithStats, WithExpectations, WithLanguage markdown_on :corollary, :sources, :learn_more, :teacher_info numbered :exercises has_many :exercises, -> { order(number: :asc) }, dependent: :destroy serialize :settings, Hash self.inheritance_column = nil enum type: [:learning, :practice] alias_method :structural_children, :exercises def clear_progress!(user, organization=Organization.current) transaction do exercises.each do |exercise| exercise.find_assignment_for(user, organization)&.destroy! end end end def lesson usage_in_organization_of_type Lesson end def chapter lesson.try(:chapter) #FIXME temporary end def exercises_count exercises.count end def next_exercise(user) user.next_exercise_at(self) end # TODO: Make use of pending_siblings logic def pending_exercises(user) exercises. joins("left join public.assignments assignments on assignments.exercise_id = exercises.id and assignments.submitter_id = #{user.id} and assignments.submission_status = #{Mumuki::Domain::Status::Submission::Passed.to_i}"). where('assignments.id is null') end def first_exercise exercises.first end def search_tags exercises.flat_map(&:search_tags).uniq end def done_for?(user) stats_for(user).done? end # Finds an exercise by bibliotheca_id within this guide def locate_exercise!(bibliotheca_id) exercises.find_by!(bibliotheca_id: bibliotheca_id) end def import_from_resource_h!(resource_h) dirty_progress_if_structural_children_changed! do self.assign_attributes whitelist_attributes(resource_h) self.language = Language.for_name(resource_h.dig(:language, :name)) self.save! resource_h[:exercises]&.each_with_index do |e, i| exercise = Exercise.find_by(guide_id: self.id, bibliotheca_id: e[:id]) exercise_type = e[:type] || 'problem' exercise = exercise ? exercise.ensure_type!(exercise_type.as_module_name) : exercise_type.as_module.new(guide_id: self.id, bibliotheca_id: e[:id]) exercise.import_from_resource_h! (i+1), e end new_ids = resource_h[:exercises].map { |it| it[:id] } self.exercises.where.not(bibliotheca_id: new_ids).destroy_all reload end end # Keep this list up to date with # Mumuki::Domain::Store::Github::GuideSchema def to_expanded_resource_h(options={}) as_json(only: BASIC_RESOURCE_FIELDS) .symbolize_keys .merge(super) .merge(exercises: exercises.map { |it| it.to_resource_h(options) }) .merge(language: language.to_embedded_resource_h) .tap { |it| it.markdownified!(:corollary, :description, :teacher_info) if options[:markdownified] } end def to_markdownified_resource_h to_resource_h(markdownified: true) end def as_lesson_of(topic) topic.lessons.find_by(guide_id: id) || Lesson.new(guide: self, topic: topic) end def as_complement_of(book) #FIXME duplication book.complements.find_by(guide_id: id) || Complement.new(guide: self, book: book) end def resettable? usage_in_organization.resettable? end ## Forking def fork_children_into!(dup, _organization, _syncer) dup.exercises = exercises.map(&:dup) end end