WikiEducationFoundation/WikiEduDashboard

View on GitHub
lib/assignment_manager.rb

Summary

Maintainability
A
55 mins
Test Coverage
# frozen_string_literal: true

# Tested via AssignmentsController
require_dependency "#{Rails.root}/lib/article_utils"
require_dependency "#{Rails.root}/lib/importers/rating_importer"
require_dependency "#{Rails.root}/lib/importers/article_importer"

class AssignmentManager
  def initialize(course:, user_id: nil, wiki: nil, title: nil, role: nil)
    @course = course
    @user_id = user_id
    @wiki = wiki
    @title = title
    @role = role
  end

  def create_random_peer_reviews
    peer_review_count = @course.peer_review_count || 1

    @course.students.each do |student|
      currently_reviewing = @course.assignments.reviewing
                                   .where(user_id: student.id).pluck(:article_title)
      needed_count = peer_review_count - currently_reviewing.count
      next unless needed_count.positive?

      reviewables = reviewable_titles(student, needed_count, currently_reviewing)

      assign_peer_reviews(student, reviewables)
    end
  end

  def create_assignment
    set_clean_title
    set_article_from_database
    check_wiki_edu_discouraged_article
    import_article_from_wiki unless @article
    # TODO: update rating via Sidekiq worker
    update_article_rating if @article
    Assignment.create!(user_id: @user_id, course: @course,
                       article_title: @clean_title, wiki: @wiki, article: @article,
                       role: @role)
  rescue ActiveRecord::RecordInvalid => e
    message = if e.message.include?('invalid')
                "#{@clean_title} is not a valid article title."
              else
                "#{@clean_title} is already assigned to this user."
              end
    raise DuplicateAssignmentError, message
  end

  def claim_assignment(claimed_assignment)
    @wiki = claimed_assignment.wiki
    @title = claimed_assignment.article_title
    @role = Assignment::Roles::ASSIGNED_ROLE
    if @course.retain_available_articles?
      create_assignment
    else
      claimed_assignment.update(user_id: @user_id)
      claimed_assignment
    end
  end

  private

  def assigned_titles
    @assigned_titles ||= @course.assignments.assigned
                                .where(user: @course.students).pluck(:article_title).shuffle
  end

  def reviewed_titles
    @course.assignments.reviewing
           .pluck(:article_title)
  end

  def review_counts
    @review_counts ||= reviewed_titles.each_with_object(Hash.new(0)) do |title, title_counts|
      title_counts[title] += 1
    end
  end

  def own_assigned_titles(student)
    @course.assignments.assigned.where(user_id: student.id).pluck(:article_title)
  end

  def reviewable_titles(student, needed_count, currently_reviewing)
    # all classmates' assigned titles that aren't assigned to this student
    # and the student isn't already reviewing it
    possible_reviews = assigned_titles.uniq - currently_reviewing - own_assigned_titles(student)
    # order by fewest reviews
    possible_reviews.sort_by { |title| review_counts[title] }.take(needed_count)
  end

  def set_clean_title
    # Wiktionary allows titles that begin lower case.
    # Other projects enforce capitalization of the first letter.
    @clean_title = ArticleUtils.format_article_title(@title, @wiki)
  end

  def set_article_from_database
    # We double check that the titles are equal to avoid false matches of case variants.
    # We can revise this once the database is set to use case-sensitive collation.
    articles = Article.where(title: @clean_title, wiki_id: @wiki.id,
                             namespace: Article::Namespaces::MAINSPACE)
    exact_title_matches = articles.select { |article| article.title == @clean_title }
    @article = exact_title_matches.first
  end

  def check_wiki_edu_discouraged_article
    category = Category.find_by(name: ENV['blocked_assignment_category'])

    if category.present? && category.article_titles.include?(@clean_title)
      raise DiscouragedArticleError, I18n.t('assignments.blocked_assignment', title: @clean_title)
    end
  end

  def import_article_from_wiki
    ArticleImporter.new(@wiki).import_articles_by_title([@clean_title])
    set_article_from_database
  end

  def update_article_rating
    RatingImporter.update_rating_for_article(@article)
  end

  def assign_peer_reviews(student, reviewable_titles)
    reviewable_titles.each do |title|
      Assignment.create!(user_id: student.id, course: @course,
                         article_title: title, wiki: @wiki,
                         role: Assignment::Roles::REVIEWING_ROLE)
      review_counts[title] += 1
    end
  end

  class DuplicateAssignmentError < StandardError; end
  class DiscouragedArticleError < StandardError; end
end