diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index e8c27165e..dda27c914 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -87,6 +87,7 @@ //= require cookies //= require columns_selector //= require budget_edit_associations.js.coffee +//= require votations var initialize_modules = function() { App.Answers.initialize(); @@ -140,6 +141,8 @@ var initialize_modules = function() { if ( $('#js-columns-selector').length ) App.ColumnsSelector.initialize(); App.BudgetEditAssociations.initialize(); + if ( $("#votation_type_enum_type").length ) + App.Votations.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/sortable.js.coffee b/app/assets/javascripts/sortable.js.coffee index 61124264e..ee98703f3 100644 --- a/app/assets/javascripts/sortable.js.coffee +++ b/app/assets/javascripts/sortable.js.coffee @@ -7,3 +7,11 @@ App.Sortable = url: $(".sortable").data("js-url"), data: { ordered_list: new_order }, type: "POST" + + $(".sortable-priotirized-votation").sortable + update: (event, ui) -> + new_order = $(this).sortable("toArray", { attribute: "data-answer-id" }) + $.ajax + url: $(this).data("js-url"), + data: { ordered_list: new_order }, + type: "POST" diff --git a/app/assets/javascripts/votations.js.coffee b/app/assets/javascripts/votations.js.coffee new file mode 100644 index 000000000..80f026c10 --- /dev/null +++ b/app/assets/javascripts/votations.js.coffee @@ -0,0 +1,45 @@ +App.Votations = + + checkMaxVotes: -> + if $("#votation_type_enum_type").val() == "0" + $(".js-max_votes").hide() + $("#max_votes").attr(disabled: true) + else + $(".js-max_votes").show() + $("#max_votes").attr(disabled: false) + + checkPrioritization: -> + if $("#votation_type_enum_type").val() == "2" + $(".js-prioritization_type").show() + $("#prioritization_type").attr(disabled: false) + else + $(".js-prioritization_type").hide() + $("#prioritization_type").attr(disabled: true) + + checkMaxGroups: -> + if $("#votation_type_enum_type").val() == "7" || $("#votation_type_enum_type").val() == "8" + $(".js-max_group_votes").show() + $("#max_groups_answers").attr(disabled: false) + else + $(".js-max_group_votes").hide() + $("#max_groups_answers").attr(disabled: true) + + setTraduction: (response) -> + console.log response + $(".js-description_text").text(response["traduction"]) + + updateChecks: () -> + App.Votations.checkMaxVotes() + App.Votations.checkPrioritization() + App.Votations.checkMaxGroups() + + initialize: -> + App.Votations.updateChecks() + $("#votation_type_enum_type").on + change: -> + App.Votations.updateChecks() + url = "/admin/get_options_traductions.json" + params = { enum_type: $("#votation_type_enum_type").val() } + $.get(url, params, (response) -> App.Votations.setTraduction response, "json") + +false diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 55ab8193c..d43db6130 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -786,6 +786,10 @@ code { font-weight: bold; } +.hidden { + display: none; +} + table { .callout { @@ -796,6 +800,15 @@ table { } } +.info-type { + background-color: #ccf5ff; + padding: 15px; +} + +.margin-description { + margin-top: rem-calc(20); +} + // 07. Legislation // -------------- diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 5a72a100b..f003c4632 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -1745,8 +1745,12 @@ background: #fafafa; border-bottom: 1px solid #eee; - .column:nth-child(odd) { - border-right: 2px solid $text; + .margin-bottom { + margin-bottom: 0; + } + + .orbit-bullets { + margin-bottom: 0; } .answer-divider { @@ -1756,14 +1760,35 @@ padding-bottom: $line-height; } + .answer-left-divider { + border-left: solid 1px $text; + padding-left: rem-calc(10); + } + + .margin-top { + margin-top: rem-calc(10); + } + + .margin-bottom { + margin-bottom: rem-calc(20); + } + .answer-description { - height: 100%; + max-height: rem-calc(1000); &.short { - height: rem-calc(300); + max-height: rem-calc(70); overflow: hidden; + max-width: rem-calc(700); } } + + .question-divider { + border-bottom: rgba(219, 219, 219, 0.62) solid 1px; + margin-bottom: 1rem; + padding: rem-calc(24); + } + } .orbit-bullets button { @@ -1991,6 +2016,69 @@ } } +.icon-like, +.icon-unlike { + background: #fff; + border: 2px solid $text-light; + border-radius: rem-calc(3); + color: $text-light; + display: inline-block; + font-size: rem-calc(30); + line-height: rem-calc(30); + padding: rem-calc(3) rem-calc(6); + position: relative; + + &:hover, + &:active { + color: #fff; + cursor: pointer; + opacity: 1 !important; + } +} + +.active-like { + color: #fff; + cursor: pointer; + opacity: 1 !important; + background: $like; + border: 2px solid $like; +} + +.active-unlike { + color: #fff; + cursor: pointer; + opacity: 1 !important; + background: $unlike; + border: 2px solid $unlike; +} + +.icon-like { + + &:hover, + &:active, + .picked { + background: $like; + border: 2px solid $like; + } +} + +.icon-unlike { + + &:hover, + &:active { + background: $unlike; + border: 2px solid $unlike; + } +} + +.vote-align { + float: right; +} + +.vote-divider { + border-bottom: 1px solid $text-light; +} + // 09. Polls results and stats // --------------------------- diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index 84a3e9e48..c1bfbcf35 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -17,6 +17,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController before_action :load_change_log, only: [:show] def index + load_tags respond_to do |format| format.html format.js diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb index 769a7998c..279f9de6f 100644 --- a/app/controllers/admin/poll/questions_controller.rb +++ b/app/controllers/admin/poll/questions_controller.rb @@ -22,6 +22,7 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController def create @question.author = @question.proposal.try(:author) || current_user + @question.votation_type = VotationType.build_by_type(@question, params[:votation_type]) if @question.save redirect_to admin_question_path(@question) @@ -53,11 +54,18 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController redirect_to admin_questions_path, notice: notice end + def get_options_traductions + render json: { + traduction: t("polls.index.descriptions.#{VotationType.enum_types.key params[:enum_type].to_i}") + } + end + private def question_params attributes = [:poll_id, :question, :proposal_id] - params.require(:poll_question).permit(*attributes, translation_params(Poll::Question)) + params.require(:poll_question).permit(*attributes, translation_params(Poll::Question), + :votation_type, :max_votes, :prioritization_type, :max_groups_answers) end def search_params diff --git a/app/controllers/polls/answers_controller.rb b/app/controllers/polls/answers_controller.rb new file mode 100644 index 000000000..49c5fcfc9 --- /dev/null +++ b/app/controllers/polls/answers_controller.rb @@ -0,0 +1,70 @@ +class Polls::AnswersController < ApplicationController + + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: "Poll::Question" + authorize_resource :answer, class: "Poll::Answer" + + def create + @question = Poll::Question.find_by(id: params[:id]) + if @question.votation_type.open? && !check_question_answer_exist + @question.question_answers.create( + title: params[:answer], + given_order: @question.question_answers.count + 1, + hidden: false + ) + flash.now[:notice] = t("dashboard.polls.index.succesfull") + else + flash.now[:alert] = "Unfortunately failed to sent" + end + load_for_answers + if @question.enum_type&.include?("answer_couples") + last_pair ||= generate_and_store_new_pair(@question) + @last_pair_question_answers = {@question.id => last_pair} + end + render "polls/questions/answer", format: :js + end + + def delete + @question = Poll::Question.find_by(id: params[:id]) + !@question.answers.find_by(author: current_user, answer: params[:answer]).destroy + @question.question_answers.each do |question_answer| + question_answer.set_most_voted + end + question_answers + load_for_answers + if @question.enum_type&.include?("answer_couples") + last_pair ||= generate_and_store_new_pair(@question) + @last_pair_question_answers = {@question.id => last_pair} + end + render "polls/questions/answer", format: :js + end + + private + + def check_question_answer_exist + exist = false + @question.question_answers.each do |question_answer| + break if exist + exist = true if question_answer.title == params[:answer] + end + exist + end + + def load_for_answers + @page = params[:page].present? ? params[:page] : 1 + question_answers + @answers_by_question_id = {@question.id => @question.answers + .by_author(current_user) + .order(:order) + .pluck(:answer)} + end + + def question_answers + if @question.is_positive_negative? + @answers = @question.question_answers.visibles.page(params[:page]) + else + @answers = @question.question_answers.visibles + end + end + +end diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb index eb054dd1e..b929e0fc5 100644 --- a/app/controllers/polls/questions_controller.rb +++ b/app/controllers/polls/questions_controller.rb @@ -3,18 +3,79 @@ class Polls::QuestionsController < ApplicationController load_and_authorize_resource :poll load_and_authorize_resource :question, class: "Poll::Question" - has_orders %w{most_voted newest oldest}, only: :show + has_orders %w[most_voted newest oldest], only: :show def answer - answer = @question.answers.find_or_initialize_by(author: current_user) - token = params[:token] - - answer.answer = params[:answer] - answer.touch if answer.persisted? - answer.save! - answer.record_voter_participation(token) - - @answers_by_question_id = { @question.id => params[:answer] } + answer = store_answer + vote_stored(answer, params[:answer], params[:token]) if answer.present? + load_for_answers + if @question.enum_type&.include?("answer_couples") + last_pair ||= generate_and_store_new_pair(@question) + @last_pair_question_answers = {@question.id => last_pair} + end end + def load_answers + load_for_answers + render action: "answer.js.erb" + end + + def prioritized_answers + unless params[:ordered_list].empty? + params[:ordered_list].each_with_index do |answer, i| + answer_obj = @question.votation_type.answer(current_user, + answer, + order: i + 1) + vote_stored(answer_obj, answer, params[:tooken]) if answer_obj.present? + end + @question.votation_type.update_priorized_values(current_user.id) + end + load_for_answers + render action: "answer.js.erb" + end + + private + + def load_for_answers + @page = params[:page].present? ? params[:page] : 1 + question_answers + @answers_by_question_id = {@question.id => @question.answers + .by_author(current_user) + .order(:order) + .pluck(:answer)} + end + + def vote_stored(answer, new_answer, token) + answer.answer = new_answer + answer.touch if answer.persisted? + answer.save! + answer.record_voter_participation(token) + @question.question_answers.visibles.where(question_id: @question).each do |question_answer| + question_answer.set_most_voted + end + end + + def store_answer + if @question.votation_type.nil? + answer = @question.answers.find_or_initialize_by(author: current_user) + else + answer = @question.votation_type.answer(current_user, + params[:answer], + positive: params[:positive]) + end + answer + end + + def generate_and_store_new_pair(question) + Poll::PairAnswer.generate_pair(question, current_user) + end + + def question_answers + if @question.is_positive_negative? + @answers = @question.question_answers.visibles.page(@page) + else + @answers = @question.question_answers.visibles + end + end + end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb index f322379ce..b95303384 100644 --- a/app/controllers/polls_controller.rb +++ b/app/controllers/polls_controller.rb @@ -20,13 +20,27 @@ class PollsController < ApplicationController def show @questions = @poll.questions.for_render.sort_for_list @token = poll_voter_token(@poll, current_user) - @poll_questions_answers = Poll::Question::Answer.where(question: @poll.questions) + @poll_questions_answers = Poll::Question::Answer.visibles + .where(question: @poll.questions) .where.not(description: "").order(:given_order) @answers_by_question_id = {} poll_answers = ::Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id)) - poll_answers.each do |answer| - @answers_by_question_id[answer.question_id] = answer.answer + + @last_pair_question_answers = {} + @questions.each do |question| + @answers_by_question_id[question.id] = question.answers.by_author(current_user).pluck(:answer) + + if question.enum_type&.include?("answer_couples") + last_pair = question.pair_answers.by_author(current_user).first + last_pair ||= generate_and_store_new_pair(question) + @last_pair_question_answers[question.id] = last_pair + end + + if question.enum_type&.include?("answer_set_closed") || + question.enum_type&.include?("answer_set_open") + votation_answer_sets(question) + end end @commentable = @poll @@ -42,6 +56,18 @@ class PollsController < ApplicationController private + def votation_answer_sets(question) + if question.votation_type.votation_set_answers.by_author(current_user).empty? + question.question_answers&.sample(question.max_groups_answers).each do |question_answer| + answer = VotationSetAnswer.new(answer: question_answer.title, + votation_type: question.votation_type, + author: current_user) + question.votation_type.votation_set_answers << answer + end + !question.save + end + end + def load_poll @poll = Poll.where(slug: params[:id]).first || Poll.where(id: params[:id]).first end @@ -50,4 +76,8 @@ class PollsController < ApplicationController @active_poll = ActivePoll.first end + def generate_and_store_new_pair(question) + Poll::PairAnswer.generate_pair(question, current_user) + end + end diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb index 2faf9b3f8..963367a4f 100644 --- a/app/helpers/polls_helper.rb +++ b/app/helpers/polls_helper.rb @@ -78,4 +78,8 @@ module PollsHelper def show_polls_description? @active_poll.present? && @current_filter == "current" end + + def stored_positive_negative_value(question, answer) + question.answers.find_by(author_id: current_user.id, answer: answer.title).positive + end end diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 1995a7a23..6fcee6cc0 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -78,8 +78,8 @@ module Abilities can [:search, :create, :index, :destroy], ::Poll::Officer can [:create, :destroy, :manage], ::Poll::BoothAssignment can [:create, :destroy], ::Poll::OfficerAssignment - can [:read, :create, :update], Poll::Question - can :destroy, Poll::Question # , comments_count: 0, votes_up: 0 + can [:read, :create, :update, :get_options_traductions], Poll::Question + can :destroy, Poll::Question can :manage, SiteCustomization::Page can :manage, SiteCustomization::Image diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index b106ebef5..d26a38680 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -104,12 +104,18 @@ module Abilities can :create, DirectMessage can :show, DirectMessage, sender_id: user.id - can :answer, Poll do |poll| + + can [:load_answers], Poll::Question + can [:answer], Poll do |poll| poll.answerable_by?(user) end - can :answer, Poll::Question do |question| + can [:answer, :prioritized_answers], Poll::Question do |question| question.answerable_by?(user) end + + can [:create, :delete], Poll::Answer do |answer| + answer.question.answerable_by?(user) + end end can [:create, :show], ProposalNotification, proposal: { author_id: user.id } diff --git a/app/models/poll/pair_answer.rb b/app/models/poll/pair_answer.rb new file mode 100644 index 000000000..2b314565c --- /dev/null +++ b/app/models/poll/pair_answer.rb @@ -0,0 +1,34 @@ +class Poll::PairAnswer < ApplicationRecord + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id" + belongs_to :answer_right, class_name: "Poll::Question::Answer", foreign_key: "answer_rigth_id" + belongs_to :answer_left, class_name: "Poll::Question::Answer", foreign_key: "answer_left_id" + + delegate :poll, :poll_id, to: :question + + validates :question, presence: true + validates :author, presence: true + validates :answer_left, presence: true + validates :answer_right, presence: true + + validates :answer_left, inclusion: { in: ->(a) { a.question.question_answers.visibles }}, + unless: ->(a) { a.question.blank? } + + validates :answer_right, inclusion: { in: ->(a) { a.question.question_answers.visibles }}, + unless: ->(a) { a.question.blank? } + + + scope :by_author, ->(author_id) { where(author_id: author_id) } + scope :by_question, ->(question_id) { where(question_id: question_id) } + + def self.generate_pair(question, user) + answers = question.question_answers.visibles.sample(2) + question.pair_answers.by_author(user).map(&:destroy) + question.pair_answers.create(author: user, answer_left: answers[0], answer_right: answers[1]) + end + + def answers + [answer_left, answer_right].compact + end +end diff --git a/app/models/poll/partial_result.rb b/app/models/poll/partial_result.rb index 7c4a6657e..036e80b75 100644 --- a/app/models/poll/partial_result.rb +++ b/app/models/poll/partial_result.rb @@ -11,6 +11,7 @@ class Poll::PartialResult < ApplicationRecord validates :author, presence: true validates :answer, presence: true validates :answer, inclusion: { in: ->(a) { a.question.question_answers + .visibles .joins(:translations) .pluck("poll_question_answer_translations.title") }}, unless: ->(a) { a.question.blank? } diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb index bcdfe68a4..41f31c867 100644 --- a/app/models/poll/question.rb +++ b/app/models/poll/question.rb @@ -15,14 +15,22 @@ class Poll::Question < ApplicationRecord has_many :answers, class_name: "Poll::Answer" has_many :question_answers, -> { order "given_order asc" }, class_name: "Poll::Question::Answer", dependent: :destroy has_many :partial_results + has_many :pair_answers, class_name: "Poll::PairAnswer" + has_one :votation_type, as: :questionable belongs_to :proposal + attr_accessor :enum_type, :max_votes, :prioritization_type + validates_translation :title, presence: true, length: { minimum: 4 } validates :author, presence: true validates :poll_id, presence: true, if: Proc.new { |question| question.poll.nil? } + validates_associated :votation_type accepts_nested_attributes_for :question_answers, reject_if: :all_blank, allow_destroy: true + delegate :enum_type, :max_votes, :prioritization_type, :max_groups_answers, + to: :votation_type, allow_nil: true + scope :by_poll_id, ->(poll_id) { where(poll_id: poll_id) } scope :sort_for_list, -> { order("poll_questions.proposal_id IS NULL", :created_at)} @@ -59,10 +67,24 @@ class Poll::Question < ApplicationRecord end def answers_total_votes - question_answers.inject(0) { |total, question_answer| total + question_answer.total_votes } + question_answers.visibles.inject(0) { |total, question_answer| total + question_answer.total_votes } end def most_voted_answer_id question_answers.max_by { |answer| answer.total_votes }.id end + + def answers_with_read_more? + question_answers.visibles.any? do |answer| answer.description.present? || answer.images.any? || + answer.documents.present? || answer.videos.present? + end + end + + def user_can_vote(user) + max_votes.nil? || max_votes > answers.where(author: user).count + end + + def is_positive_negative? + votation_type.present? && enum_type == "positive_negative_open" + end end diff --git a/app/models/poll/question/answer.rb b/app/models/poll/question/answer.rb index aab7acb59..c9856575e 100644 --- a/app/models/poll/question/answer.rb +++ b/app/models/poll/question/answer.rb @@ -1,6 +1,7 @@ class Poll::Question::Answer < ApplicationRecord include Galleryable include Documentable + paginates_per 10 translates :title, touch: true translates :description, touch: true @@ -14,6 +15,10 @@ class Poll::Question::Answer < ApplicationRecord validates_translation :title, presence: true validates :given_order, presence: true, uniqueness: { scope: :question_id } + scope :by_author, -> (author) { where(author: author) } + + scope :visibles, -> { where(hidden: false) } + def description self[:description].try :html_safe end @@ -29,11 +34,72 @@ class Poll::Question::Answer < ApplicationRecord end def total_votes - Poll::Answer.where(question_id: question, answer: title).count + - ::Poll::PartialResult.where(question: question).where(answer: title).sum(:amount) + if !question.votation_type.present? + Poll::Answer.where(question_id: question, answer: title).count + + ::Poll::PartialResult.where(question: question).where(answer: title).sum(:amount) + else + case question.votation_type.enum_type + when "positive_negative_open" + total_votes_positive_negative + when "prioritized" + total_votes_prioritized + when "unique" + Poll::Answer.where(question_id: question, answer: title).count + + ::Poll::PartialResult.where(question: question).where(answer: title).sum(:amount) + else + Poll::Answer.where(question_id: question, answer: title).count + end + end + end + + def total_votes_positive_negative + count_positive_negative(self, true) - count_positive_negative(self, false) + end + + def total_votes_prioritized + Poll::Answer.where(question_id: question, answer: title).sum(:value) + end + + def most_voted? + most_voted end def total_votes_percentage question.answers_total_votes.zero? ? 0 : (total_votes * 100.0) / question.answers_total_votes end + + def set_most_voted + if question.enum_type.nil? + for_only_votes + else + case question.enum_type + when "positive_negative_open" + answers = question.question_answers.visibles + .map { |a| count_positive_negative(a, true) - count_positive_negative(a, false) } + is_most_voted = answers.none? {|a| a > total_votes_positive_negative} + update(most_voted: is_most_voted) + when "prioritized" + answers = question.question_answers.visibles + .map { |a| Poll::Answer.where(question_id: a.question, answer: a.title).sum(:value) } + is_most_voted = answers.none? {|a| a > total_votes_prioritized} + update(most_voted: is_most_voted) + else + for_only_votes + end + end + end + + private + + def count_positive_negative(answer, value) + Poll::Answer.where(question_id: answer.question, answer: answer.title, positive: value).count + end + + def for_only_votes + answers = question.question_answers.visibles + .map {|a| Poll::Answer.where(question_id: a.question, answer: a.title).count} + is_most_voted = answers.none? {|a| a > total_votes} + update(most_voted: is_most_voted) + end + end diff --git a/app/models/user.rb b/app/models/user.rb index fd2adecc2..2f787f34d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -35,6 +35,7 @@ class User < ApplicationRecord has_many :follows has_many :budget_rol_assignments has_many :budgets, through: :budget_rol_assignments + has_many :votation_set_answers belongs_to :geozone validates :username, presence: true, if: :username_required? diff --git a/app/models/votation_set_answer.rb b/app/models/votation_set_answer.rb new file mode 100644 index 000000000..bcce7df69 --- /dev/null +++ b/app/models/votation_set_answer.rb @@ -0,0 +1,6 @@ +class VotationSetAnswer < ApplicationRecord + belongs_to :votation_type + belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id" + + scope :by_author, -> (author) { where(author: author) } +end diff --git a/app/models/votation_type.rb b/app/models/votation_type.rb new file mode 100644 index 000000000..c84418401 --- /dev/null +++ b/app/models/votation_type.rb @@ -0,0 +1,202 @@ +class VotationType < ApplicationRecord + belongs_to :questionable, polymorphic: true + has_many :votation_set_answers + + QUESTIONABLE_TYPES = %w[Poll::Question].freeze + + ENUM_TYPES_PROPS = { + unique: { enum_type: 0, open_answer: false, prioritized: false }, + multiple: { enum_type: 1, open_answer: false, prioritized: false, + variables: [:max_votes] }, + prioritized: { enum_type: 2, open_answer: false, prioritized: true, + variables: [:max_votes, :prioritization_type] }, + positive_open: { enum_type: 3, open_answer: true, prioritized: false, + variables: [:max_votes] }, + positive_negative_open: { enum_type: 4, open_answer: true, prioritized: false, + variables: [:max_votes] }, + answer_couples_open: { enum_type: 5, open_answer: true, prioritized: false, + variables: [:max_votes, :display_skip_question] }, + answer_couples_closed: { enum_type: 6, open_answer: false, prioritized: false, + variables: [:max_votes, :display_skip_question] }, + answer_set_open: { enum_type: 7, open_answer: true, prioritized: false, + variables: [:max_votes, :max_groups_answers] }, + answer_set_closed: { enum_type: 8, open_answer: false, prioritized: false, + variables: [:max_votes, :max_groups_answers] }, + }.freeze + + enum enum_type: ENUM_TYPES_PROPS.map{ |k,v| [k, v[:enum_type]] }.to_h.freeze + + enum prioritization_type: {borda: 1, dowdall: 2}.freeze + + validates :questionable, presence: true + validates :questionable_type, inclusion: {in: QUESTIONABLE_TYPES} + validates_presence_of :max_votes, allow_blank: false, + if: :max_votes_required? + validates_presence_of :max_groups_answers, allow_blank: false, + if: :max_groups_answers_required? + validates_presence_of :prioritization_type, allow_blank: false, + if: :prioritization_type_required? + + after_create :add_skip_question_answer, if: :display_skip_question? + + attr_accessor :display_skip_question + + def open? + open_answer + end + + def prioritized? + prioritized + end + + def answer (user, answer, options = {}) + result = nil + votes = questionable.answers + + if votable_question?(answer) + case enum_type + when "unique" + result = votes.find_or_initialize_by(author: user) + + when "multiple", "positive_open" + if check_max_votes(user, votes) + result = votes.find_or_initialize_by(author: user, answer: answer) + end + + when "prioritized" + result = votes.find_by(author: user, answer: answer) + if result.nil? + if check_max_votes(user, votes) + if votes.by_author(user.id).empty? + order = 1 + else + order = votes.by_author(user.id).order(:order).last.order + 1 + end + result = votes.find_or_initialize_by(author: user, + answer: answer, + order: order) + end + else + !result.update(order: options[:order]) + end + + when "positive_negative_open" + result = votes.by_author(user.id).find_by(answer: answer) + if result.nil? + if check_max_votes(user, votes) + result = votes.create(author: user, + answer: answer, + positive: options[:positive]) + end + else + !result.update(positive: options[:positive]) + end + + when "answer_couples_closed", "answer_couples_open" + if check_max_votes(user, votes) + result = votes.create( + answer: answer, + author: user, + positive: true, + order: votes&.by_author(user.id).count + 1 + ) + end + Poll::PairAnswer.generate_pair(questionable, user) + + when "answer_set_open", "answer_set_closed" + if check_max_votes(user, votes) && answer_in_set?(answer, user) + result = votes&.find_or_initialize_by(author: user, answer: answer) + end + end + end + + + result + end + + def create_question_answer(answer, hidden=false) + return if questionable.question_answers.where(title: answer).any? + + questionable.question_answers + .create( + title: answer, + given_order: questionable.question_answers.maximum(:given_order).to_i + 1, + hidden: hidden + ) + true + end + + def votable_question?(answer) + questionable.question_answers.where(title: answer).present? + end + + def self.build_by_type(questionable, params) + attributes = {questionable: questionable} + enum_type = self.enum_types.key(params[:enum_type].to_i) + enum_type_props = enum_properties(enum_type) + attributes.merge!(enum_type_props.except(:variables)) + enum_type_props[:variables]&.each do |property| + attributes[property] = params[property] + end + attributes[:prioritization_type] = attributes[:prioritization_type]&.to_i + new(attributes) + end + + def self.create_by_type(questionable, params) + votation_type = build_by_type(questionable, params) + votation_type.save + end + + def update_priorized_values(user) + case prioritization_type + when "borda" + questionable.answers.by_author(user).order(:order).each_with_index do |answer, i| + value = max_votes - i + !answer.update(value: value) + end + when "dowdall" + questionable.answers.by_author(user).order(:order).each_with_index do |answer, i| + value = 60/(i + 1) + !answer.update(value: value) + end + end + end + + private + + def answer_in_set?(answer, user) + votation_set_answers&.by_author(user)&.pluck(:answer).include?(answer) + end + + def check_max_votes(user, votes) + max_votes > votes&.by_author(user.id).count + end + + def self.enum_properties(enum_type) + ENUM_TYPES_PROPS[enum_type&.to_sym] || ENUM_TYPES_PROPS[:unique] + end + + def self.enum_properties_variables(enum_type) + enum_properties(enum_type)&.dig(:variables) + end + + def max_votes_required? + VotationType.enum_properties_variables(self.enum_type)&.include?(:max_votes) + end + + def max_groups_answers_required? + VotationType.enum_properties_variables(self.enum_type)&.include?(:max_groups_answers) + end + + def prioritization_type_required? + VotationType.enum_properties_variables(self.enum_type)&.include?(:prioritization_type) + end + + def display_skip_question? + VotationType.enum_properties_variables(self.enum_type)&.include?(:display_skip_question) + end + + def add_skip_question_answer + create_question_answer("I can't decided", true) + end +end diff --git a/app/views/admin/poll/questions/_form.html.erb b/app/views/admin/poll/questions/_form.html.erb index f0ca89b0d..c7aadadf8 100644 --- a/app/views/admin/poll/questions/_form.html.erb +++ b/app/views/admin/poll/questions/_form.html.erb @@ -25,6 +25,38 @@ <%= translations_form.text_field :title %> <% end %> + <% if !@question.persisted? %> + <%= fields_for :votation_type do |votation_f| %> +
+ <%= votation_f.select :enum_type, + options_for_select(VotationType.enum_types.map {|k, v| [t(k, scope: :enum_type), v]}, + params.dig(:votation_type, :enum_type)), default: 0, + disabled: @question.persisted?, label: t("enum_type.title") %> +
+ +
+ + <%= t("polls.index.descriptions.unique") %> + + + + +
+ <% end %> + <% end %> +
<%= f.submit(class: "button success expanded", value: t("shared.save")) %>
diff --git a/app/views/admin/poll/questions/show.html.erb b/app/views/admin/poll/questions/show.html.erb index 88daffcc6..1055d3b27 100644 --- a/app/views/admin/poll/questions/show.html.erb +++ b/app/views/admin/poll/questions/show.html.erb @@ -26,6 +26,35 @@ <%= link_to @question.proposal.title, proposal_path(@question.proposal) %>

