Merge pull request #6061 from consuldemocracy/poll_text_answers
Add support for essay poll questions
This commit is contained in:
@@ -101,7 +101,7 @@ module Abilities
|
||||
end
|
||||
can [:read, :order_options], Poll::Question::Option
|
||||
can [:create, :update, :destroy], Poll::Question::Option do |option|
|
||||
can?(:update, option.question)
|
||||
can?(:update, option.question) && option.question.accepts_options?
|
||||
end
|
||||
can :read, Poll::Question::Option::Video
|
||||
can [:create, :update, :destroy], Poll::Question::Option::Video do |video|
|
||||
|
||||
@@ -9,21 +9,9 @@ class Poll::Answer < ApplicationRecord
|
||||
validates :author, presence: true
|
||||
validates :answer, presence: true
|
||||
validates :option, uniqueness: { scope: :author_id }, allow_nil: true
|
||||
validate :max_votes
|
||||
|
||||
validates :answer, inclusion: { in: ->(poll_answer) { poll_answer.option.possible_answers }},
|
||||
if: ->(poll_answer) { poll_answer.option.present? }
|
||||
|
||||
scope :by_author, ->(author_id) { where(author_id: author_id) }
|
||||
scope :by_question, ->(question_id) { where(question_id: question_id) }
|
||||
|
||||
private
|
||||
|
||||
def max_votes
|
||||
return if !question || !author || persisted?
|
||||
|
||||
if question.answers.by_author(author).count >= question.max_votes
|
||||
errors.add(:answer, "Maximum number of votes per user exceeded")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,10 +27,11 @@ class Poll::Question < ApplicationRecord
|
||||
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
|
||||
delegate :multiple?, :open?, :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) }
|
||||
scope :for_physical_votes, -> { left_joins(:votation_type).merge(VotationType.accepts_options) }
|
||||
|
||||
def copy_attributes_from_proposal(proposal)
|
||||
if proposal.present?
|
||||
@@ -61,6 +62,10 @@ class Poll::Question < ApplicationRecord
|
||||
votation_type.nil? || votation_type.unique?
|
||||
end
|
||||
|
||||
def accepts_options?
|
||||
votation_type.nil? || votation_type.accepts_options?
|
||||
end
|
||||
|
||||
def max_votes
|
||||
if multiple?
|
||||
votation_type.max_votes
|
||||
@@ -69,23 +74,51 @@ class Poll::Question < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_initialize_user_answer(user, option_id)
|
||||
option = question_options.find(option_id)
|
||||
def find_or_initialize_user_answer(user, option_id: nil, answer_text: nil)
|
||||
answer = answers.find_or_initialize_by(find_by_attributes(user, option_id))
|
||||
|
||||
if accepts_options?
|
||||
option = question_options.find(option_id)
|
||||
answer.option = option
|
||||
answer.answer = option.title
|
||||
else
|
||||
answer.answer = answer_text
|
||||
end
|
||||
|
||||
answer = answers.find_or_initialize_by(find_by_attributes(user, option))
|
||||
answer.option = option
|
||||
answer.answer = option.title
|
||||
answer
|
||||
end
|
||||
|
||||
def open_ended_valid_answers_count
|
||||
answers.count
|
||||
end
|
||||
|
||||
def open_ended_blank_answers_count
|
||||
poll.voters.count - open_ended_valid_answers_count
|
||||
end
|
||||
|
||||
def open_ended_valid_percentage
|
||||
return 0.0 if open_ended_total_answers.zero?
|
||||
|
||||
(open_ended_valid_answers_count * 100.0) / open_ended_total_answers
|
||||
end
|
||||
|
||||
def open_ended_blank_percentage
|
||||
return 0.0 if open_ended_total_answers.zero?
|
||||
|
||||
(open_ended_blank_answers_count * 100.0) / open_ended_total_answers
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_by_attributes(user, option)
|
||||
case vote_type
|
||||
when "unique", nil
|
||||
def find_by_attributes(user, option_id)
|
||||
if multiple?
|
||||
{ author: user, option_id: option_id }
|
||||
else
|
||||
{ author: user }
|
||||
when "multiple"
|
||||
{ author: user, answer: option.title }
|
||||
end
|
||||
end
|
||||
|
||||
def open_ended_total_answers
|
||||
open_ended_valid_answers_count + open_ended_blank_answers_count
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,8 +63,17 @@ class Poll::WebVote
|
||||
def answers_for_question(question, question_params)
|
||||
return [] unless question_params
|
||||
|
||||
Array(question_params[:option_id]).map do |option_id|
|
||||
question.find_or_initialize_user_answer(user, option_id)
|
||||
if question.open?
|
||||
answer_text = question_params[:answer].to_s.strip
|
||||
if answer_text.present?
|
||||
[question.find_or_initialize_user_answer(user, answer_text: answer_text)]
|
||||
else
|
||||
[]
|
||||
end
|
||||
else
|
||||
Array(question_params[:option_id]).map do |option_id|
|
||||
question.find_or_initialize_user_answer(user, option_id: option_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
class VotationType < ApplicationRecord
|
||||
belongs_to :questionable, polymorphic: true
|
||||
|
||||
validate :cannot_be_open_ended_if_question_has_options
|
||||
|
||||
QUESTIONABLE_TYPES = %w[Poll::Question].freeze
|
||||
|
||||
enum :vote_type, { unique: 0, multiple: 1 }
|
||||
enum :vote_type, { unique: 0, multiple: 1, open: 2 }
|
||||
|
||||
validates :questionable, presence: true
|
||||
validates :questionable_type, inclusion: { in: ->(*) { QUESTIONABLE_TYPES }}
|
||||
validates :max_votes, presence: true, if: :max_votes_required?
|
||||
|
||||
scope :accepts_options, -> { where.not(vote_type: "open") }
|
||||
|
||||
def accepts_options?
|
||||
!open?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def max_votes_required?
|
||||
multiple?
|
||||
end
|
||||
|
||||
def cannot_be_open_ended_if_question_has_options
|
||||
if questionable&.question_options&.any? && !accepts_options?
|
||||
errors.add(:vote_type, :cannot_change_to_open_ended)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user