assemblymade/coderwall

View on GitHub
app/models/skill.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# == Schema Information
#
# Table name: skills
#
#  id                 :integer          not null, primary key
#  user_id            :integer
#  name               :citext           not null
#  endorsements_count :integer          default(0)
#  created_at         :datetime
#  updated_at         :datetime
#  tokenized          :string(255)
#  weight             :integer          default(0)
#  repos              :text
#  speaking_events    :text
#  attended_events    :text
#  deleted            :boolean          default(FALSE), not null
#  deleted_at         :datetime
#  links              :json             default("{}")
#

class Skill < ActiveRecord::Base
  never_wastes

  SPACE = ' '
  BLANK = ''

  belongs_to :user, touch: true
  has_many :endorsements

  validates_presence_of :tokenized
  validates_presence_of :user_id
  validates_uniqueness_of :tokenized, scope: [:user_id, :deleted]

  before_validation :tokenize_name
  before_create :scrub_name
  before_create :apply_facts
  after_create :generate_event

  serialize :repos, Array
  serialize :attended_events, Array
  serialize :speaking_events, Array
  serialize :links,  ActiveRecord::Coders::JSON


  default_scope {where(deleted: false)}
  scope :deleted, -> { unscoped.where(deleted: true) }

  def self.tokenize(value)
    v = value.to_s.gsub('&', 'and').downcase.gsub(/\s|\./, BLANK)
    v = 'nodejs' if v == 'node'
    Sanitize.clean(v)
  end

  def self.deleted?(user_id, skill_name)
    deleted.where(user_id: user_id, name: skill_name).any?
  end

  # def merge_with(another_skill)
  #   if another_skill.user_id == self.user_id
  #     another_skill.endorsements.each do |endorsement|
  #       self.endorsed_by(endorsement.endorser)
  #     end
  #     self.repos           += another_skill.repos
  #     self.attended_events += another_skill.attended_events
  #     self.speaking_events += another_skill.speaking_events
  #   end
  # end

  def endorsed_by(endorser)
    # endorsed is only in here during migration of endorsement to skill
    endorsements.create!(endorser: endorser, endorsed: self.user, specialty: self.name)
  end

  def has_endorsements?
    endorsements_count > 0
  end

  def endorsed_by?(user)
    endorsements.where(endorsing_user_id: user.id).any?
  end

  def has_repos?
    repos.size > 0
  end

  def has_events?
    speaking_events.size > 0 || attended_events.size > 0
  end

  def matching_badges_in(badges)
    badges.select { |badge| badge.tokenized_skill_name == tokenized }
  end

  def matching_protips_in(protips)
    protips.select { |protip| protip.original? && protip.tokenized_skills.include?(tokenized) }
  end

  def protips
    @protips ||= matching_protips_in(user.protips)
  end

  def has_protips?
    !protips.empty?
  end

  def locked?
    return false #no longer lock skills
  end

  def trending_protips
    protips.select { |protip| protip.upvotes >= 5 }
  end

  def deletable?
    (endorsements_count + repos.size + speaking_events.size + badges_count) <= 0
  end

  def endorse_message
    "#{user.short_name} needs just #{3 - endorsements.size} more endorsements to unlock #{name}"
  end

  def apply_facts(facts = nil)
    facts = user.facts if facts.nil?
    repos.clear
    attended_events.clear
    speaking_events.clear
    facts.each do |fact|
      associate_repo_fact(fact)
      associate_event_fact(fact)
    end
    self.weight = endorsements_count + repos.count + (speaking_events.count * 2) + attended_events.count + (badges_count * 3)
  end

  def generate_event
    GenerateEventJob.perform_async(self.event_type, Audience.user_reach(self.user.id), self.to_event_hash)
  end

  def to_event_hash
    { skill: { name: self.name, add_path: Rails.application.routes.url_helpers.add_skill_path(skill: { name: self.name }) },
      user:  { username: self.user.username } }
  end

  def event_type
    :new_skill
  end

  def badges_count
    matching_badges_in(user.badges).size
  end

  def associate_repo_fact(fact)
    if fact.tagged?('personal', 'repo', 'original')
      tokenized_tags = fact.tags.map { |t| Skill.tokenize(t) }

      if tokenized_tags.include?(tokenized)
        self.repos << { name: fact.name, url: fact.url }
      end
    end
  end

  def associate_event_fact(fact)
    if fact.tagged?('event')
      tokenized_tags = fact.tags.map { |t| Skill.tokenize(t) }
      if tokenized_tags.include?(tokenized)
        if fact.tagged?('spoke')
          self.speaking_events << { name: fact.name, url: fact.url }
        else
          self.attended_events << { name: fact.name, url: fact.url }
        end
      end
    end
  end

  protected

  def tokenize_name
    self.tokenized = self.class.tokenize(name) if name_changed?
  end

  def scrub_name
    self.name = name.strip
  end
end