ophrescue/RescueRails

View on GitHub
app/models/user.rb

Summary

Maintainability
A
1 hr
Test Coverage
#    Copyright 2017 Operation Paws for Homes
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
# == Schema Information
#
# Table name: users
#
#  id                              :integer          not null, primary key
#  name                            :string
#  email                           :string
#  created_at                      :datetime
#  updated_at                      :datetime
#  encrypted_password              :string
#  salt                            :string
#  admin                           :boolean          default(FALSE)
#  password_reset_token            :string
#  password_reset_sent_at          :datetime
#  is_foster                       :boolean          default(FALSE)
#  phone                           :string
#  address1                        :string
#  address2                        :string
#  city                            :string
#  region                          :string(2)
#  postal_code                     :string
#  duties                          :string
#  edit_dogs                       :boolean          default(FALSE)
#  share_info                      :text
#  edit_my_adopters                :boolean          default(FALSE)
#  edit_all_adopters               :boolean          default(FALSE)
#  locked                          :boolean          default(FALSE)
#  edit_events                     :boolean          default(FALSE)
#  other_phone                     :string
#  lastlogin                       :datetime
#  lastverified                    :datetime
#  available_to_foster             :boolean          default(FALSE)
#  foster_dog_types                :text
#  complete_adopters               :boolean          default(FALSE)
#  add_dogs                        :boolean          default(FALSE)
#  ban_adopters                    :boolean          default(FALSE)
#  dl_resources                    :boolean          default(TRUE)
#  agreement_id                    :integer
#  house_type                      :string(40)
#  breed_restriction               :boolean
#  weight_restriction              :boolean
#  has_own_dogs                    :boolean
#  has_own_cats                    :boolean
#  children_under_five             :boolean
#  has_fenced_yard                 :boolean
#  can_foster_puppies              :boolean
#  parvo_house                     :boolean
#  admin_comment                   :text
#  is_photographer                 :boolean          default(FALSE)
#  writes_newsletter               :boolean          default(FALSE)
#  is_transporter                  :boolean          default(FALSE)
#  mentor_id                       :integer
#  latitude                        :float
#  longitude                       :float
#  dl_locked_resources             :boolean          default(FALSE)
#  training_team                   :boolean          default(FALSE)
#  confidentiality_agreement_id    :integer
#  foster_mentor                   :boolean          default(FALSE)
#  public_relations                :boolean          default(FALSE)
#  fundraising                     :boolean          default(FALSE)
#  translator                      :boolean          default(FALSE), not null
#  known_languages                 :string(255)
#  code_of_conduct_agreement_id    :integer
#  medical_behavior_permission     :boolean          default(FALSE)
#  boarding_buddies                :boolean          default(FALSE), not null
#  social_media_manager            :boolean          default(FALSE), not null
#  graphic_design                  :boolean          default(FALSE), not null
#  country                         :string(3)        not null
#  active                          :boolean          default(FALSE), not null
#  confirmation_token              :string(128)
#  remember_token                  :string(128)
#  avatar_file_name                :string
#  avatar_content_type             :string
#  avatar_file_size                :integer
#  avatar_updated_at               :datetime
#  has_children                    :boolean          default(FALSE)
#  insurance_training_agreement_id :integer
#