<% end %> + + <% unless @question.votation_type.nil? %> +

+ <%= t("question.votation_type") %> +
+ <%= t("enum_type.#{@question.votation_type.enum_type}") %> +

+ <% if !@question.votation_type.max_votes.nil? %> +

+ <%= t("question.max_votes") %> +
+ <%= @question.votation_type.max_votes %> +

+ <% end %> + <% if !@question.votation_type.prioritization_type.nil? %> +

+ <%= t("prioritization_type.title") %> +
+ <%= t("prioritization_type.#{@question.votation_type.prioritization_type}") %> +

+ <% end %> + <% if !@question.votation_type.max_groups_answers.nil? %> +

+ <%= t("question.max_group_answers") %> +
+ <%= @question.votation_type.max_groups_answers %> +

+ <% end %> + <% end %> diff --git a/app/views/admin/poll/results/_votation_types_results.html.erb b/app/views/admin/poll/results/_votation_types_results.html.erb new file mode 100644 index 000000000..bd5f3eeaf --- /dev/null +++ b/app/views/admin/poll/results/_votation_types_results.html.erb @@ -0,0 +1,40 @@ +
+ +
+
+

<%= t("polls.show.results.title") %>

+ +
+ +
+ <%- @poll.questions.each do |question| %> +

<%= question.title %>

