Files
nairobi/app/models/user.rb
rgarcia f670019325 Deal gracefully with recommendations of hidden proposals
We were seeing an exceptions in the home page when displaying
recommendations. This was due to trying to load tags of hidden proposals

With this commit we are skipping proposals that that have been hidden,
which will hopefully solve this exception
2018-07-10 17:47:28 +02:00

350 lines
10 KiB
Ruby

class User < ActiveRecord::Base
include Verification
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable,
:trackable, :validatable, :omniauthable, :async, :password_expirable, :secure_validatable,
authentication_keys: [:login]
acts_as_voter
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
include Graphqlable
has_one :administrator
has_one :moderator
has_one :valuator
has_one :manager
has_one :poll_officer, class_name: "Poll::Officer"
has_one :organization
has_one :lock
has_many :flags
has_many :identities, dependent: :destroy
has_many :debates, -> { with_hidden }, foreign_key: :author_id
has_many :proposals, -> { with_hidden }, foreign_key: :author_id
has_many :budget_investments, -> { with_hidden }, foreign_key: :author_id, class_name: 'Budget::Investment'
has_many :comments, -> { with_hidden }
has_many :spending_proposals, foreign_key: :author_id
has_many :failed_census_calls
has_many :notifications
has_many :direct_messages_sent, class_name: 'DirectMessage', foreign_key: :sender_id
has_many :direct_messages_received, class_name: 'DirectMessage', foreign_key: :receiver_id
has_many :legislation_answers, class_name: 'Legislation::Answer', dependent: :destroy, inverse_of: :user
has_many :follows
belongs_to :geozone
validates :username, presence: true, if: :username_required?
validates :username, uniqueness: { scope: :registering_with_oauth }, if: :username_required?
validates :document_number, uniqueness: { scope: :document_type }, allow_nil: true
validate :validate_username_length
validates :official_level, inclusion: {in: 0..5}
validates :terms_of_service, acceptance: { allow_nil: false }, on: :create
validates_associated :organization, message: false
accepts_nested_attributes_for :organization, update_only: true
attr_accessor :skip_password_validation
attr_accessor :use_redeemable_code
attr_accessor :login
scope :administrators, -> { joins(:administrator) }
scope :moderators, -> { joins(:moderator) }
scope :organizations, -> { joins(:organization) }
scope :officials, -> { where("official_level > 0") }
scope :newsletter, -> { where(newsletter: true) }
scope :for_render, -> { includes(:organization) }
scope :by_document, ->(document_type, document_number) do
where(document_type: document_type, document_number: document_number)
end
scope :email_digest, -> { where(email_digest: true) }
scope :active, -> { where(erased_at: nil) }
scope :erased, -> { where.not(erased_at: nil) }
scope :public_for_api, -> { all }
scope :by_comments, ->(query, topics_ids) { joins(:comments).where(query, topics_ids).uniq }
scope :by_authors, ->(author_ids) { where("users.id IN (?)", author_ids) }
scope :by_username_email_or_document_number, ->(search_string) do
string = "%#{search_string}%"
where("username ILIKE ? OR email ILIKE ? OR document_number ILIKE ?", string, string, string)
end
before_validation :clean_document_number
# Get the existing user by email if the provider gives us a verified email.
def self.first_or_initialize_for_oauth(auth)
oauth_email = auth.info.email
oauth_email_confirmed = oauth_email.present? && (auth.info.verified || auth.info.verified_email)
oauth_user = User.find_by(email: oauth_email) if oauth_email_confirmed
oauth_user || User.new(
username: auth.info.name || auth.uid,
email: oauth_email,
oauth_email: oauth_email,
password: Devise.friendly_token[0, 20],
terms_of_service: '1',
confirmed_at: oauth_email_confirmed ? DateTime.current : nil
)
end
def name
organization? ? organization.name : username
end
def debate_votes(debates)
voted = votes.for_debates(Array(debates).map(&:id))
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def proposal_votes(proposals)
voted = votes.for_proposals(Array(proposals).map(&:id))
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def legislation_proposal_votes(proposals)
voted = votes.for_legislation_proposals(proposals)
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def spending_proposal_votes(spending_proposals)
voted = votes.for_spending_proposals(spending_proposals)
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def budget_investment_votes(budget_investments)
voted = votes.for_budget_investments(budget_investments)
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def comment_flags(comments)
comment_flags = flags.for_comments(comments)
comment_flags.each_with_object({}){ |f, h| h[f.flaggable_id] = true }
end
def voted_in_group?(group)
votes.for_budget_investments(Budget::Investment.where(group: group)).exists?
end
def administrator?
administrator.present?
end
def moderator?
moderator.present?
end
def valuator?
valuator.present?
end
def manager?
manager.present?
end
def poll_officer?
poll_officer.present?
end
def organization?
organization.present?
end
def verified_organization?
organization && organization.verified?
end
def official?
official_level && official_level > 0
end
def add_official_position!(position, level)
return if position.blank? || level.blank?
update official_position: position, official_level: level.to_i
end
def remove_official_position!
update official_position: nil, official_level: 0
end
def has_official_email?
domain = Setting['email_domain_for_officials']
email.present? && ((email.end_with? "@#{domain}") || (email.end_with? ".#{domain}"))
end
def display_official_position_badge?
return true if official_level > 1
official_position_badge? && official_level == 1
end
def block
debates_ids = Debate.where(author_id: id).pluck(:id)
comments_ids = Comment.where(user_id: id).pluck(:id)
proposal_ids = Proposal.where(author_id: id).pluck(:id)
proposal_notification_ids = ProposalNotification.where(author_id: id).pluck(:id)
hide
Debate.hide_all debates_ids
Comment.hide_all comments_ids
Proposal.hide_all proposal_ids
ProposalNotification.hide_all proposal_notification_ids
end
def erase(erase_reason = nil)
update(
erased_at: Time.current,
erase_reason: erase_reason,
username: nil,
email: nil,
unconfirmed_email: nil,
phone_number: nil,
encrypted_password: "",
confirmation_token: nil,
reset_password_token: nil,
email_verification_token: nil,
confirmed_phone: nil,
unconfirmed_phone: nil
)
identities.destroy_all
end
def erased?
erased_at.present?
end
def take_votes_if_erased_document(document_number, document_type)
erased_user = User.erased.where(document_number: document_number)
.where(document_type: document_type).first
if erased_user.present?
take_votes_from(erased_user)
erased_user.update(document_number: nil, document_type: nil)
end
end
def take_votes_from(other_user)
return if other_user.blank?
Poll::Voter.where(user_id: other_user.id).update_all(user_id: id)
Budget::Ballot.where(user_id: other_user.id).update_all(user_id: id)
Vote.where("voter_id = ? AND voter_type = ?", other_user.id, "User").update_all(voter_id: id)
data_log = "id: #{other_user.id} - #{Time.current.strftime('%Y-%m-%d %H:%M:%S')}"
update(former_users_data_log: "#{former_users_data_log} | #{data_log}")
end
def locked?
Lock.find_or_create_by(user: self).locked?
end
def self.search(term)
term.present? ? where("email = ? OR username ILIKE ?", term, "%#{term}%") : none
end
def self.username_max_length
@@username_max_length ||= columns.find { |c| c.name == 'username' }.limit || 60
end
def self.minimum_required_age
(Setting['min_age_to_participate'] || 16).to_i
end
def show_welcome_screen?
verification = Setting["feature.user.skip_verification"].present? ? true : unverified?
sign_in_count == 1 && verification && !organization && !administrator?
end
def password_required?
return false if skip_password_validation
super
end
def username_required?
!organization? && !erased?
end
def email_required?
!erased? && unverified?
end
def locale
self[:locale] ||= I18n.default_locale.to_s
end
def confirmation_required?
super && !registering_with_oauth
end
def send_oauth_confirmation_instructions
if oauth_email != email
update(confirmed_at: nil)
send_confirmation_instructions
end
update(oauth_email: nil) if oauth_email.present?
end
def name_and_email
"#{name} (#{email})"
end
def age
Age.in_years(date_of_birth)
end
def save_requiring_finish_signup
begin
self.registering_with_oauth = true
save(validate: false)
# Devise puts unique constraints for the email the db, so we must detect & handle that
rescue ActiveRecord::RecordNotUnique
self.email = nil
save(validate: false)
end
true
end
def ability
@ability ||= Ability.new(self)
end
delegate :can?, :cannot?, to: :ability
def public_proposals
public_activity? ? proposals : User.none
end
def public_debates
public_activity? ? debates : User.none
end
def public_comments
public_activity? ? comments : User.none
end
# overwritting of Devise method to allow login using email OR username
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
login = conditions.delete(:login)
where(conditions.to_hash).where(["lower(email) = ?", login.downcase]).first ||
where(conditions.to_hash).where(["username = ?", login]).first
end
def interests
followables = follows.map(&:followable)
followables.compact.map { |followable| followable.tags.map(&:name) }.flatten.compact.uniq
end
private
def clean_document_number
return unless document_number.present?
self.document_number = document_number.gsub(/[^a-z0-9]+/i, "").upcase
end
def validate_username_length
validator = ActiveModel::Validations::LengthValidator.new(
attributes: :username,
maximum: User.username_max_length)
validator.validate(self)
end
end