class User < ApplicationRecord
  audited except: %i[lastverified password encrypted_password password_reset_token salt password_reset_sent_at], on: [:update]
  include Clearance::User
  include Filterable

  attr_accessor :accessible

  strip_attributes only: :email

  validates :name,  presence: true,
                    length: { maximum: 50 }

  validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }

  validates :country, length: { is: 3 }
  validate :country_is_supported

  validates :password,
            presence: true,
            confirmation: true,
            length: { within: 8..40 },
            unless: :skip_password_validation?

  validates_with RegionValidator
  validates_with PostalCodeValidator

  geocoded_by :full_street_address
  after_validation :geocode, ->(obj){ obj.address1.present? and obj.address1_changed? }
  before_update :unset_permissions_on_locked_user

  has_many :foster_cats, class_name: 'Cat', foreign_key: 'foster_id'
  has_many :current_foster_cats, -> { where(status: ['adoptable', 'adoption pending', 'on hold', 'coming soon', 'return pending']) }, class_name: 'Cat', foreign_key: 'foster_id'
  has_many :coordinated_cats, -> { where(status: ['adoptable', 'adopted', 'adoption pending', 'on hold', 'coming soon', 'return pending']) }, class_name: 'Cat', foreign_key: 'coordinator_id'

  has_many :foster_dogs, class_name: 'Dog', foreign_key: 'foster_id'
  has_many :current_foster_dogs, -> { where(status: ['adoptable', 'adoption pending', 'on hold', 'coming soon', 'return pending']) }, class_name: 'Dog', foreign_key: 'foster_id'
  has_many :coordinated_dogs, -> { where(status: ['adoptable', 'adopted', 'adoption pending', 'on hold', 'coming soon', 'return pending']) }, class_name: 'Dog', foreign_key: 'coordinator_id'

  has_many :comments

  has_many :contracts, -> { order('created_at DESC') }, as: :contractable

  has_one :agreement, -> { where(agreement_type: Attachment::AGREEMENT_TYPE_FOSTER) },
          as: :attachable, class_name: 'Attachment', dependent: :destroy
  accepts_nested_attributes_for :agreement

  has_one :confidentiality_agreement, -> { where(agreement_type: Attachment::AGREEMENT_TYPE_CONFIDENTIALITY) },
          as: :attachable, class_name: 'Attachment', dependent: :destroy
  accepts_nested_attributes_for :confidentiality_agreement

  has_one :code_of_conduct_agreement, -> { where(agreement_type: Attachment::AGREEMENT_TYPE_CODE_OF_CONDUCT) },
          as: :attachable, class_name: 'Attachment', dependent: :destroy
  accepts_nested_attributes_for :code_of_conduct_agreement

  has_one :insurance_training_agreement, -> { where(agreement_type: Attachment::AGREEMENT_TYPE_INSURANCE_TRAINING) },
          as: :attachable, class_name: 'Attachment', dependent: :destroy
  accepts_nested_attributes_for :insurance_training_agreement

  has_many :assignments, class_name: 'Adopter', foreign_key: 'assigned_to_user_id'
  has_many :active_applications, -> { where(status: ['new', 'pend response', 'workup', 'ready for final', 'approved']) }, class_name: 'Adopter', foreign_key: 'assigned_to_user_id'
  belongs_to :mentor, class_name: 'User', foreign_key: 'mentor_id', optional: true
  has_many :mentees, class_name: 'User', foreign_key: 'mentor_id'

  has_and_belongs_to_many :badges, optional: true

  before_validation :sanitize_postal_code
  before_validation :sanitize_region

  before_save :format_cleanup
  before_create :chimp_subscribe
  before_update :chimp_check

  scope :unlocked,                -> { where(locked: false) }

  scope :inactive_volunteer,      -> (status = false) { where(active: status, locked: status)}
  scope :house_type,              -> (type) { where(house_type: type) }

  HASH_SECRET = "5b0a2cd2064828d34ad737f9f6a586fb773297a01639c2b3ff24fcb7"
  has_attached_file :avatar, styles: { thumb: "75x75>",
                                       medium: "300x300>" },
                             s3_permissions: "public-read",
                             hash_secret: HASH_SECRET,
                             default_url: "no_profile_photo.png"

  PAPERCLIP_STORAGE_PATH = { test: "/system/test/user_photo/:hash.:extension",
                             production: "/user_photo/:hash.:extension",
                             staging: "/user_photo/:hash.:extension" }

  CONTENT_TYPES = {"Images" => ['image/jpg', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif']}

  MIME_TYPES = CONTENT_TYPES.values.flatten

  validates_attachment_content_type :avatar,
                                    content_type: MIME_TYPES,
                                    message: 'Images Only.'
  PHOTO_MAX_SIZE = 5
  validates_attachment_size :avatar, less_than: PHOTO_MAX_SIZE.megabytes

  HOUSE_TYPES = %w[rent own]

  RANGES = %w[5 10 15 20 30]

  # mapping of scope name (= query string parameter) to model attributes
  FILTER_FLAGS = { active_volunteer: :active,
                   admin: :admin,
                   adoption_coordinator: :edit_my_adopters,
                   available_to_foster: :available_to_foster,
                   boarding_buddy: :boarding_buddies,
                   dog_adder: :add_dogs,
                   dog_editor: :edit_dogs,
                   event_planner: :edit_events,
                   foster: :is_foster,
                   foster_mentor: :foster_mentor,
                   fundraising: :fundraising,
                   graphic_designer: :graphic_design,
                   has_cats: :has_own_cats,
                   has_children: :has_children,
                   has_children_under_five: :children_under_five,
                   has_dogs: :has_own_dogs,
                   has_fence: :has_fenced_yard,
                   has_parvo_house: :parvo_house,
                   locked: :locked,
                   medical_behavior: :medical_behavior_permission,
                   newsletter: :writes_newsletter,
                   clerical: :is_clerical,
                   photographer: :is_photographer,
                   public_relations: :public_relations,
                   puppies_ok: :can_foster_puppies,
                   dogs_ok: :can_foster_dogs,
                   cats_ok: :can_foster_cats,
                   social_media: :social_media_manager,
                   training_team: :training_team,
                   translator: :translator,
                   transporter: :is_transporter,
                   foster_friend: :foster_friend }

  FILTER_FLAGS.each do |param,attr|
    scope :"#{param}", ->(status = true) { where "#{attr}": status}
  end

  def display_name
    name
  end

  def self.authenticate_with_salt(id, cookie_salt)
    user = find_by(id: id)
    user && user.salt == cookie_salt ? user : nil
  end

  def out_of_date?
    lastverified.blank? || (lastverified.to_date < 120.days.ago.to_date)
  end

  def chimp_subscribe
    UserSubscribeJob.perform_later(name, email)
  end

  def chimp_unsubscribe
    UserUnsubscribeJob.perform_later(email)
  end

  def chimp_check
    if email_changed?
      chimp_subscribe
    end

    if locked_changed?
      if locked?
        chimp_unsubscribe
      else
        chimp_subscribe
      end
    end
  end

  def location
    has_location? && "#{city.titleize}, #{region.upcase}".html_safe
  end

  def has_location?
    [city, region].all?
  end

  def audits_sorted
    audits.sort_by(&:created_at).reverse!
  end

  private

    def generate_token(column)
      begin
        self[column] = SecureRandom.urlsafe_base64
      end while User.exists?(column => self[column])
    end

    def full_street_address
      [address1, address2, city, region, postal_code].compact.join(', ')
    end

    def format_cleanup
      region.upcase!
      email.downcase!
    end

    def unset_permissions_on_locked_user
      return if !locked?
      update_columns(admin: false,
                    is_foster: false,
                    edit_dogs: false,
                    edit_my_adopters: false,
                    edit_all_adopters: false,
                    edit_events: false,
                    available_to_foster: false,
                    complete_adopters: false,
                    add_dogs: false,
                    ban_adopters: false,
                    dl_resources: false,
                    is_photographer: false,
                    writes_newsletter: false,
                    is_transporter: false,
                    dl_locked_resources: false,
                    training_team: false,
                    foster_mentor: false,
                    public_relations: false,
                    fundraising: false,
                    boarding_buddies: false,
                    foster_friend: false,
                    medical_behavior_permission: false,
                    social_media_manager: false,
                    graphic_design: false,
                    is_clerical: false,
                    active: false)
    end

    def sanitize_postal_code
      return if postal_code.blank?
      self.postal_code = postal_code.delete(' ').upcase
    end

    def sanitize_region
      return if region.blank?
      self.region = region.delete(' ').upcase
    end

    def country_is_supported
      country = ISO3166::Country.find_country_by_alpha3(self.country)
      if country.nil?
        errors.add(:country, "is not recognized.")
        return
      end
      unless CountryService.supported_country? country
        errors.add(:country, "is not supported. Must be one of: #{CountryService.supported_country_names.join(', ')}.")
      end
    end
end