app/models/user.rb

Summary

Maintainability
B
6 hrs
Test Coverage
class User < ActiveRecord::Base
  include Concerns::Slackable
  include Concerns::Cacheable

  QUORUM_PERCENTAGE = 0.6

  belongs_to :team
  has_many :votes
  has_many :calendar_events
  has_and_belongs_to_many :companies, -> { distinct }

  validates :username, presence: true, uniqueness: true
  validates :cached_name, presence: true, uniqueness: true
  validates :slack_id, uniqueness: { allow_nil: true }
  validates :trello_id, uniqueness: { allow_nil: true }

  before_validation :set_slack_id, on: :create
  before_validation :set_cached_name, on: :create
  after_create :add_to_wit

  devise

  def self.inactive(team, since = Time.now)
    where(team: team)
    .where(
      arel_table[:inactive_since].lt(since).
      or(arel_table[:created_at].gt(since))
    )
  end

  def self.active(team, since = Time.now)
    where(team: team)
    .where(
      arel_table[:inactive_since].gteq(since).
      or(arel_table[:inactive_since].eq(nil))
    )
    .where(
      arel_table[:created_at].lteq(since)
    )
  end

  def self.quorum(team, at)
    (active(team, at).count * QUORUM_PERCENTAGE).to_i
  end

  def active?
    (inactive_since.blank? || inactive_since >= Time.now) && created_at <= Time.now
  end

  def toggle_active!(time = Time.now)
    update!(inactive_since: active? ? time : nil)
  end

  def stats
    {
      yes_votes: votes.yes.count,
      no_votes: votes.no.count,
      agreed: agreed.count,
      disagreed: disagreed.count,
      averages: Vote.metrics(votes)
    }.with_indifferent_access
  end

  def email
    "#{username}@#{ENV['DOMAIN']}"
  end

  def slack_name
    cached { "@#{slack_user.name}" } if slack_user.present?
  end

  def send!(message)
    slack_send! slack_id, message
  end

  def name
    cached_name || set_cached_name
  end

  def first_name
    slack_or_block(:first_name) { name.split(' ').first }
  end

  def last_name
    slack_or_block(:last_name) { name.split(' ').drop(1).join(' ') }
  end

  def initials
    cached { trello_user.try(:initials) || "#{first_name.first}#{last_name.first}" }
  end

  def title
    slack_or_block :title
  end

  def ensure_token
    authentication_token || Devise.friendly_token.tap do |token|
      update! authentication_token: token
    end
  end

  def self.from_omniauth(auth)
    return nil unless auth.present?
    from_email(auth.info.email)&.tap do |user|
      user.update! access_token: auth.credentials.token, refresh_token: auth.credentials.refresh_token
    end
  end

  def self.from_slack(slack_id)
    user = where(slack_id: slack_id).first
    return user if user.present?

    user = slack_client_factory.users_info(user: slack_id).user
    from_email user.profile.email
  rescue Slack::Web::Api::Error
    nil
  end

  def self.from_trello(trello_id)
    user = where(trello_id: trello_id).first
    return user if user.present?

    user = Trello::Member.find trello_id
    if user.email.present?
      from_email user.email
    else
      where(cached_name: user.full_name).or(where(username: user.username)).first
    end
  rescue Trello::Error
    nil
  end

  def self.from_email(email)
    from_username_domain *email.split('@')
  end

  def self.from_username_domain(username, domain)
    return nil unless domain == ENV['DOMAIN']
    where(username: username).first_or_create!
  end

  def self.from_multi(multi)
    Array.wrap(multi).map do |item|
      return item if item.is_a?(User) || item.is_a?(Team)
      return find(item) if item.is_a?(Integer)
      return where(username: item).first if item.is_a?(String)
    end.compact
  end

  def trello_user
    @trello_user ||= find_trello_user.tap do |user|
      if user.present?
        self.trello_id = user.id
        save! if changed?
      end
    end
  end

  def find_trello_user
    [trello_id, email, username, slack_user&.name].each do |query|
      next unless query.present?
      user = begin
        Trello::Member.find(query)
      rescue Trello::Error, SystemStackError
        nil
      end
      return user if user.present?
    end
    nil
  end

  def as_json(options = {})
    options.reverse_merge!(
      only: [:username, :inactive_since, :trello_id, :slack_id]
    )
    super(options).merge(
      name: cached_name,
      team: team&.name
    )
  end

  def admin?
    username == ENV['ADMIN']
  end

  private

  def fetch_name
    slack_or_block :real_name do
      trello_user.try(:full_name).present? ? trello_user.full_name : self[:cached_name]
    end
  end


  def add_to_wit
    Http::Wit::Entity.new('slack_user').add_value("<@#{slack_id}>") if slack_id.present?
  end

  def set_cached_name
    self.cached_name ||= fetch_name || username
  end

  def set_slack_id
    self.slack_id ||= begin
      members = slack_client.users_search(user: email).members
      members = slack_client.users_search(user: username).members unless members.present?
      members.first
    end.try(:id)
  end

  def slack_or_block(property)
    key_cached [property] do
      from_slack = slack_user&.profile&.public_send(property)
      if from_slack.present?
        from_slack.titleize
      else
        yield if block_given?
      end
    end
  end

  def slack_user
    @slack_user ||= slack_client.users_info(user: slack_id).user if slack_id.present?
  end

  def agreed
    @agreed ||= company_votes.select { |c, v| c.funded? == v.yes? }
  end

  def disagreed
    @disagreed ||= company_votes.except(agreed.keys)
  end

  def company_votes
    @company_votes ||= votes.includes(:company).final.map { |vote| [vote.company, vote] }.to_h
  end
end