+ + + <%- question.question_answers.visibles.each do |answer| %> + > + + + + <% end %> + +
+ <% if answer.most_voted %> + <%= t("polls.show.results.most_voted_answer") %> + <% end %> + <%= answer.title %> + > + <%= answer.total_votes %> + <% unless question.enum_type == "positive_negative_open" %> + (<%= answer.total_votes_percentage.round(2) %>%) + <% end %> +
+ <% end %> + +
+
+
diff --git a/app/views/admin/poll/results/index.html.erb b/app/views/admin/poll/results/index.html.erb index eaf6ad745..1358eaebb 100644 --- a/app/views/admin/poll/results/index.html.erb +++ b/app/views/admin/poll/results/index.html.erb @@ -11,10 +11,14 @@ <% end %> - <% if @partial_results.present? %> - <%= render "recount", resource: @poll %> - <%= render "result" %> - <%= render "results_by_booth" %> + <% if @poll.questions.any? { |question| question.votation_type.present? } %> + <%= render "votation_types_results" %> + <% else %> + <% if @partial_results.present? %> + <%= render "recount", resource: @poll %> + <%= render "result" %> + <%= render "results_by_booth" %> + <% end %> <% end %> <% if @poll.voters.any? %> diff --git a/app/views/polls/questions/_answers.html.erb b/app/views/polls/questions/_answers.html.erb index 206896b04..bfba35b91 100644 --- a/app/views/polls/questions/_answers.html.erb +++ b/app/views/polls/questions/_answers.html.erb @@ -1,33 +1,22 @@ -
- <% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %> - <% question.question_answers.each do |answer| %> - <% if @answers_by_question_id[question.id] == answer.title && - (!voted_before_sign_in(question) || - question.poll.voted_in_booth?(current_user)) %> - "> - <%= answer.title %> - - <% else %> - <%= link_to answer.title, - answer_question_path(question, answer: answer.title, token: token), - method: :post, - remote: true, - title: t("poll_questions.show.vote_answer", answer: answer.title), - class: "button secondary hollow js-question-answer" %> - <% end %> - <% end %> - <% elsif !user_signed_in? %> - <% question.question_answers.order(id: :desc).each do |answer| %> - <%= link_to answer.title, new_user_session_path, class: "button secondary hollow" %> - <% end %> - <% elsif !current_user.level_two_or_three_verified? %> - <% question.question_answers.order(id: :desc).each do |answer| %> - <%= link_to answer.title, verification_path, class: "button secondary hollow" %> - <% end %> +<% if question.votation_type.nil? %> + <%= render "polls/questions/answers_unique", question: question, answers: answers, token: token %> +<% else %> + <% case question.votation_type.enum_type %> + <% when "unique" %> + <%= render "polls/questions/answers_unique", question: question, answers: answers, token: token %> + <% when "multiple", "positive_open" %> + <%= render "polls/questions/answers_multiple", question: question, answers: answers, token: token %> + <% when "positive_negative_open" %> + <%= render "polls/questions/answers_positive_negative", question: question, answers: answers, token: token, page: page %> + <% when "answer_couples_closed" %> + <%= render "polls/questions/answers_couples", answers_open: false, question: question, token: token %> + <% when "answer_couples_open" %> + <%= render "polls/questions/answers_couples", answers_open: true, question: question, token: token %> + <% when "answer_set_closed", "answer_set_open" %> + <%= render "polls/questions/answers_set", question: question, token: token, answers: question.votation_type.votation_set_answers.by_author(current_user) %> + <% when "prioritized" %> + <%= render "polls/questions/answers_prioritized", answers_open: false, question: question, answers: answers, token: token %> <% else %> - <% question.question_answers.order(id: :desc).each do |answer| %> - <%= answer.title %> - <% end %> + <%= render "polls/questions/answers_unique", question: question, answers: answers, token: token %> <% end %> -
+<% end %> diff --git a/app/views/polls/questions/_answers_couples.html.erb b/app/views/polls/questions/_answers_couples.html.erb new file mode 100644 index 000000000..cab665ed8 --- /dev/null +++ b/app/views/polls/questions/_answers_couples.html.erb @@ -0,0 +1,39 @@ +
+ <% if can?(:answer, question) && question.user_can_vote(current_user) %> +
+ <% @last_pair_question_answers.dig(question.id)&.answers&.each do |answer| %> +
+ <%= link_to answer.title, + answer_question_path(question, answer: answer.title, token: token), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer.title), + class: "button secondary hollow js-question-answer" %> +
+ <% end %> +
+
+ <%= link_to "I can't decide", + answer_question_path(question, answer: "I can't decided", token: token), + method: :post, + remote: true, + title: "I can't decide", + class: "button secondary hollow js-question-answer" %> +
+ + <% if answers_open %> + <%= render "/polls/questions/new_answer", question: question, token: token %> + <% end %> + + <% elsif !user_signed_in? %> + <% question.question_answers.visibles.order(id: :desc).each do |answer| %> + <%= link_to answer.title, new_user_session_path, class: "button secondary hollow" %> + <% end %> + <% elsif !current_user.level_two_or_three_verified? %> + <% question.question_answers.visibles.order(id: :desc).each do |answer| %> + <%= link_to answer.title, verification_path, class: "button secondary hollow" %> + <% end %> + <% else %> + <%= t('polls.index.max_votes_reached') %> + <% end %> +
diff --git a/app/views/polls/questions/_answers_multiple.html.erb b/app/views/polls/questions/_answers_multiple.html.erb new file mode 100644 index 000000000..3770b0e3c --- /dev/null +++ b/app/views/polls/questions/_answers_multiple.html.erb @@ -0,0 +1,36 @@ +
+ <% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %> + <% answers&.each do |answer| %> + <% if @answers_by_question_id[question.id].include?(answer.title) %> + <%= link_to answer.title, + answer_question_path(question, answer: answer.title, token: token), + method: :delete, + remote: true, + title: t("poll_questions.show.voted", answer: answer.title), + class: "button answered expand" %> + <% else %> + <%= link_to answer.title, + answer_question_path(question, answer: answer.title, token: token), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer.title), + class: "button secondary hollow js-question-answer" %> + <% end %> + <% end %> + <% if question.enum_type == "positive_open" %> + <%= render "/polls/questions/new_answer", question: question, token: token %> + <% end %> + <% elsif !user_signed_in? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, new_user_session_path, class: "button secondary hollow" %> + <% end %> + <% elsif !current_user.level_two_or_three_verified? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, verification_path, class: "button secondary hollow" %> + <% end %> + <% else %> + <% answers.order(id: :desc).each do |answer| %> + <%= answer.title %> + <% end %> + <% end %> +
diff --git a/app/views/polls/questions/_answers_positive_negative.html.erb b/app/views/polls/questions/_answers_positive_negative.html.erb new file mode 100644 index 000000000..22959931c --- /dev/null +++ b/app/views/polls/questions/_answers_positive_negative.html.erb @@ -0,0 +1,55 @@ +
+ <% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %> + + <% answers&.each do |answer| %> + + + + + <% end %> +
+ <%= answer.title %> + +
+ <% if @answers_by_question_id[question.id].include?(answer.title) && + stored_positive_negative_value(question, answer) %> + <%= render "polls/questions/like_dislike", question: question, + answer: answer, method: "delete", positive: true, + token: token, active: true, page: page %> + <% else %> + <%= render "polls/questions/like_dislike", question: question, + answer: answer, method: "post", positive: true, + token: token, active: false, page: page %> + <% end %> +
+
+ <% if @answers_by_question_id[question.id].include?(answer.title) && + !stored_positive_negative_value(question, answer) %> + <%= render "polls/questions/like_dislike", question: question, + answer: answer, method: "delete", positive: false, + token: token, active: true, page: page %> + <% else %> + <%= render "polls/questions/like_dislike", question: question, + answer: answer, method: "post", positive: false, + token: token, active: false, page: page %> + <% end %> +
+
+ <%= paginate answers, params: {controller: "polls/questions", action: :load_answers, id: question}, remote: true %> + + <%= render "/polls/questions/new_answer", question: question, token: token %> + + <% elsif !user_signed_in? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, new_user_session_path, class: "button secondary hollow" %> + <% end %> + <% elsif !current_user.level_two_or_three_verified? %> + <% answers.visibles.order(id: :desc).each do |answer| %> + <%= link_to answer.title, verification_path, class: "button secondary hollow" %> + <% end %> + <% else %> + <% answers.order(id: :desc).each do |answer| %> + <%= answer.title %> + <% end %> + <% end %> +
diff --git a/app/views/polls/questions/_answers_prioritized.html.erb b/app/views/polls/questions/_answers_prioritized.html.erb new file mode 100644 index 000000000..075008550 --- /dev/null +++ b/app/views/polls/questions/_answers_prioritized.html.erb @@ -0,0 +1,44 @@ +
+ <% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %> +
    + <% @answers_by_question_id[question.id].each do |answer| %> +
  1. + "> + <%= answer %> + +
  2. + <% end %> +
