Until now, we've stored the text of the answer somebody replied to. The idea was to handle the scenarios where the user voters for an option but then that option is deleted and restored, or the texts of the options are accidentally edited and so the option "Yes" is now "Now" and vice versa. However, since commit3a6e99cb8, options can no longer be edited once the poll starts, so there's no risk of the option changing once somebody has voted. This means we can now store the ID of the option that has been voted. That'll also help us deal with a bug introduced int673ec075e, since answers in different locales are not counted as the same answer. Note we aren't dealing with this bug right now. We're still keeping (and storing) the answer as well. There are two reasons for that. First, we might add an "open answer" type of questions in the future and use this column for it. Second, we've still got logic depending on the answer, and we need to be careful when changing it because there are existing installations where the answer is present but the option_id is not. Note that we're using `dependent: nullify`. The reasoning is that, since we're storing both the option_id and the answer text, we can still use the answer text when removing the option. In practice, this won't matter much, though, since we've got a validation rule that makes it impossible to destroy options once the poll has started. Also note we're still allowing duplicate records when the option is nil. We need to do that until we've removed every duplicate record in the database.
121 lines
3.2 KiB
Ruby
121 lines
3.2 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, 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
|