Files
grecia/app/models/poll/question.rb
Javi Martín a54d424aed Add missing validation rule to poll answers
We were checking we didn't have more votes than allowed in the case of
questions with multiple answers, but we weren't checking it in the case
of questions with a single answer. This made it possible to create more
than one answer to the same question. This could happen because the
method `find_or_initialize_user_answer` might initialize two answers in
different threads, due to a race condition.
2024-06-26 15:41:44 +02:00

118 lines
3.1 KiB
Ruby

class Poll::Question < ApplicationRecord
include Measurable
include Searchable
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
translates :title, touch: true
include Globalizable
belongs_to :poll
belongs_to :author, -> { with_hidden }, class_name: "User", inverse_of: :poll_questions
has_many :comments, as: :commentable, inverse_of: :commentable
has_many :answers, class_name: "Poll::Answer"
has_many :question_options, -> { order "given_order asc" },
class_name: "Poll::Question::Option",
inverse_of: :question,
dependent: :destroy
has_many :partial_results
has_one :votation_type, as: :questionable, dependent: :destroy
belongs_to :proposal
validates_translation :title, presence: true, length: { minimum: 4 }
validates :author, presence: true
validates :poll_id, presence: true, if: proc { |question| question.poll.nil? }
accepts_nested_attributes_for :question_options, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :votation_type
delegate :multiple?, :vote_type, to: :votation_type, allow_nil: true
scope :by_poll_id, ->(poll_id) { where(poll_id: poll_id) }
scope :sort_for_list, -> { order(Arel.sql("poll_questions.proposal_id IS NULL"), :created_at) }
scope :for_render, -> { includes(:author, :proposal) }
def self.search(params)
results = all
results = results.by_poll_id(params[:poll_id]) if params[:poll_id].present?
results = results.pg_search(params[:search]) if params[:search].present?
results
end
def searchable_values
{ title => "A",
proposal&.title => "A",
author.username => "C",
author_visible_name => "C" }
end
def copy_attributes_from_proposal(proposal)
if proposal.present?
self.author = proposal.author
self.author_visible_name = proposal.author.name
self.proposal_id = proposal.id
send(:"#{localized_attr_name_for(:title, Globalize.locale)}=", proposal.title)
end
end
delegate :answerable_by?, to: :poll
def self.answerable_by(user)
return none if user.nil? || user.unverified?
where(poll_id: Poll.answerable_by(user).pluck(:id))
end
def options_total_votes
question_options.reduce(0) { |total, question_option| total + question_option.total_votes }
end
def most_voted_option_id
question_options.max_by(&:total_votes)&.id
end
def possible_answers
question_options.joins(:translations).pluck(:title)
end
def options_with_read_more?
options_with_read_more.any?
end
def options_with_read_more
question_options.select(&:with_read_more?)
end
def unique?
votation_type.nil? || votation_type.unique?
end
def max_votes
if multiple?
votation_type.max_votes
else
1
end
end
def find_or_initialize_user_answer(user, title)
answer = answers.find_or_initialize_by(find_by_attributes(user, title))
answer.answer = title
answer
end
private
def find_by_attributes(user, title)
case vote_type
when "unique", nil
{ author: user }
when "multiple"
{ author: user, answer: title }
end
end
end