+ + <% answers&.each do |answer| %> + <% if @answers_by_question_id[question.id].include?(answer.title) %> + <%= link_to answer.title, + answer_question_path(question, answer: answer.title, token: token), + method: :delete, + remote: true, + title: t("poll_questions.show.voted", answer: answer.title), + class: "button answered expand" %> + <% else %> + <%= link_to answer.title, + answer_question_path(question, answer: answer.title, token: token), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer.title), + class: "button secondary hollow js-question-answer" %> + <% end %> + <% end %> + <% elsif !user_signed_in? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, new_user_session_path, class: "button secondary hollow" %> + <% end %> + <% elsif !current_user.level_two_or_three_verified? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, verification_path, class: "button secondary hollow" %> + <% end %> + <% else %> + <% answers.order(id: :desc).each do |answer| %> + <%= answer.title %> + <% end %> + <% end %> +
diff --git a/app/views/polls/questions/_answers_set.html.erb b/app/views/polls/questions/_answers_set.html.erb new file mode 100644 index 000000000..f8870c0d4 --- /dev/null +++ b/app/views/polls/questions/_answers_set.html.erb @@ -0,0 +1,37 @@ +
+ <% if can?(:answer, question) %> + <% answers.each do |answer| %> + <% if @answers_by_question_id[question.id].include?(answer.answer) %> + <%= link_to answer.answer, + answer_question_path(question, answer: answer.answer, token: token), + method: :delete, + remote: true, + title: t("poll_questions.show.voted", answer: answer.answer), + class: "button answered" %> + <% else %> + <%= link_to answer.answer, + answer_question_path(question, answer: answer.answer, token: token), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer.answer), + class: "button secondary hollow js-question-answer" %> + <% end %> + <% end %> + <% if question.enum_type == "answer_set_open" %> + <%= render "/polls/questions/new_answer", question: question, token: token %> + <%= flash[:notice] %> + <% end %> + <% elsif !user_signed_in? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.answer, new_user_session_path, class: "button secondary hollow" %> + <% end %> + <% elsif !current_user.level_two_or_three_verified? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.answer, verification_path, class: "button secondary hollow" %> + <% end %> + <% else %> + <% answers.order(id: :desc).each do |answer| %> + <%= answer.answer %> + <% end %> + <% end %> +
diff --git a/app/views/polls/questions/_answers_unique.html.erb b/app/views/polls/questions/_answers_unique.html.erb new file mode 100644 index 000000000..8029624ee --- /dev/null +++ b/app/views/polls/questions/_answers_unique.html.erb @@ -0,0 +1,33 @@ +
+ <% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %> + <% answers&.each do |answer| %> + <% if @answers_by_question_id[question.id].include?(answer.title) && + (!voted_before_sign_in(question) || + question.poll.voted_in_booth?(current_user)) %> + "> + <%= answer.title %> + + <% else %> + <%= link_to answer.title, + answer_question_path(question, answer: answer.title, token: token), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer.title), + class: "button secondary hollow js-question-answer" %> + <% end %> + <% end %> + <% elsif !user_signed_in? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, new_user_session_path, class: "button secondary hollow" %> + <% end %> + <% elsif !current_user.level_two_or_three_verified? %> + <% answers.order(id: :desc).each do |answer| %> + <%= link_to answer.title, verification_path, class: "button secondary hollow" %> + <% end %> + <% else %> + <% answers.order(id: :desc).each do |answer| %> + <%= answer.title %> + <% end %> + <% end %> +
diff --git a/app/views/polls/questions/_like_dislike.html.erb b/app/views/polls/questions/_like_dislike.html.erb new file mode 100644 index 000000000..e34476e9a --- /dev/null +++ b/app/views/polls/questions/_like_dislike.html.erb @@ -0,0 +1,19 @@ +<%= link_to answer_question_path(question, answer: answer.title, positive: positive, token: token, page: page), + method: method.to_sym, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer.title), + class: "expand js-question-answer" do %> + <% if positive %> + <% if active %> + + <% else %> + + <% end %> + <% else %> + <% if active %> + + <% else %> + + <% end %> + <% end %> +<% end %> diff --git a/app/views/polls/questions/_new_answer.html.erb b/app/views/polls/questions/_new_answer.html.erb new file mode 100644 index 000000000..2f056bafa --- /dev/null +++ b/app/views/polls/questions/_new_answer.html.erb @@ -0,0 +1,5 @@ +<%= form_tag(create_answer_question_path(question, token: token), method: :post, remote: true) do %> + + + "> +<% end %> diff --git a/app/views/polls/questions/_question.html.erb b/app/views/polls/questions/_question.html.erb index 016c0fa95..bb50da3bc 100644 --- a/app/views/polls/questions/_question.html.erb +++ b/app/views/polls/questions/_question.html.erb @@ -3,7 +3,35 @@ <%= question.title %> + <% unless question.votation_type.nil? %> + + <%= t("poll_questions.description.#{question.enum_type}", + maximum: question.votation_type.max_votes, + system: question.votation_type.prioritization_type) %> + + <% end %> +
- <%= render "polls/questions/answers", question: question, token: token %> + <% answers = question.is_positive_negative? ? question.question_answers.visibles.page(1) : question.question_answers.visibles %> + <%= render "polls/questions/answers", question: question, token: token, answers: answers, page: 1 %>
+ + <% if question.answers_with_read_more? %> +
+

<%= t("poll_questions.read_more_about") %>

+

+ <% first = true %> + <% question.question_answers&.visibles&.each do |answer| %> + <% if answer.description.present? || answer.images.any? || + answer.documents.present? || answer.videos.present? %> + <% unless first %> + + <% end %> + <% first = false if first %> + <%= link_to answer.title, "#answer_description_#{answer.id}" %> + <% end %> + <% end %> +

+
+ <% end %> diff --git a/app/views/polls/questions/answer.js.erb b/app/views/polls/questions/answer.js.erb index 79978e2c1..c35c55bfc 100644 --- a/app/views/polls/questions/answer.js.erb +++ b/app/views/polls/questions/answer.js.erb @@ -1,2 +1,3 @@ <% token = poll_voter_token(@question.poll, current_user) %> -$("#<%= dom_id(@question) %>_answers").html("<%= j render("polls/questions/answers", question: @question, token: token) %>"); +$("#<%= dom_id(@question) %>_answers").html("<%= j render("polls/questions/answers", question: @question, answers: @answers, token: token, page: @page) %>"); +App.Sortable.initialize(); diff --git a/app/views/polls/results.html.erb b/app/views/polls/results.html.erb index 7627fe018..c64362b95 100644 --- a/app/views/polls/results.html.erb +++ b/app/views/polls/results.html.erb @@ -1,4 +1,5 @@ -<% provide :title do %><%= @poll.name %><% end %> +<% provide :title do %><%= @poll.name %> +<% end %>
<%= render "poll_header" %> @@ -10,37 +11,32 @@

