Files
nairobi/app/models/poll/question.rb
taitus a29eeaf2e2 Add option_id to partial results and unique index
Similar to what we did in PR "Avoid duplicate records in poll answers" 5539,
specifically in commit 503369166, we want to stop relying on the plain text
"answer" and start using "option_id" to avoid issues with counts across
translations and to add consistency to the poll_partial_results table.

Note that we also moved the `possible_answers` method from Poll::Question to
Poll::Question::Option, since the list of valid answers really comes from the
options of a question and not from the question itself. Tests were updated
to validate answers against the translations of the assigned option.

Additionally, we renamed lambda parameters in validations to improve clarity.
2025-09-26 15:05:34 +02:00

92 lines
2.5 KiB
Ruby

class Poll::Question < ApplicationRecord
include Measurable
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 :sort_for_list, -> { order(Arel.sql("poll_questions.proposal_id IS NULL"), :created_at) }
scope :for_render, -> { includes(:author, :proposal) }
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
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 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, option_id)
option = question_options.find(option_id)
answer = answers.find_or_initialize_by(find_by_attributes(user, option))
answer.option = option
answer.answer = option.title
answer
end
private
def find_by_attributes(user, option)
case vote_type
when "unique", nil
{ author: user }
when "multiple"
{ author: user, answer: option.title }
end
end
end