rtopitt/bolao2014

View on GitHub
app/models/user.rb

Summary

Maintainability
A
0 mins
Test Coverage
class User < ActiveRecord::Base

  EMAIL_REGEX = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i
  PASSWORD_EXPIRATION_MINUTES = 15

  has_one :bet, dependent: :destroy

  validates :email,
    presence: true,
    uniqueness: { case_sensitive: false },
    format: { with: EMAIL_REGEX, allow_blank: true }

  validates :name,
    uniqueness: { case_sensitive: false },
    allow_blank: true

  validates :time_zone,
    presence: true,
    inclusion: { in: ActiveSupport::TimeZone.all.map(&:name), allow_blank: true }

  validates :locale,
    presence: true,
    inclusion: { in: I18n.available_locales.map(&:to_s), allow_blank: true }

  validates :remember_me_token,
    presence: { on: :update }, # on create we now it's there
    uniqueness: { case_sensitive: true, allow_blank: true }

  validates :authentication_token,
    uniqueness: { case_sensitive: true, allow_blank: true }

  before_validation :normalize_email
  before_create :set_remember_me_token

  # TODO spec
  def email_with_name
    if self.name.present?
      "#{self.name} <#{self.email}>"
    else
      self.email
    end
  end

  # TODO spec
  def name_or_email
    self.name.present? ? self.name : self.email
  end

  def authentication_token_exists?
    self.authentication_token.present? && self.authentication_token_expires_at.present?
  end

  def generate_authentication_token!
    clear_text_token = non_ambiguous_token(8)
    expiration = (Time.zone.now + PASSWORD_EXPIRATION_MINUTES.minutes)
    self.authentication_token_expires_at = expiration
    self.authentication_token = BCrypt::Password.create(authentication_token_input(clear_text_token)).to_s
    self.save!
    clear_text_token
  end

  def authentication_token_expired?
    raise RuntimeError.new, 'authentication_token not set' unless authentication_token_exists?
    self.authentication_token_expires_at < Time.zone.now
  end

  def validate_authentication_token!(clear_text_token)
    return false unless authentication_token_exists?
    result = !authentication_token_expired? &&
      BCrypt::Password.new(self.authentication_token) == authentication_token_input(clear_text_token.upcase)
    reset_authentication_token!
    result
  end

  def set_defaults
    self.locale = I18n.locale.to_s
    self.time_zone = Time.zone.name
    self.build_bet if self.bet.nil?
  end

  private

  def reset_authentication_token!
    self.authentication_token_expires_at = nil
    self.authentication_token = nil
    self.save!
  end

  def authentication_token_input(clear_text_token)
    [
      self.email,
      clear_text_token,
      self.authentication_token_expires_at.to_i
    ].reject(&:blank?).join('--')
  end

  # before_create
  def set_remember_me_token
    begin
      self.remember_me_token = SecureRandom.urlsafe_base64
    end while User.exists?(remember_me_token: self.remember_me_token)
  end

  # before_validation
  def normalize_email
    self.email.downcase! unless self.email.blank?
  end

  # Generates a random string from a set of easily readable characters
  def non_ambiguous_token(length=8)
    charset = %w{ 2 3 4 6 7 9 A C D E F G H J K M N P Q R T V W X Y Z }
    (0...length).map { charset.to_a[SecureRandom.random_number(charset.size)] }.join
  end

end