<%= t("polls.show.results.title") %>

<%- @poll.questions.each do |question| %> - <% most_voted_answer_id = question.most_voted_answer_id %>

<%= question.title %>

- - - <%- question.question_answers.each do |answer| %> - - <% end %> - - - - <%- question.question_answers.each do |answer| %> - > + + - <% end %> + <% end %> + + <% end %>
> - <% if answer.id == most_voted_answer_id %> - <%= t("polls.show.results.most_voted_answer") %> - <% end %> - <%= answer.title %> -
> - <%= answer.total_votes %> + <%- question.question_answers.visibles.each do |answer| %> +
+ <% if answer.most_voted %> + <%= t("polls.show.results.most_voted_answer") %> + <% end %> + <%= answer.title %> + > + <%= answer.total_votes %> + <% unless question.enum_type == "positive_negative_open" || question.enum_type == "prioritized" %> (<%= answer.total_votes_percentage.round(2) %>%) -
<% end %> diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb index 5fc0d6722..045218822 100644 --- a/app/views/polls/show.html.erb +++ b/app/views/polls/show.html.erb @@ -1,4 +1,4 @@ -<%= provide :title, t("social_share.polls_show.title_#{@poll.id}", default: @poll.title) %> +<%= provide :title, t("social_share.polls_show.title_#{@poll.id}", default: @poll.title) %> <%= provide :meta_description, t("social_share.polls_show.description_#{@poll.id}", default: @poll.title) %> <%= provide :social_media_meta_tags do %> <%= render "shared/social_media_meta_tags", @@ -55,71 +55,80 @@
+ <% @questions.each do |question| %> + <% if question.answers_with_read_more? %> +
+

<%= question.title %>

+ <% question.question_answers.visibles.each do |answer| %> + <% if answer.description.present? || answer.images.any? || answer.documents.present? || answer.videos.present? %> +
+

<%= answer.title %>

+
+
+ <% if answer.description.present? %> + <%= answer.description %> + <% end %> - <% @poll_questions_answers.each do |answer| %> -
" id="answer_<%= answer.id %>"> + <% if answer.images.any? %> +
+ <%= render "gallery", answer: answer %> +
+ <% end %> - <% if answer.description.present? %> -

<%= answer.title %>

