require "rubygems/text"

class GemTypo
  attr_reader :protected_gem

  include Gem::Text

  DOWNLOADS_THRESHOLD = 10_000_000
  SIZE_THRESHOLD = 4

  def initialize(rubygem_name)
    @rubygem_name       = rubygem_name.downcase
    @distance_threshold = distance_threshold
  end

  def protected_typo?
    return false if @rubygem_name.size < GemTypo::SIZE_THRESHOLD

    gem_typo_exceptions = GemTypoException.all.pluck(:name)
    return false if gem_typo_exceptions.include?(@rubygem_name)

    protected_gems.each do |protected_gem|
      distance = levenshtein_distance(@rubygem_name, protected_gem)
      if distance <= @distance_threshold
        @protected_gem = protected_gem
        return true
      end
    end

    false
  end

  private

  def distance_threshold
    @rubygem_name.size == GemTypo::SIZE_THRESHOLD ? 1 : 2
  end

  def protected_gems
    Rubygem.joins(:gem_download)
      .where("gem_downloads.count > ?", GemTypo::DOWNLOADS_THRESHOLD)
      .where.not(name: @rubygem_name)
      .pluck(:name)
  end
end

require "rubygems/text"

class GemTypo
  attr_reader :protected_gem

  include Gem::Text

  DOWNLOADS_THRESHOLD = 10_000_000
  SIZE_THRESHOLD = 4

  def initialize(rubygem_name)
    @rubygem_name       = rubygem_name.downcase
    @distance_threshold = distance_threshold
  end

  def protected_typo?
    return false

    return false if @rubygem_name.size < GemTypo::SIZE_THRESHOLD

    gem_typo_exceptions = GemTypoException.all.pluck(:name)
    return false if gem_typo_exceptions.include?(@rubygem_name)

    protected_gems.each do |protected_gem|
      distance = levenshtein_distance(@rubygem_name, protected_gem)
      if distance <= @distance_threshold
        @protected_gem = protected_gem
        return true
      end
    end

    false
  end

  private

  def distance_threshold
    @rubygem_name.size == GemTypo::SIZE_THRESHOLD ? 1 : 2
  end

  def protected_gems
    Rubygem.joins(:gem_download)
      .where("gem_downloads.count > ?", GemTypo::DOWNLOADS_THRESHOLD)
      .where.not(name: @rubygem_name)
      .pluck(:name)
  end
end

Disable typo protection for now
Disable typo protection for now

This is a good idea, but it's blocking a lot of legitimate gems, and
it's too hard for our limited support time to go to manually allowing
gems through.

A Slack feed of gems that look like they might be typos to allow humans
to review would be a more practical way to implement this sort of idea.
Better yet, a Slack message with a single button to yank the gem and/or
the user who pushed the gem. :looks hopefully in @colby-swandale's
direction:
