<%= f.submit(class: "button success expanded", value: t("shared.save")) %>
diff --git a/app/components/admin/votation_types/fields_component.html.erb b/app/components/admin/votation_types/fields_component.html.erb
new file mode 100644
index 000000000..f55d74579
--- /dev/null
+++ b/app/components/admin/votation_types/fields_component.html.erb
@@ -0,0 +1,20 @@
+
+ <%= form.enum_select :vote_type, {}, class: "votation-type-vote-type" %>
+
+
+
+
+
+ <%= t("admin.polls.votation_type.unique_description") %>
+
+
+ <%= t("admin.polls.votation_type.multiple_description") %>
+
+
+
+
+
+
+ <%= form.number_field :max_votes, min: 2, max: 999, class: "votation-type-max-votes" %>
+
+
diff --git a/app/components/admin/votation_types/fields_component.rb b/app/components/admin/votation_types/fields_component.rb
new file mode 100644
index 000000000..6fe487397
--- /dev/null
+++ b/app/components/admin/votation_types/fields_component.rb
@@ -0,0 +1,7 @@
+class Admin::VotationTypes::FieldsComponent < ApplicationComponent
+ attr_reader :form
+
+ def initialize(form:)
+ @form = form
+ end
+end
diff --git a/app/components/polls/questions/answers_component.html.erb b/app/components/polls/questions/answers_component.html.erb
index 91be9ab15..c73247ec2 100644
--- a/app/components/polls/questions/answers_component.html.erb
+++ b/app/components/polls/questions/answers_component.html.erb
@@ -1,16 +1,22 @@
<% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %>
<% question_answers.each do |question_answer| %>
- <% if already_answered?(question_answer) && !voted_before_sign_in? %>
-
">
+ <% if already_answered?(question_answer) %>
+ <%= button_to question_answer_path(question, user_answer(question_answer)),
+ method: :delete,
+ remote: true,
+ title: t("poll_questions.show.voted", answer: question_answer.title),
+ class: "button answered",
+ "aria-pressed": true do %>
<%= question_answer.title %>
-
+ <% end %>
<% else %>
<%= button_to answer_question_path(question, answer: question_answer.title),
remote: true,
title: t("poll_questions.show.vote_answer", answer: question_answer.title),
- class: "button secondary hollow" do %>
+ class: "button secondary hollow",
+ "aria-pressed": false,
+ disabled: disable_answer?(question_answer) do %>
<%= question_answer.title %>
<% end %>
<% end %>
diff --git a/app/components/polls/questions/answers_component.rb b/app/components/polls/questions/answers_component.rb
index f51ad4e7f..0509d8fa7 100644
--- a/app/components/polls/questions/answers_component.rb
+++ b/app/components/polls/questions/answers_component.rb
@@ -7,19 +7,21 @@ class Polls::Questions::AnswersComponent < ApplicationComponent
end
def already_answered?(question_answer)
- user_answers.find_by(answer: question_answer.title).present?
- end
-
- def voted_before_sign_in?
- user_answers.any? do |vote|
- vote.updated_at < current_user.current_sign_in_at
- end
+ user_answer(question_answer).present?
end
def question_answers
question.question_answers
end
+ def user_answer(question_answer)
+ user_answers.find_by(answer: question_answer.title)
+ end
+
+ def disable_answer?(question_answer)
+ question.multiple? && user_answers.count == question.max_votes
+ end
+
private
def user_answers
diff --git a/app/components/polls/questions/question_component.html.erb b/app/components/polls/questions/question_component.html.erb
index 5fd7eeb8b..ac675a889 100644
--- a/app/components/polls/questions/question_component.html.erb
+++ b/app/components/polls/questions/question_component.html.erb
@@ -3,7 +3,20 @@
<%= question.title %>
+ <% if question.votation_type.present? %>
+
+ <%= t("poll_questions.description.#{question.vote_type}", maximum: question.max_votes) %>
+
+ <% end %>
+
<%= render Polls::Questions::AnswersComponent.new(question) %>
+
+ <% if question.answers_with_read_more? %>
+
+
<%= t("poll_questions.read_more_about") %>
+
<%= answers_read_more_links %>
+
+ <% end %>
diff --git a/app/components/polls/questions/question_component.rb b/app/components/polls/questions/question_component.rb
index d1a4edf27..09b8cacd9 100644
--- a/app/components/polls/questions/question_component.rb
+++ b/app/components/polls/questions/question_component.rb
@@ -4,4 +4,10 @@ class Polls::Questions::QuestionComponent < ApplicationComponent
def initialize(question:)
@question = question
end
+
+ def answers_read_more_links
+ safe_join(question.answers_with_read_more.map do |answer|
+ link_to answer.title, "#answer_#{answer.id}"
+ end, ", ")
+ end
end
diff --git a/app/components/polls/questions/read_more_answer_component.html.erb b/app/components/polls/questions/read_more_answer_component.html.erb
deleted file mode 100644
index becece5a7..000000000
--- a/app/components/polls/questions/read_more_answer_component.html.erb
+++ /dev/null
@@ -1,60 +0,0 @@
-
" id="answer_<%= answer.id %>">
-
<%= answer.title %>
-
- <% if answer.images.any? %>
- <%= render "polls/gallery", answer: answer %>
- <% end %>
-
- <% if answer.description.present? %>
-
-
- <%= wysiwyg(answer.description) %>
-
-
-
-
-
-
- <% end %>
-
- <% if answer.documents.present? %>
-
-
-
- <%= t("polls.show.documents") %>
-
-
- <% answer.documents.each do |document| %>
- <%= link_to document.title,
- document.attachment,
- target: "_blank",
- rel: "nofollow" %>
- <% end %>
-
- <% end %>
-
- <% if answer.videos.present? %>
-
-
-
- <%= t("polls.show.videos") %>
-
-
- <% answer.videos.each do |video| %>
- <%= link_to video.title,
- video.url,
- target: "_blank",
- rel: "nofollow" %>
- <% end %>
-
- <% end %>
-
diff --git a/app/components/polls/questions/read_more_answer_component.rb b/app/components/polls/questions/read_more_answer_component.rb
deleted file mode 100644
index edb34180c..000000000
--- a/app/components/polls/questions/read_more_answer_component.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class Polls::Questions::ReadMoreAnswerComponent < ApplicationComponent
- with_collection_parameter :answer
- attr_reader :answer
- delegate :wysiwyg, to: :helpers
-
- def initialize(answer:)
- @answer = answer
- end
-end
diff --git a/app/components/polls/questions/read_more_component.html.erb b/app/components/polls/questions/read_more_component.html.erb
new file mode 100644
index 000000000..27f87d7ed
--- /dev/null
+++ b/app/components/polls/questions/read_more_component.html.erb
@@ -0,0 +1,63 @@
+
<%= question.title %>
+<% question.answers_with_read_more.each do |answer| %>
+
" id="answer_<%= answer.id %>">
+
<%= answer.title %>
+
+
+ <% if answer.description.present? %>
+
+ <%= wysiwyg(answer.description) %>
+
+
+
+
+
+ <% end %>
+
+ <% if answer.images.any? %>
+ <%= render "polls/gallery", answer: answer %>
+ <% end %>
+
+ <% if answer.documents.present? %>
+
+
+
+ <%= t("polls.show.documents") %>
+
+
+ <% answer.documents.each do |document| %>
+ <%= link_to document.title,
+ document.attachment,
+ target: "_blank",
+ rel: "nofollow" %>
+ <% end %>
+
+ <% end %>
+
+ <% if answer.videos.present? %>
+
+
+
+ <%= t("polls.show.videos") %>
+
+
+ <% answer.videos.each do |video| %>
+ <%= link_to video.title,
+ video.url,
+ target: "_blank",
+ rel: "nofollow" %>
+ <% end %>
+
+ <% end %>
+
+
+<% end %>
diff --git a/app/components/polls/questions/read_more_component.rb b/app/components/polls/questions/read_more_component.rb
new file mode 100644
index 000000000..035cc2888
--- /dev/null
+++ b/app/components/polls/questions/read_more_component.rb
@@ -0,0 +1,13 @@
+class Polls::Questions::ReadMoreComponent < ApplicationComponent
+ with_collection_parameter :question
+ attr_reader :question
+ delegate :wysiwyg, to: :helpers
+
+ def initialize(question:)
+ @question = question
+ end
+
+ def render?
+ question.answers_with_read_more?
+ end
+end
diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb
index c0c8540f8..3981f652b 100644
--- a/app/controllers/admin/poll/questions_controller.rb
+++ b/app/controllers/admin/poll/questions_controller.rb
@@ -14,10 +14,10 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController
end
def new
- @polls = Poll.all
proposal = Proposal.find(params[:proposal_id]) if params[:proposal_id].present?
@question.copy_attributes_from_proposal(proposal)
@question.poll = @poll
+ @question.votation_type = VotationType.new
authorize! :create, @question
end
@@ -58,8 +58,7 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController
end
def allowed_params
- attributes = [:poll_id, :question, :proposal_id]
-
+ attributes = [:poll_id, :question, :proposal_id, votation_type_attributes: [:vote_type, :max_votes]]
[*attributes, translation_params(Poll::Question)]
end
diff --git a/app/controllers/polls/answers_controller.rb b/app/controllers/polls/answers_controller.rb
new file mode 100644
index 000000000..9e4988c05
--- /dev/null
+++ b/app/controllers/polls/answers_controller.rb
@@ -0,0 +1,19 @@
+class Polls::AnswersController < ApplicationController
+ load_and_authorize_resource :question, class: "::Poll::Question"
+ load_and_authorize_resource :answer, class: "::Poll::Answer",
+ through: :question,
+ through_association: :answers
+
+ def destroy
+ @answer.destroy_and_remove_voter_participation
+
+ respond_to do |format|
+ format.html do
+ redirect_to request.referer
+ end
+ format.js do
+ render "polls/questions/answers"
+ end
+ end
+ end
+end
diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb
index 3731aa2f9..a21acc9bc 100644
--- a/app/controllers/polls/questions_controller.rb
+++ b/app/controllers/polls/questions_controller.rb
@@ -5,9 +5,7 @@ class Polls::QuestionsController < ApplicationController
has_orders %w[most_voted newest oldest], only: :show
def answer
- answer = @question.answers.find_or_initialize_by(author: current_user)
-
- answer.answer = params[:answer]
+ answer = @question.find_or_initialize_user_answer(current_user, params[:answer])
answer.save_and_record_voter_participation
respond_to do |format|
@@ -15,7 +13,7 @@ class Polls::QuestionsController < ApplicationController
redirect_to request.referer
end
format.js do
- render :answer
+ render :answers
end
end
end
diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb
index af2dc563a..ecb8ffadb 100644
--- a/app/controllers/polls_controller.rb
+++ b/app/controllers/polls_controller.rb
@@ -19,10 +19,7 @@ class PollsController < ApplicationController
def show
@questions = @poll.questions.for_render.sort_for_list
- @poll_questions_answers = Poll::Question::Answer.where(question: @poll.questions)
- .with_content.order(:given_order)
- @commentable = @poll
- @comment_tree = CommentTree.new(@commentable, params[:page], @current_order)
+ @comment_tree = CommentTree.new(@poll, params[:page], @current_order)
end
def stats
diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb
index 2da9a057d..6c8d6cdd2 100644
--- a/app/models/abilities/common.rb
+++ b/app/models/abilities/common.rb
@@ -112,6 +112,9 @@ module Abilities
can :answer, Poll::Question do |question|
question.answerable_by?(user)
end
+ can :destroy, Poll::Answer do |answer|
+ answer.author == user && answer.question.answerable_by?(user)
+ end
end
can [:create, :show], ProposalNotification, proposal: { author_id: user.id }
diff --git a/app/models/concerns/questionable.rb b/app/models/concerns/questionable.rb
new file mode 100644
index 000000000..1d0606fc6
--- /dev/null
+++ b/app/models/concerns/questionable.rb
@@ -0,0 +1,30 @@
+module Questionable
+ extend ActiveSupport::Concern
+
+ included do
+ has_one :votation_type, as: :questionable, dependent: :destroy
+ accepts_nested_attributes_for :votation_type
+ delegate :max_votes, :multiple?, :vote_type, to: :votation_type, allow_nil: true
+ end
+
+ def unique?
+ votation_type.nil? || votation_type.unique?
+ 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
diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb
index 685830201..d9d86e9d6 100644
--- a/app/models/poll/answer.rb
+++ b/app/models/poll/answer.rb
@@ -7,6 +7,7 @@ class Poll::Answer < ApplicationRecord
validates :question, presence: true
validates :author, presence: true
validates :answer, presence: true
+ validate :max_votes
validates :answer, inclusion: { in: ->(a) { a.question.possible_answers }},
unless: ->(a) { a.question.blank? }
@@ -21,4 +22,27 @@ class Poll::Answer < ApplicationRecord
Poll::Voter.find_or_create_by!(user: author, poll: poll, origin: "web")
end
end
+
+ def destroy_and_remove_voter_participation
+ transaction do
+ destroy!
+
+ if author.poll_answers.where(question_id: poll.question_ids).none?
+ Poll::Voter.find_by(user: author, poll: poll, origin: "web").destroy!
+ end
+ end
+ end
+
+ private
+
+ def max_votes
+ return if !question || question&.unique? || persisted?
+
+ author.reload
+ author.lock!
+
+ if question.answers.by_author(author).count >= question.max_votes
+ errors.add(:answer, "Maximum number of votes per user exceeded")
+ end
+ end
end
diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb
index 6f489c8d8..e513d3298 100644
--- a/app/models/poll/question.rb
+++ b/app/models/poll/question.rb
@@ -1,6 +1,7 @@
class Poll::Question < ApplicationRecord
include Measurable
include Searchable
+ include Questionable
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
@@ -73,4 +74,12 @@ class Poll::Question < ApplicationRecord
def possible_answers
question_answers.joins(:translations).pluck("poll_question_answer_translations.title")
end
+
+ def answers_with_read_more?
+ answers_with_read_more.any?
+ end
+
+ def answers_with_read_more
+ question_answers.select(&:with_read_more?)
+ end
end
diff --git a/app/models/poll/question/answer.rb b/app/models/poll/question/answer.rb
index ea251c180..57aba5609 100644
--- a/app/models/poll/question/answer.rb
+++ b/app/models/poll/question/answer.rb
@@ -40,4 +40,8 @@ class Poll::Question::Answer < ApplicationRecord
def total_votes_percentage
question.answers_total_votes.zero? ? 0 : (total_votes * 100.0) / question.answers_total_votes
end
+
+ def with_read_more?
+ description.present? || images.any? || documents.any? || videos.any?
+ end
end
diff --git a/app/models/votation_type.rb b/app/models/votation_type.rb
new file mode 100644
index 000000000..00fca0ecf
--- /dev/null
+++ b/app/models/votation_type.rb
@@ -0,0 +1,17 @@
+class VotationType < ApplicationRecord
+ belongs_to :questionable, polymorphic: true
+
+ QUESTIONABLE_TYPES = %w[Poll::Question].freeze
+
+ enum vote_type: %w[unique multiple]
+
+ validates :questionable, presence: true
+ validates :questionable_type, inclusion: { in: ->(*) { QUESTIONABLE_TYPES }}
+ validates :max_votes, presence: true, if: :max_votes_required?
+
+ private
+
+ def max_votes_required?
+ multiple?
+ end
+end
diff --git a/app/views/admin/poll/questions/show.html.erb b/app/views/admin/poll/questions/show.html.erb
index ec4e9cecc..f6c8ef32b 100644
--- a/app/views/admin/poll/questions/show.html.erb
+++ b/app/views/admin/poll/questions/show.html.erb
@@ -28,6 +28,21 @@
<%= link_to @question.proposal.title, proposal_path(@question.proposal) %>
<% end %>
+
+ <% if @question.votation_type.present? %>
+
+ <%= t("admin.polls.votation_type.title") %>
+
+ <%= VotationType.human_attribute_name("vote_type.#{@question.vote_type}") %>
+
+ <% if @question.max_votes.present? %>
+
+ <%= VotationType.human_attribute_name("max_votes") %>
+
+ <%= @question.max_votes %>
+
+ <% end %>
+ <% end %>