- <% end %> + <% if answer.documents.present? %> + + <% end %> - <% if answer.images.any? %> - <%= render "gallery", answer: answer %> - <% end %> + <% if answer.videos.present? %> + + +
+
<% end %> -
- <% end %> - - <% if answer.videos.present? %> - - <% end %> -
+ <% end %> +
+ <% end %> <% end %>
diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index a02f71928..510294c5f 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -556,6 +556,7 @@ en: index: title: Polls create: Create poll + succesfull: Answer added succesfully count: one: You have created %{count} poll. other: You have created %{count} polls. @@ -648,6 +649,17 @@ en: title: Help about voting description: Citizens' polls are a participatory mechanism by which citizens with voting rights can make direct decisions no_polls: "There are no open votings." + max_votes_reached: "You have already made the maximum number of votes. Thank you very much for participating" + descriptions: + unique: It's only possible to answer one time to the question. + multiple: Allows to choose multiple answers. It's possible to set the maximum number of answers. + prioritized: Allows to choose more than one answer and they will be prioritized. It's possible to set the maximum number of answers chosen and the answer count type. + positive_open: Allows to vote positively a maximum number of times to questions. It's possible to set the maximum number of answers and add other answers. + positive_negative_open: Allows to vote positively and negatively a maximum number of times to questions. It's possible to set the maximum number of answers and add other answers. + answer_couples_open: Allows to vote a maximum number of times to couples of possible answers. It's possible to set the maximum number of couples and add other answers. + answer_couples_closed: Allows to vote a maximum number of times to couples of possible answers. It's possible to set the maximum number of couples and it's not possible to add other answers. + answer_set_open: Allows to vote a maximum number of answers to a group of them. It's possible to set the maximum number of answers, the size of the group and add other answers. + answer_set_closed: Allows to vote a maximum number of answers to a group of them. It's possible to set the maximum number of answers and the size of the group. show: already_voted_in_booth: "You have already participated in a physical booth. You can not participate again." already_voted_in_web: "You have already participated in this poll. If you vote again it will be overwritten." @@ -689,6 +701,18 @@ en: show: vote_answer: "Vote %{answer}" voted: "You have voted %{answer}" + add_answer: "Add answer" + description: + multiple: "You can select a maximum of %{maximum} answers." + positive_negative_open: "You can vote positive or negative a maximum of %{maximum} answers. And you can add your own answers." + answer_couples_open: "Choose an option from the following pair. You can choose a maximum of %{maximum} answers." + answer_couples_closed: "Choose an option from the following pair. You can choose a maximum of %{maximum} answers." + answer_set_open: "You can choose a maximum of %{maximum} answers." + answer_set_closed: "You can choose a maximum of %{maximum} answers." + prioritized: "You can select a maximum of %{maximum} answers. This question will use the %{system} system for count." + positive_open: "You can select a maximum of %{maximum} answers and add your own answers." + unique: "" + read_more_about: "Read more about:" proposal_notifications: new: title: "Send message" @@ -992,3 +1016,22 @@ en: surveys: Surveys poll: take_part: Take part from %{from} to %{to} + question: + max_votes: Maximum number of votes + max_group_answers: Maximum number of answers in the set + votation_type: Votation type + enum_type: + title: Votation type + unique: Unique answer, closed + multiple: Multiple answers, closed + prioritized: Multiple prioritized answer, closed + positive_open: Votable positive, open + positive_negative_open: Votable positive and negative, open + answer_couples_open: Couples of answers, open + answer_couples_closed: Couples of answers, closed + answer_set_open: Set of answers, open + answer_set_closed: Set of answers, closed + prioritization_type: + title: Prioritization type + borda: Borda votation + dowdall: Dowdall votation diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index 75931e379..1e9621506 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -556,6 +556,7 @@ es: index: title: Encuestas create: Crear encuesta + succesfull: Respuesta añadida correctamente. count: one: Has creado %{count} encuesta. other: Has creado %{count} encuestas. @@ -646,6 +647,17 @@ es: title: Ayuda sobre las votaciones description: Las votaciones ciudadanas son un mecanismo de participación por el que la ciudadanía con derecho a voto puede tomar decisiones de forma directa. no_polls: "No hay votaciones abiertas." + max_votes_reached: "Ya has realizado el número máximo de votos. Muchas gracias por participar" + descriptions: + unique: Solo se puede responder a la pregunta con una única respuesta. + multiple: Permite elegir más de una respuesta. Se puede elegir el número máximo de respuestas. + prioritized: Permite elegir mas de una respuesta, que a su vez se podran ordenar por prioridad. Se puede elegir el número máximo de respuestas y que tipo de recuento se utilizará en la priorización. + positive_open: Permite votar positivamente a un número máximo de respuestas. Se puede elegir el número máximo y los usuarios podran añadir resuestas. + positive_negative_open: Permite votar positiva y negativamente a un máximo de respuestas. Se puede elegir el número máximo y los usuarios podran añadir resuestas. + answer_couples_open: Permite votar a un número maximo de pares de respuestas. Se puede elegir el número maximo de pares y los usuarios podran añadir respuestas. + answer_couples_closed: Permite votar a un número maximo de pares de respuestas. Se puede elegir el número maximo de pares y los usuarios NO podran añadir respuestas. + answer_set_open: Permite votar un número máximo de respuestas en un conjunto de ellas. Se puede elegir el numero máximo de respuestas y el número de respuestas que hay en el conjunto. Los usuarios podran añadir respuestas. + answer_set_closed: Permite votar un número máximo de respuestas en un conjunto de ellas. Se puede elegir el numero máximo de respuestas y el número de respuestas que hay en el conjunto. show: already_voted_in_booth: "Ya has participado en esta votación en urnas presenciales, no puedes volver a participar." already_voted_in_web: "Ya has participado en esta votación. Si vuelves a votar se sobreescribirá tu resultado anterior." @@ -687,6 +699,17 @@ es: show: vote_answer: "Votar %{answer}" voted: "Has votado %{answer}" + add_answer: "Añadir respuesta" + description: + multiple: "Puedes seleccionar un máximo de %{maximum} respuestas." + positive_negative_open: "Puedes votar, positiva o negativamente, un máximo de %{maximum} respuestas. Puedes añadir tus propias respuestas." + multiple: "Puedes seleccionar un máximo de %{maximum} respuestas" + answer_couples_open: "Elige una opción entre el siguiente par. Puedes elegir un máximo de %{maximum} respuestas" + answer_couples_closed: "Elige una opción entre el siguiente par. Puedes elegir un máximo de %{maximum} respuestas" + prioritized: "Puedes seleccionar un máximo de %{maximum} respuestas. Esta pregunta utilizara el método %{system} para el recuento." + positive_open: "Puedes seleccionar un máximo de %{maximum} respuestas y añadir tus propias respuestas." + unique: "" + read_more_about: "Leer más:" proposal_notifications: new: title: "Enviar mensaje" @@ -990,3 +1013,22 @@ es: surveys: Encuestas poll: take_part: Participa del %{from} al %{to} + question: + max_votes: Número máximo de votos + max_group_answers: Número máximo de respuestas en el conjunto + votation_type: Tipo de votación + enum_type: + title: Tipo de votación + unique: Respuesta única, cerrada + multiple: Respuesta múltiple, cerrada + prioritized: Respuesta múltiple priorizada, cerrada + positive_open: Respuestas votables positivamente, abiertas + positive_negative_open: Respuestas votables positiva y negativamente, abiertas + answer_couples_open: Respuestas a pares, abiertas + answer_couples_closed: Respuestas a pares, cerradas + answer_set_open: Conjunto de respuestas, abiertas + answer_set_closed: Conjunto de respuestas, cerradas + prioritization_type: + title: Tipo de priorizacion + borda: Votación con recuento Borda + dowdall: Votación con recuento Dowdall diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 294868b27..2b850a27d 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -163,6 +163,7 @@ namespace :admin do end resource :active_polls, only: [:create, :edit, :update] + get :get_options_traductions, controller: "questions" end resources :verifications, controller: :verifications, only: :index do diff --git a/config/routes/poll.rb b/config/routes/poll.rb index 96c43f856..8836fa762 100644 --- a/config/routes/poll.rb +++ b/config/routes/poll.rb @@ -5,6 +5,12 @@ resources :polls, only: [:show, :index] do end resources :questions, controller: "polls/questions", shallow: true do - post :answer, on: :member + member do + post :answer + post :prioritized_answers + delete :answer, to: "polls/answers#delete" + post :create_answer, to: "polls/answers#create" + get :load_answers + end end end diff --git a/db/migrate/20190513112613_create_votation_types.rb b/db/migrate/20190513112613_create_votation_types.rb new file mode 100644 index 000000000..4ccfa8b55 --- /dev/null +++ b/db/migrate/20190513112613_create_votation_types.rb @@ -0,0 +1,17 @@ +class CreateVotationTypes < ActiveRecord::Migration[5.0] + def change + create_table :votation_types do |t| + t.integer :questionable_id + t.string :questionable_type + t.integer :enum_type + t.boolean :open_answer + t.boolean :prioritized + t.integer :prioritization_type + t.integer :max_votes + t.integer :max_groups_answers + + t.timestamps + end + end +end + diff --git a/db/migrate/20190514153436_add_order_and_positive_in_poll_answers.rb b/db/migrate/20190514153436_add_order_and_positive_in_poll_answers.rb new file mode 100644 index 000000000..10d34e7e9 --- /dev/null +++ b/db/migrate/20190514153436_add_order_and_positive_in_poll_answers.rb @@ -0,0 +1,7 @@ +class AddOrderAndPositiveInPollAnswers < ActiveRecord::Migration[5.0] + def change + add_column :poll_answers, :positive, :boolean + add_column :poll_answers, :order, :integer + end +end + diff --git a/db/migrate/20190522075912_create_poll_pair_answers.rb b/db/migrate/20190522075912_create_poll_pair_answers.rb new file mode 100644 index 000000000..d230c7f5b --- /dev/null +++ b/db/migrate/20190522075912_create_poll_pair_answers.rb @@ -0,0 +1,13 @@ +class CreatePollPairAnswers < ActiveRecord::Migration[5.0] + def change + create_table :poll_pair_answers do |t| + t.references :question, index: true + t.references :author, index: true + t.references :answer_rigth + t.references :answer_left + + t.timestamps + end + end +end + diff --git a/db/migrate/20190523113821_add_hidden_field_to_poll_question_answers.rb b/db/migrate/20190523113821_add_hidden_field_to_poll_question_answers.rb new file mode 100644 index 000000000..9c91fd4bc --- /dev/null +++ b/db/migrate/20190523113821_add_hidden_field_to_poll_question_answers.rb @@ -0,0 +1,6 @@ +class AddHiddenFieldToPollQuestionAnswers < ActiveRecord::Migration[5.0] + def change + add_column :poll_question_answers, :hidden, :boolean, default: false + end +end + diff --git a/db/migrate/20190524121957_add_value_to_poll_answers.rb b/db/migrate/20190524121957_add_value_to_poll_answers.rb new file mode 100644 index 000000000..22a6dcb9b --- /dev/null +++ b/db/migrate/20190524121957_add_value_to_poll_answers.rb @@ -0,0 +1,6 @@ +class AddValueToPollAnswers < ActiveRecord::Migration[5.0] + def change + add_column :poll_answers, :value, :integer + end +end + diff --git a/db/migrate/20190528134530_create_votation_set_answers.rb b/db/migrate/20190528134530_create_votation_set_answers.rb new file mode 100644 index 000000000..92367e63e --- /dev/null +++ b/db/migrate/20190528134530_create_votation_set_answers.rb @@ -0,0 +1,12 @@ +class CreateVotationSetAnswers < ActiveRecord::Migration[5.0] + def change + create_table :votation_set_answers do |t| + t.integer :author_id, index: true + t.references :votation_type, foreign_key: true, index: true + t.string :answer + + t.timestamps + end + end +end + diff --git a/db/schema.rb b/db/schema.rb index ae60009b0..313147f38 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1029,6 +1029,9 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.string "answer" t.datetime "created_at" t.datetime "updated_at" + t.boolean "positive" + t.integer "order" + t.integer "value" t.index ["author_id"], name: "index_poll_answers_on_author_id", using: :btree t.index ["question_id", "answer"], name: "index_poll_answers_on_question_id_and_answer", using: :btree t.index ["question_id"], name: "index_poll_answers_on_question_id", using: :btree @@ -1084,6 +1087,19 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.index ["user_id"], name: "index_poll_officers_on_user_id", using: :btree end + create_table "poll_pair_answers", force: :cascade do |t| + t.integer "question_id" + t.integer "author_id" + t.integer "answer_rigth_id" + t.integer "answer_left_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["answer_left_id"], name: "index_poll_pair_answers_on_answer_left_id", using: :btree + t.index ["answer_rigth_id"], name: "index_poll_pair_answers_on_answer_rigth_id", using: :btree + t.index ["author_id"], name: "index_poll_pair_answers_on_author_id", using: :btree + t.index ["question_id"], name: "index_poll_pair_answers_on_question_id", using: :btree + end + create_table "poll_partial_results", force: :cascade do |t| t.integer "question_id" t.integer "author_id" @@ -1127,6 +1143,7 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.integer "question_id" t.integer "given_order", default: 1 t.boolean "most_voted", default: false + t.boolean "hidden", default: false t.index ["question_id"], name: "index_poll_question_answers_on_question_id", using: :btree end @@ -1613,6 +1630,29 @@ ActiveRecord::Schema.define(version: 20190607160900) do t.index ["user_id"], name: "index_visits_on_user_id", using: :btree end + create_table "votation_set_answers", force: :cascade do |t| + t.integer "author_id" + t.integer "votation_type_id" + t.string "answer" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["author_id"], name: "index_votation_set_answers_on_author_id", using: :btree + t.index ["votation_type_id"], name: "index_votation_set_answers_on_votation_type_id", using: :btree + end + + create_table "votation_types", force: :cascade do |t| + t.integer "questionable_id" + t.string "questionable_type" + t.integer "enum_type" + t.boolean "open_answer" + t.boolean "prioritized" + t.integer "prioritization_type" + t.integer "max_votes" + t.integer "max_groups_answers" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "votes", force: :cascade do |t| t.string "votable_type" t.integer "votable_id" @@ -1721,4 +1761,5 @@ ActiveRecord::Schema.define(version: 20190607160900) do add_foreign_key "trackers", "users" add_foreign_key "users", "geozones" add_foreign_key "valuators", "users" + add_foreign_key "votation_set_answers", "votation_types" end diff --git a/spec/factories/polls.rb b/spec/factories/polls.rb index 7b49d3f5a..e9b525d00 100644 --- a/spec/factories/polls.rb +++ b/spec/factories/polls.rb @@ -38,6 +38,69 @@ FactoryBot.define do create(:poll_question_answer, question: question, title: "No") end end + + factory :poll_question_unique do + after(:create) do |question| + create(:votation_type_unique, questionable: question) + question.reload + end + end + + factory :poll_question_multiple do + after(:create) do |question| + create(:votation_type_multiple, questionable: question) + question.reload + end + end + + factory :poll_question_prioritized do + after(:create) do |question| + create(:votation_type_prioritized, questionable: question) + question.reload + end + end + + factory :poll_question_positive_open do + after(:create) do |question| + create(:votation_type_positive_open, questionable: question) + question.reload + end + end + + factory :poll_question_positive_negative_open do + after(:create) do |question| + create(:votation_type_positive_negative_open, questionable: question) + question.reload + end + end + + factory :poll_question_answer_couples_open do + after(:create) do |question| + create(:votation_type_answer_couples_open, questionable: question) + question.reload + end + end + + factory :poll_question_answer_couples_closed do + after(:create) do |question| + create(:votation_type_answer_couples_closed, questionable: question) + question.reload + end + end + + factory :poll_question_answer_set_open do + after(:create) do |question| + create(:votation_type_answer_set_open, questionable: question) + question.reload + end + end + + factory :poll_question_answer_set_closed do + after(:create) do |question| + create(:votation_type_answer_set_closed, questionable: question) + question.reload + end + end end factory :poll_question_answer, class: "Poll::Question::Answer" do @@ -177,4 +240,11 @@ FactoryBot.define do factory :active_poll do end + + factory :poll_pair_answer, class: "Poll::PairAnswer" do + question { create :poll_question } + author { create :user } + answer_left { create(:poll_question_answer, question: question) } + answer_right { create(:poll_question_answer, question: question) } + end end diff --git a/spec/factories/votation_type.rb b/spec/factories/votation_type.rb new file mode 100644 index 000000000..fc391409f --- /dev/null +++ b/spec/factories/votation_type.rb @@ -0,0 +1,77 @@ +FactoryBot.define do + factory :votation_type do + factory :votation_type_unique do + enum_type "unique" + open_answer false + prioritized false + end + + factory :votation_type_multiple do + enum_type "multiple" + open_answer false + prioritized false + max_votes 5 + end + + factory :votation_type_prioritized do + enum_type "prioritized" + open_answer false + prioritized true + max_votes 5 + prioritization_type "borda" + end + + factory :votation_type_positive_open do + enum_type "positive_open" + open_answer true + prioritized false + max_votes 5 + end + + factory :votation_type_positive_negative_open do + enum_type "positive_negative_open" + open_answer true + prioritized false + max_votes 5 + prioritization_type "borda" + end + + factory :votation_type_answer_couples_open do + enum_type "answer_couples_open" + open_answer true + prioritized false + max_votes 5 + end + + factory :votation_type_answer_couples_closed do + enum_type "answer_couples_open" + open_answer false + prioritized false + max_votes 5 + end + + factory :votation_type_answer_set_open do + enum_type "answer_set_open" + open_answer true + prioritized false + max_votes 3 + max_groups_answers 5 + end + + factory :votation_type_answer_set_closed do + enum_type "answer_set_open" + open_answer false + prioritized false + max_votes 3 + max_groups_answers 5 + end + + trait :open do + open_answer true + end + trait :prioritized do + prioritized true + end + + end +end diff --git a/spec/features/admin/poll/polls_spec.rb b/spec/features/admin/poll/polls_spec.rb index 398ba4925..536e45858 100644 --- a/spec/features/admin/poll/polls_spec.rb +++ b/spec/features/admin/poll/polls_spec.rb @@ -248,14 +248,17 @@ describe "Admin polls" do scenario "Question list", :js do poll = create(:poll) question = create(:poll_question, poll: poll) + votation_type_question = create(:poll_question_unique, poll: poll) other_question = create(:poll_question) visit admin_poll_path(poll) - expect(page).to have_content "Questions (1)" + expect(page).to have_content "Questions (2)" expect(page).to have_content question.title + expect(page).to have_content votation_type_question.title expect(page).not_to have_content other_question.title expect(page).not_to have_content "There are no questions assigned to this poll" + end end diff --git a/spec/features/admin/poll/questions_spec.rb b/spec/features/admin/poll/questions_spec.rb index c315f904e..c1906e4dd 100644 --- a/spec/features/admin/poll/questions_spec.rb +++ b/spec/features/admin/poll/questions_spec.rb @@ -19,6 +19,7 @@ describe "Admin poll questions" do question1 = create(:poll_question, poll: poll1) question2 = create(:poll_question, poll: poll2) question3 = create(:poll_question, poll: poll3, proposal: proposal) + question4 = create(:poll_question_unique, poll: poll1) visit admin_poll_path(poll1) expect(page).to have_content(poll1.name) @@ -30,6 +31,13 @@ describe "Admin poll questions" do expect(page).to have_content("Delete") end + within("#poll_question_#{question4.id}") do + expect(page).to have_content(question4.title) + expect(page).to have_content("Edit answers") + expect(page).to have_content("Edit") + expect(page).to have_content("Delete") + end + visit admin_poll_path(poll2) expect(page).to have_content(poll2.name) @@ -52,16 +60,35 @@ describe "Admin poll questions" do end end - scenario "Show" do - geozone = create(:geozone) - poll = create(:poll, geozone_restricted: true, geozone_ids: [geozone.id]) - question = create(:poll_question, poll: poll) + context "Show" do + scenario "Without Votation type" do + geozone = create(:geozone) + poll = create(:poll, geozone_restricted: true, geozone_ids: [geozone.id]) + question = create(:poll_question, poll: poll) - visit admin_poll_path(poll) - click_link "#{question.title}" + visit admin_poll_path(poll) + click_link question.title - expect(page).to have_content(question.title) - expect(page).to have_content(question.author.name) + expect(page).to have_content(question.title) + expect(page).to have_content(question.author.name) + expect(page).not_to have_content("Votation type") + end + + scenario "With Votation type" do + geozone = create(:geozone) + poll = create(:poll, geozone_restricted: true, geozone_ids: [geozone.id]) + question = create(:poll_question_multiple, poll: poll) + + visit admin_poll_path(poll) + click_link "#{question.title}" + + expect(page).to have_content(question.title) + expect(page).to have_content(question.author.name) + expect(page).to have_content("Votation type") + expect(page).to have_content("Multiple") + expect(page).to have_content("Maximum number of votes") + expect(page).to have_content("5") + end end scenario "Create" do @@ -123,6 +150,179 @@ describe "Admin poll questions" do expect(page).to have_content(proposal.title) end + context "create with votation type" do + before do + poll = create(:poll, name: "Movies") + visit admin_poll_path(poll) + click_link "Create question" + end + + scenario "unique" do + title = "unique question" + fill_in "Question", with: title + select "Unique answer, closed", from: "votation_type_enum_type" + + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Unique answer, closed") + end + + scenario "multiple" do + title = "multiple question" + fill_in "Question", with: title + select "Multiple answers, closed", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Multiple answers, closed") + end + + scenario "prioritized" do + title = "prioritized question" + fill_in "Question", with: title + select "Multiple prioritized answer, closed", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Multiple prioritized answer, closed") + end + + scenario "positive_open" do + title = "positive open question" + fill_in "Question", with: title + select "Votable positive, open", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Votable positive, open") + end + + scenario "positive_negative_open" do + title = "positive negative open question" + fill_in "Question", with: title + select "Votable positive and negative, open", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Votable positive and negative, open") + end + + scenario "answer_couples_open" do + title = "answer couples open question" + fill_in "Question", with: title + select "Couples of answers, open", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Couples of answers, open") + end + + scenario "answer_couples_closed" do + title = "answer couples closed question" + fill_in "Question", with: title + select "Couples of answers, closed", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Couples of answers, closed") + end + + scenario "answer_set_open" do + title = "answer set open question" + fill_in "Question", with: title + select "Set of answers, open", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of answers in the set", with: 3 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Set of answers, open") + end + + scenario "answer_set_closed" do + title = "answer set closed question" + fill_in "Question", with: title + select "Set of answers, closed", from: "votation_type_enum_type" + + expect(page).to have_content("Maximum number of votes") + + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of votes", with: 6 + click_button "Save" + + expect(page).to have_content("1 error prevented this Poll/Question from being saved.") + + fill_in "Maximum number of answers in the set", with: 3 + click_button "Save" + + expect(page).to have_content(title) + expect(page).to have_content("Set of answers, closed") + end + end + scenario "Update" do poll = create(:poll) question1 = create(:poll_question, poll: poll) diff --git a/spec/features/polls/polls_spec.rb b/spec/features/polls/polls_spec.rb index b0d968448..02ba30eb5 100644 --- a/spec/features/polls/polls_spec.rb +++ b/spec/features/polls/polls_spec.rb @@ -231,11 +231,12 @@ describe "Polls" do visit poll_path(expired_poll) - expect(page).to have_content("Luke") - expect(page).to have_content("Leia") - expect(page).not_to have_link("Luke") - expect(page).not_to have_link("Leia") - + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content("Luke") + expect(page).to have_content("Leia") + expect(page).not_to have_link("Luke") + expect(page).not_to have_link("Leia") + end expect(page).to have_content("This poll has finished") end @@ -251,10 +252,12 @@ describe "Polls" do visit poll_path(poll) - expect(page).to have_content("Vader") - expect(page).to have_content("Palpatine") - expect(page).not_to have_link("Vader") - expect(page).not_to have_link("Palpatine") + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content("Vader") + expect(page).to have_content("Palpatine") + expect(page).not_to have_link("Vader") + expect(page).not_to have_link("Palpatine") + end end scenario "Level 2 users reading a same-geozone poll" do @@ -268,8 +271,10 @@ describe "Polls" do login_as(create(:user, :level_two, geozone: geozone)) visit poll_path(poll) - expect(page).to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + within("#poll_question_#{question.id}_answers") do + expect(page).to have_link("Han Solo") + expect(page).to have_link("Chewbacca") + end end scenario "Level 2 users reading a all-geozones poll" do @@ -280,8 +285,10 @@ describe "Polls" do login_as(create(:user, :level_two)) visit poll_path(poll) - expect(page).to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + within("#poll_question_#{question.id}_answers") do + expect(page).to have_link("Han Solo") + expect(page).to have_link("Chewbacca") + end end scenario "Level 2 users who have already answered" do @@ -294,8 +301,10 @@ describe "Polls" do login_as user visit poll_path(poll) - expect(page).to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + within("#poll_question_#{question.id}_answers") do + expect(page).to have_link("Han Solo") + expect(page).to have_link("Chewbacca") + end end scenario "Level 2 users answering", :js do @@ -311,10 +320,12 @@ describe "Polls" do login_as user visit poll_path(poll) - click_link "Han Solo" + within("#poll_question_#{question.id}_answers") do + click_link "Han Solo" - expect(page).not_to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + expect(page).not_to have_link("Han Solo") + expect(page).to have_link("Chewbacca") + end end scenario "Level 2 users changing answer", :js do @@ -330,15 +341,17 @@ describe "Polls" do login_as user visit poll_path(poll) - click_link "Han Solo" + within("#poll_question_#{question.id}_answers") do + click_link "Han Solo" - expect(page).not_to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + expect(page).not_to have_link("Han Solo") + expect(page).to have_link("Chewbacca") - click_link "Chewbacca" + click_link "Chewbacca" - expect(page).not_to have_link("Chewbacca") - expect(page).to have_link("Han Solo") + expect(page).not_to have_link("Chewbacca") + expect(page).to have_link("Han Solo") + end end scenario "Level 2 votes, signs out, signs in, votes again", :js do @@ -353,26 +366,33 @@ describe "Polls" do login_as user visit poll_path(poll) - click_link "Han Solo" - expect(page).not_to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + within("#poll_question_#{question.id}_answers") do + click_link "Han Solo" + + expect(page).not_to have_link("Han Solo") + expect(page).to have_link("Chewbacca") + end click_link "Sign out" login_as user visit poll_path(poll) - click_link "Han Solo" + within("#poll_question_#{question.id}_answers") do + click_link "Han Solo" - expect(page).not_to have_link("Han Solo") - expect(page).to have_link("Chewbacca") + expect(page).not_to have_link("Han Solo") + expect(page).to have_link("Chewbacca") + end click_link "Sign out" login_as user visit poll_path(poll) - click_link "Chewbacca" + within("#poll_question_#{question.id}_answers") do + click_link "Chewbacca" - expect(page).not_to have_link("Chewbacca") - expect(page).to have_link("Han Solo") + expect(page).not_to have_link("Chewbacca") + expect(page).to have_link("Han Solo") + end end end diff --git a/spec/features/polls/votation_type_spec.rb b/spec/features/polls/votation_type_spec.rb new file mode 100644 index 000000000..255a52e5c --- /dev/null +++ b/spec/features/polls/votation_type_spec.rb @@ -0,0 +1,442 @@ +require "rails_helper" + +feature "Poll Votation Type" do + + context "Unique" do + + let(:user) { create(:user, :verified) } + let(:poll_current) { create(:poll, :current) } + let(:unique) { create(:poll_question_unique, poll: poll_current) } + let!(:answer1) { create(:poll_question_answer, question: unique, title: "answer_1") } + let!(:answer2) { create(:poll_question_answer, question: unique, title: "answer_2") } + + background do + login_as(user) + end + + scenario "response question without votation type" do + question = create(:poll_question, :with_answers, poll: poll_current) + visit poll_path(poll_current) + + expect(page).to have_content(question.title) + expect(page).to have_content(answer1.title) + end + + scenario "response question with votation type" do + visit poll_path(poll_current) + + expect(page).to have_content(unique.title) + expect(page).to have_content(answer1.title) + end + + scenario "response question vote", :js do + visit poll_path(poll_current) + + within("#poll_question_#{unique.id}_answers") do + click_link answer1.title + expect(page).to have_css("a", text: answer2.title) + expect(page).not_to have_css("a", text: answer1.title) + end + end + + scenario "response question change vote", :js do + visit poll_path(poll_current) + + within("#poll_question_#{unique.id}_answers") do + click_link answer1.title + expect(page).to have_css("a", text: answer2.title) + expect(page).not_to have_css("a", text: answer1.title) + click_link answer2.title + expect(page).to have_css("a", text: answer1.title) + expect(page).not_to have_css("a", text: answer2.title) + end + end + end + + context "Multiple" do + + let(:user) { create(:user, :verified) } + let(:poll_current) { create(:poll, :current) } + let(:question) { create(:poll_question_multiple, poll: poll_current) } + let!(:answer1) { create(:poll_question_answer, question: question, title: "answer_1") } + let!(:answer2) { create(:poll_question_answer, question: question, title: "answer_2") } + let!(:answer3) { create(:poll_question_answer, question: question, title: "answer_3") } + let!(:answer4) { create(:poll_question_answer, question: question, title: "answer_4") } + let!(:answer5) { create(:poll_question_answer, question: question, title: "answer_5") } + + background do + login_as(user) + end + + scenario "response question" do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content(answer.title) + end + end + end + + scenario "response question vote", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + expect(page).to have_css(".answered", text: answer.title) + end + end + end + + scenario "response question no more vote than allowed", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer6.title + expect(page).not_to have_css(".answered", text: answer6.title) + end + end + + scenario "response question remove vote and vote again", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer1.title + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + expect(page).to have_css("a", text: answer6.title) + + click_link answer6.title + + expect(page).to have_css(".answered", text: answer6.title) + end + end + end + + context "Prioritized" do + + let(:user) { create(:user, :verified) } + let(:poll_current) { create(:poll, :current) } + let(:question) { create(:poll_question_prioritized, poll: poll_current) } + let!(:answer1) { create(:poll_question_answer, question: question, title: "answer_1") } + let!(:answer2) { create(:poll_question_answer, question: question, title: "answer_2") } + let!(:answer3) { create(:poll_question_answer, question: question, title: "answer_3") } + let!(:answer4) { create(:poll_question_answer, question: question, title: "answer_4") } + let!(:answer5) { create(:poll_question_answer, question: question, title: "answer_5") } + + background do + login_as(user) + end + + scenario "response question" do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content(answer.title) + end + end + end + + scenario "response question vote", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + expect(page).to have_css("a", text: answer.title) + end + end + + end + + scenario "response question no more vote than allowed", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer6.title + expect(page).not_to have_css(".answered", text: answer6.title) + end + end + end + + context "Positive open" do + + let(:user) { create(:user, :verified) } + let(:poll_current) { create(:poll, :current) } + let(:question) { create(:poll_question_positive_open, poll: poll_current) } + let!(:answer1) { create(:poll_question_answer, question: question, title: "answer_1") } + let!(:answer2) { create(:poll_question_answer, question: question, title: "answer_2") } + let!(:answer3) { create(:poll_question_answer, question: question, title: "answer_3") } + let!(:answer4) { create(:poll_question_answer, question: question, title: "answer_4") } + let!(:answer5) { create(:poll_question_answer, question: question, title: "answer_5") } + + background do + login_as(user) + end + + scenario "response question" do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content(answer.title) + end + end + end + + scenario "response question vote", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + expect(page).to have_css(".answered", text: answer.title) + end + end + end + + scenario "response question no more vote than allowed", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer6.title + expect(page).not_to have_css(".answered", text: answer6.title) + end + end + + scenario "response question remove vote and vote again", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer1.title + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer6.title + expect(page).to have_css(".answered", text: answer6.title) + end + end + + scenario "add answer", :js do + visit poll_path(poll_current) + + fill_in "answer", with: "added_answer" + click_button "Add answer" + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + expect(page).to have_css("a", text: "added_answer") + click_link "added_answer" + expect(page).to have_css(".answered", text: "added_answer") + end + end + end + + xcontext "Positive and negative open" do + + let(:user) { create(:user, :verified) } + let(:poll_current) { create(:poll, :current) } + let(:question) { create(:poll_question_positive_negative_open, poll: poll_current) } + let!(:answer1) { create(:poll_question_answer, question: question, title: "answer_1") } + let!(:answer2) { create(:poll_question_answer, question: question, title: "answer_2") } + let!(:answer3) { create(:poll_question_answer, question: question, title: "answer_3") } + let!(:answer4) { create(:poll_question_answer, question: question, title: "answer_4") } + let!(:answer5) { create(:poll_question_answer, question: question, title: "answer_5") } + + background do + login_as(user) + end + + scenario "response question" do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content(answer.title) + end + end + end + + scenario "response question vote", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + title = "vote #{answer.title}" + page.find("poll_question_#{question.id}_answers .in-favor a[title=#{title}]").click + page.find("poll_question_#{question.id}_answers .in-favor a[title=#{title}]").has_css?(".answered") + end + end + + scenario "response question no more vote than allowed", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer6.title + expect(page.find_link(answer6.title)).not_to have_css(".answered") + end + end + + scenario "response question remove vote and vote again", :js do + visit poll_path(poll_current) + + question.question_answers.each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.title + end + end + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer1.title + end + + answer6 = create(:poll_question_answer, question: question, title: "answer_6") + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + click_link answer6.title + page.find_link(answer6.title).has_css?(".answered") + end + end + + scenario "add answer", :js do + visit poll_path(poll_current) + + fill_in "answer", with: "added_answer" + click_button "Add answer" + + visit poll_path(poll_current) + + within("#poll_question_#{question.id}_answers") do + expect(page).to have_css("a", text: "added_answer") + click_link "added_answer" + expect(page).to have_css(".answered", text: "added_answer") + end + end + end + + context "Answers set" do + + let(:user) { create(:user, :verified) } + let(:poll_current) { create(:poll, :current) } + let(:question) { create(:poll_question_answer_set_open, poll: poll_current) } + let!(:answer1) { create(:poll_question_answer, question: question, title: "answer_1") } + let!(:answer2) { create(:poll_question_answer, question: question, title: "answer_2") } + let!(:answer3) { create(:poll_question_answer, question: question, title: "answer_3") } + let!(:answer4) { create(:poll_question_answer, question: question, title: "answer_4") } + let!(:answer5) { create(:poll_question_answer, question: question, title: "answer_5") } + + before do + login_as(user) + end + + scenario "response question" do + visit poll_path(poll_current) + + expect(page.find("#poll_question_#{question.id}_answers")).to have_css("a", count: question.max_groups_answers) + end + + scenario "response question vote", :js do + visit poll_path(poll_current) + + question.votation_type.votation_set_answers.by_author(user).each do |answer| + within("#poll_question_#{question.id}_answers") do + click_link answer.answer + end + end + + within("#poll_question_#{question.id}_answers") do + expect(page).to have_css(".answered", count: question.max_votes) + end + + end + + scenario "add answer", :js do + visit poll_path(poll_current) + + fill_in "answer", with: "added_answer" + click_button "Add answer" + + within("#poll_question_#{question.id}_answers") do + expect(page).to have_content("Answer added succesfully") + end + end + end + + +end diff --git a/spec/features/polls/voter_spec.rb b/spec/features/polls/voter_spec.rb index c2b3f5bbc..760957254 100644 --- a/spec/features/polls/voter_spec.rb +++ b/spec/features/polls/voter_spec.rb @@ -150,7 +150,9 @@ describe "Voter" do login_as user visit poll_path(poll) - expect(page).not_to have_link(answer_yes.title) + within("#poll_question_#{question.id}_answers") do + expect(page).not_to have_link(answer_yes.title) + end expect(page).to have_content "You have already participated in a physical booth. You can not participate again." expect(Poll::Voter.count).to eq(1) @@ -214,7 +216,10 @@ describe "Voter" do visit poll_path(poll) - expect(page).not_to have_link(answer_yes.title) + within("#poll_question_#{question.id}_answers") do + expect(page).not_to have_link(answer_yes.title) + end + expect(page).to have_content "You have already participated in a physical booth. You can not participate again." expect(Poll::Voter.count).to eq(1) diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index 1d1acb222..575a34744 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -88,6 +88,11 @@ describe Abilities::Administrator do it { should be_able_to(:stats, poll) } it { should be_able_to(:results, poll) } + it { should be_able_to(:read, Poll::Question)} + it { should be_able_to(:create, Poll::Question)} + it { should be_able_to(:update, Poll::Question)} + it { should be_able_to(:get_options_traductions, Poll::Question)} + it { is_expected.to be_able_to :manage, Dashboard::AdministratorTask } it { is_expected.to be_able_to :manage, dashboard_administrator_task } end diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index f9fd8fbef..d30c7ddfc 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -213,6 +213,18 @@ describe Abilities::Common do it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) } it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) } + it { should be_able_to(:prioritized_answers, poll_question_from_own_geozone) } + it { should be_able_to(:prioritized_answers, poll_question_from_all_geozones) } + it { should_not be_able_to(:prioritized_answers, poll_question_from_other_geozone) } + + it { should_not be_able_to(:prioritized_answers, expired_poll_question_from_own_geozone) } + it { should_not be_able_to(:prioritized_answers, expired_poll_question_from_all_geozones) } + it { should_not be_able_to(:prioritized_answers, expired_poll_question_from_other_geozone) } + + context "Poll::Question" do + it { should be_able_to(:load_answers, Poll::Question) } + end + context "without geozone" do before { user.geozone = nil } diff --git a/spec/models/poll/pair_answer_spec.rb b/spec/models/poll/pair_answer_spec.rb new file mode 100644 index 000000000..8f7d5efde --- /dev/null +++ b/spec/models/poll/pair_answer_spec.rb @@ -0,0 +1,131 @@ +require "rails_helper" + +describe Poll::PairAnswer do + + describe "validations" do + + let(:pair_answer) { build(:poll_pair_answer) } + + it "is valid" do + expect(pair_answer).to be_valid + end + + it "is not valid wihout a question" do + pair_answer.question = nil + + expect(pair_answer).not_to be_valid + end + + it "is not valid without an author" do + pair_answer.author = nil + + expect(pair_answer).not_to be_valid + end + + it "is valid if answer_left is included in the Poll::Question's question_answers list" do + question = create(:poll_question) + answer1 = create(:poll_question_answer, title: "One", question: question) + answer2 = create(:poll_question_answer, title: "Two", question: question) + answer3 = create(:poll_question_answer, title: "Three") + + expect(build(:poll_pair_answer, question: question, answer_left: answer1)).to be_valid + expect(build(:poll_pair_answer, question: question, answer_left: answer2)).to be_valid + expect(build(:poll_pair_answer, question: question, answer_left: answer3)).not_to be_valid + end + + it "is valid if answer_right is included in the Poll::Question's question_answers list" do + question = create(:poll_question) + answer1 = create(:poll_question_answer, title: "One", question: question) + answer2 = create(:poll_question_answer, title: "Two", question: question) + answer3 = create(:poll_question_answer, title: "Three") + + expect(build(:poll_pair_answer, question: question, answer_right: answer1)).to be_valid + expect(build(:poll_pair_answer, question: question, answer_right: answer2)).to be_valid + expect(build(:poll_pair_answer, question: question, answer_right: answer3)).not_to be_valid + end + end + + context "scopes" do + let(:pair_answer_1) { create(:poll_pair_answer) } + let(:pair_answer_2) { create(:poll_pair_answer) } + + describe "#by_author" do + + it "returns pair_answers associated to an user" do + author = pair_answer_1.author + + expect(described_class.by_author(author)).to include(pair_answer_1) + expect(described_class.by_author(author)).not_to include(pair_answer_2) + end + + end + + describe "#by_question" do + + it "returns pair_answers associated to a question" do + question = pair_answer_1.question + + expect(described_class.by_question(question)).to include(pair_answer_1) + expect(described_class.by_question(question)).not_to include(pair_answer_2) + end + end + + end + + + describe "#generate_pair" do + let(:user) { create(:user) } + let(:question) { create(:poll_question) } + + context "without question_answers" do + + it "assigns nil value to pair_answers" do + pair_answer = described_class.generate_pair(question, user) + + expect(pair_answer).to be_a Poll::PairAnswer + expect(pair_answer.question).to eq(question) + expect(pair_answer.author).to eq(user) + expect(pair_answer.answer_left).to be_nil + expect(pair_answer.answer_right).to be_nil + end + end + + context "With question answers" do + let!(:answer1) { create(:poll_question_answer, question: question) } + + it "assigns only right question if only has one question_answer" do + pair_answer = described_class.generate_pair(question, user) + + expect(pair_answer).to be_a Poll::PairAnswer + expect(pair_answer.question).to eq(question) + expect(pair_answer.author).to eq(user) + expect(pair_answer.answer_left).to eq(answer1) + expect(pair_answer.answer_right).to be_nil + end + + it "assigns random values if question has some question_answer" do + create(:poll_question_answer, question: question) + + pair_answer = described_class.generate_pair(question, user) + + expect(pair_answer).to be_a Poll::PairAnswer + expect(pair_answer.question).to eq(question) + expect(pair_answer.author).to eq(user) + expect(pair_answer.answer_left).to be_a Poll::Question::Answer + expect(pair_answer.answer_right).to be_a Poll::Question::Answer + expect(pair_answer.answer_left).not_to eq(pair_answer.answer_right) + end + end + end + + describe "#answers" do + let(:pair_answer) { create(:poll_pair_answer) } + + it "returns and array of answers" do + expect(pair_answer.answers).to be_a Array + expect(pair_answer.answers[0]).to eq(pair_answer.answer_left) + expect(pair_answer.answers[1]).to eq(pair_answer.answer_right) + end + end + +end diff --git a/spec/models/poll/question_spec.rb b/spec/models/poll/question_spec.rb index 87a3698f1..7adee447d 100644 --- a/spec/models/poll/question_spec.rb +++ b/spec/models/poll/question_spec.rb @@ -44,4 +44,35 @@ RSpec.describe Poll::Question, type: :model do end end + describe "#enum_type" do + + it "returns nil if not has votation_type association" do + expect(poll_question.votation_type).to be_nil + expect(poll_question.enum_type).to be_nil + end + + it "returns enum_type from votation_type association" do + question = create(:poll_question_answer_couples_open) + + expect(question.votation_type).not_to be_nil + expect(question.enum_type).to eq("answer_couples_open") + end + + end + + describe "#max_votes" do + + it "returns nil if not has votation_type association" do + expect(poll_question.votation_type).to be_nil + expect(poll_question.max_votes).to be_nil + end + + it "returns max_votes from votation_type association" do + question = create(:poll_question_answer_couples_open) + + expect(question.votation_type).not_to be_nil + expect(question.max_votes).to eq(5) + end + + end end