Merge branch 'master' into fix/shift_creation_polls

This commit is contained in:
Raimond Garcia
2017-10-04 20:14:08 +02:00
committed by GitHub
32 changed files with 283 additions and 84 deletions

View File

@@ -0,0 +1,30 @@
class Admin::Poll::Questions::AnswersController < Admin::Poll::BaseController
before_action :load_question
load_and_authorize_resource :question, class: "::Poll::Question"
def new
@answer = ::Poll::Question::Answer.new
end
def create
@answer = ::Poll::Question::Answer.new(answer_params)
if @answer.save
redirect_to admin_question_path(@question),
notice: t("flash.actions.create.poll_question_answer")
else
render :new
end
end
private
def answer_params
params.require(:poll_question_answer).permit(:title, :description, :poll_question_id)
end
def load_question
@question = ::Poll::Question.find(params[:question_id])
end
end

View File

@@ -12,7 +12,7 @@ class Polls::QuestionsController < ApplicationController
answer.save!
answer.record_voter_participation
@answers_by_question_id = {@question.id => params[:answer]}
@answers_by_question_id = { @question.id => params[:answer] }
end
end

View File

@@ -8,8 +8,10 @@ class Poll::Answer < ActiveRecord::Base
validates :question, presence: true
validates :author, presence: true
validates :answer, presence: true
validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }},
unless: ->(a) { a.question.blank? }
# temporary skipping validation, review when removing valid_answers
# validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }},
# 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) }

View File

@@ -14,7 +14,8 @@ class Poll::Question < ActiveRecord::Base
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
has_many :comments, as: :commentable
has_many :answers
has_many :answers, class_name: 'Poll::Answer'
has_many :question_answers, class_name: 'Poll::Question::Answer'
has_many :partial_results
belongs_to :proposal

View File

@@ -0,0 +1,9 @@
class Poll::Question::Answer < ActiveRecord::Base
belongs_to :question, class_name: 'Poll::Question', foreign_key: 'question_id'
validates :title, presence: true
def description
super.try :html_safe
end
end

View File

@@ -16,10 +16,6 @@
<%= f.text_field :title, maxlength: Poll::Question.title_max_length %>
<%= f.label :valid_answers %>
<p class="help-text" id="valid-answers-help-text"><%= t("admin.questions.new.valid_answers_note") %></p>
<%= f.text_field :valid_answers, label: false, aria: {describedby: "valid-answers-help-text"} %>
<div class="documents small-12">
<%= render 'documents/nested_documents', documentable: @question, f: f %>
</div>

View File

@@ -0,0 +1,24 @@
<%= form_for(@answer, url: form_url) do |f| %>
<%= render 'shared/errors', resource: @answer %>
<%= f.hidden_field :question_id, value: @question.id %>
<div class="row">
<div class="small-12 column">
<%= f.text_field :title %>
<div class="ckeditor">
<%= f.cktext_area :description,
maxlength: Poll::Question.description_max_length,
ckeditor: { language: I18n.locale } %>
</div>
<div class="row">
<div class="actions small-12 medium-4 column margin-top">
<%= f.submit(class: "button expanded", value: t("shared.save")) %>
</div>
</div>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,7 @@
<%= back_link_to %>
<h2><%= t('admin.answers.new.title') %></h2>
<div class="poll-question-answer-form">
<%= render "form", form_url: admin_question_answers_path %>
</div>

View File

@@ -24,15 +24,30 @@
<%= link_to @question.author.name, user_path(@question.author) %>
</p>
<p>
<strong><%= t("admin.questions.show.valid_answers") %></strong>
</p>
<table>
<tr>
<th colspan="1" scope="colgroup">
<%= t('admin.questions.show.valid_answers') %>
</th>
<th colspan="1" scope="colgroup">
<%= link_to t("admin.questions.show.add_answer"),
new_admin_question_answer_path(@question),
class: "button hollow float-right" %>
</th>
</tr>
<% @question.valid_answers.each do |answer| %>
<span class="button">
<%= answer %>
</span>
<% end %>
<tr>
<th scope="col"><%= t("admin.questions.show.answers.title") %></th>
<th scope="col"><%= t("admin.questions.show.answers.description") %></th>
</tr>
<% @question.question_answers.each do |answer| %>
<tr id="<%= dom_id(answer) %>" class="poll_question_answer">
<th scope="col"><%= answer.title %></th>
<th scope="col"><%= answer.description %></th>
</tr>
<% end %>
</table>
<% if @question.video_url.present? %>
<p>

View File

@@ -18,5 +18,9 @@
<div class="callout alert">
<%= t('polls.show.cant_answer_expired') %>
</div>
<% else %>
<div class="callout warning">
<%= t('polls.show.cant_answer_wrong_geozone') %>
</div>
<% end %>
<% end %>

View File

@@ -1,24 +0,0 @@
<% if poll.incoming? %>
<div class="callout primary">
<%= t('poll_questions.show.cant_answer_incoming') %>
</div>
<% elsif poll.expired? %>
<div class="callout alert">
<%= t('poll_questions.show.cant_answer_expired') %>
</div>
<% elsif current_user.nil? %>
<div class="callout primary">
<%= t("poll_questions.show.not_logged_in",
signin: link_to(t("poll_questions.show.signin"), new_user_session_path, class: "probe-message"),
signup: link_to(t("poll_questions.show.signup"), new_user_registration_path, class: "probe-message")).html_safe %>
</div>
<% elsif current_user.unverified? %>
<div class="callout warning">
<%= t('poll_questions.show.cant_answer_verify_html',
verify_link: link_to(t('poll_questions.show.verify_link'), verification_path)) %>
</div>
<% else %>
<div class="callout warning">
<%= t('poll_questions.show.cant_answer_wrong_geozone') %>
</div>
<% end %>

View File

@@ -1,22 +1,23 @@
<div class="poll-question-answers">
<% if can? :answer, question %>
<% question.valid_answers.each do |answer| %>
<% if @answers_by_question_id[question.id] == answer %>
<span class="button answered" title="<%= t("poll_questions.show.voted", answer: answer)%>">
<%= answer %>
<% question.question_answers.each do |answer| %>
<% if @answers_by_question_id[question.id] == answer.title %>
<span class="button answered"
title="<%= t("poll_questions.show.voted", answer: answer)%>">
<%= answer.title %>
</span>
<% else %>
<%= link_to answer,
answer_question_path(question, answer: answer),
<%= link_to answer.title,
answer_question_path(question, answer: answer.title),
method: :post,
remote: true,
title: t("poll_questions.show.vote_answer", answer: answer),
title: t("poll_questions.show.vote_answer", answer: answer.title),
class: "button secondary hollow" %>
<% end %>
<% end %>
<% else %>
<% question.valid_answers.each do |answer| %>
<span class="button secondary hollow disabled"><%= answer %></span>
<% question.question_answers.each do |answer| %>
<span class="button secondary hollow disabled"><%= answer.title %></span>
<% end %>
<% end %>
</div>

View File

@@ -213,6 +213,9 @@ en:
image:
title: Title
attachment: Attachment
poll/question_answer:
title: "Answer"
description: "Description"
errors:
models:
user:

View File

@@ -587,14 +587,20 @@ en:
new:
title: "Create Question"
poll_label: "Poll"
valid_answers_note: "Enter the answers separated by commas (,)"
show:
proposal: Original proposal
author: Author
title: Title
valid_answers: Valid answers
add_answer: "Add answer"
video_url: External video
documents: Documents (1)
answers:
title: Answer
description: Description
answers:
new:
title: "New answer"
recounts:
index:
title: "Recounts"

View File

@@ -490,6 +490,7 @@ en:
verify_link: "verify your account"
cant_answer_incoming: "This poll has not yet started."
cant_answer_expired: "This poll has finished."
cant_answer_wrong_geozone: "This question is not available on your geozone."
more_info_title: "More information"
documents: Documents
zoom_plus: Expand image
@@ -497,14 +498,6 @@ en:
create_question: "Create question"
default_valid_answers: "Yes, No"
show:
not_logged_in: "You must %{signin} or %{signup} to participate."
signin: Sign in
signup: Sign up
cant_answer_verify_html: "You must %{verify_link} in order to answer."
verify_link: "verify your account"
cant_answer_incoming: "This poll has not yet started."
cant_answer_expired: "This poll has finished."
cant_answer_wrong_geozone: "This question is not available on your geozone."
vote_answer: "Vote %{answer}"
voted: "You have voted %{answer}"
proposal_notifications:

View File

@@ -8,6 +8,7 @@ en:
direct_message: "You message has been sent successfully."
poll: "Poll created successfully."
poll_booth: "Booth created successfully."
poll_question_answer: "Answer created successfully"
proposal: "Proposal created successfully."
proposal_notification: "Your message has been sent correctly."
spending_proposal: "Spending proposal created successfully. You can access it from %{activity}"

View File

@@ -207,6 +207,9 @@ es:
image:
title: Título
attachment: Archivo adjunto
poll/question_answer:
title: "Respuesta"
description: "Descripción"
errors:
models:
user:

View File

@@ -587,12 +587,22 @@ es:
new:
title: "Crear pregunta ciudadana"
poll_label: "Votación"
valid_answers_note: "Escribe las respuestas separadas por comas (,)"
show:
proposal: Propuesta ciudadana original
author: Autor
title: Título
valid_answers: Respuestas válidas
add_answer: "Añadir respuesta"
description: Descripción
video_url: Video externo
documents: Documentos (1)
preview: Ver en la web
answers:
title: Respuesta
description: Descripción
answers:
new:
title: "Nueva respuesta"
video_url: Video externo
documents: Documentos (1)
recounts:

View File

@@ -490,6 +490,7 @@ es:
verify_link: "verifica tu cuenta"
cant_answer_incoming: "Esta votación todavía no ha comenzado."
cant_answer_expired: "Esta votación ha terminado."
cant_answer_wrong_geozone: "Esta votación no está disponible en tu zona."
more_info_title: "Más información"
documents: Documentación
zoom_plus: Ampliar imagen
@@ -497,14 +498,6 @@ es:
create_question: "Crear pregunta para votación"
default_valid_answers: "Sí, No"
show:
not_logged_in: "Necesitas %{signin} o %{signup} para participar."
signin: iniciar sesión
signup: registrarte
cant_answer_verify_html: "Por favor %{verify_link} para poder responder."
verify_link: "verifica tu cuenta"
cant_answer_incoming: "Esta votación todavía no ha comenzado."
cant_answer_expired: "Esta votación ha terminado."
cant_answer_wrong_geozone: "Esta votación no está disponible en tu zona."
vote_answer: "Votar %{answer}"
voted: "Has votado %{answer}"
proposal_notifications:

View File

@@ -8,6 +8,7 @@ es:
direct_message: "Tu mensaje ha sido enviado correctamente."
poll: "Votación creada correctamente."
poll_booth: "Urna creada correctamente."
poll_question_answer: "Respuesta creada correctamente"
proposal: "Propuesta creada correctamente."
proposal_notification: "Tu message ha sido enviado correctamente."
spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}"

View File

@@ -147,6 +147,9 @@ fr:
name: Nom
locale: Langue
body: Contenu
poll/question_answer:
title: "Réponse"
description: "Description"
errors:
models:
user:

View File

@@ -377,14 +377,18 @@ fr:
new:
title: "Créer une question"
poll_label: "Vote"
valid_answers_note: "Saisir les réponses séparées par des virgules (,)"
show:
proposal: Proposition originale
author: Auteur
title: Titre
valid_answers: Réponses valides
add_answer: "Ajouter une réponse"
answer: "Réponse"
description: Description
preview: Voir l'aperçu
answers:
new:
title: "Nouvelle réponse"
recounts:
index:
title: "Dépouillements"

View File

@@ -300,7 +300,9 @@ Rails.application.routes.draw do
end
end
resources :questions
resources :questions do
resources :answers, only: [:new, :create], controller: 'questions/answers'
end
end
resources :verifications, controller: :verifications, only: :index do

View File

@@ -0,0 +1,9 @@
class CreatePollQuestionAnswers < ActiveRecord::Migration
def change
create_table :poll_question_answers do |t|
t.string :title
t.text :description
t.references :poll_question, index: true, foreign_key: true
end
end
end

View File

@@ -0,0 +1,5 @@
class RenamePollQuestionIdToQuestionId < ActiveRecord::Migration
def change
rename_column :poll_question_answers, :poll_question_id, :question_id
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171003223152) do
ActiveRecord::Schema.define(version: 20171004151553) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -668,6 +668,14 @@ ActiveRecord::Schema.define(version: 20171003223152) do
add_index "poll_partial_results", ["origin"], name: "index_poll_partial_results_on_origin", using: :btree
add_index "poll_partial_results", ["question_id"], name: "index_poll_partial_results_on_question_id", using: :btree
create_table "poll_question_answers", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "question_id"
end
add_index "poll_question_answers", ["question_id"], name: "index_poll_question_answers_on_question_id", using: :btree
create_table "poll_questions", force: :cascade do |t|
t.integer "proposal_id"
t.integer "poll_id"
@@ -1144,6 +1152,7 @@ ActiveRecord::Schema.define(version: 20171003223152) do
add_foreign_key "poll_partial_results", "poll_officer_assignments", column: "officer_assignment_id"
add_foreign_key "poll_partial_results", "poll_questions", column: "question_id"
add_foreign_key "poll_partial_results", "users", column: "author_id"
add_foreign_key "poll_question_answers", "poll_questions", column: "question_id"
add_foreign_key "poll_questions", "polls"
add_foreign_key "poll_questions", "proposals"
add_foreign_key "poll_questions", "users", column: "author_id"

View File

@@ -501,6 +501,12 @@ FactoryGirl.define do
valid_answers { Faker::Lorem.words(3).join(', ') }
end
factory :poll_question_answer, class: 'Poll::Question::Answer' do
association :question, factory: :poll_question
sequence(:title) { |n| "Question title #{n}" }
sequence(:description) { |n| "Question description #{n}" }
end
factory :poll_booth, class: 'Poll::Booth' do
sequence(:name) { |n| "Booth #{n}" }
sequence(:location) { |n| "Street #{n}" }

View File

@@ -25,7 +25,6 @@ feature 'Admin poll questions' do
expect(page).to have_content(question.title)
expect(page).to have_content(question.author.name)
expect(page).to have_content(question.valid_answers.join(" "))
end
scenario 'Create' do
@@ -61,7 +60,6 @@ feature 'Admin poll questions' do
expect(current_path).to eq(new_admin_question_path)
expect(page).to have_field('poll_question_title', with: proposal.title)
expect(page).to have_field('poll_question_valid_answers', with: "Yes, No")
select 'Proposals', from: 'poll_question_poll_id'

View File

@@ -0,0 +1,41 @@
require 'rails_helper'
feature 'Answers' do
background do
admin = create(:administrator)
login_as(admin.user)
end
scenario "Index" do
question = create(:poll_question)
answer1 = create(:poll_question_answer, question: question)
answer2 = create(:poll_question_answer, question: question)
visit admin_question_path(question)
expect(page).to have_css(".poll_question_answer", count: 2)
within("#poll_question_answer_#{answer1.id}") do
expect(page).to have_content answer1.title
expect(page).to have_content answer1.description
end
end
scenario "Create" do
question = create(:poll_question)
visit admin_question_path(question)
click_link "Add answer"
fill_in "poll_question_answer_title", with: "¿Would you like to reform Central Park?"
fill_in "poll_question_answer_description", with: "Adding more trees, creating a play area..."
click_button "Save"
expect(page).to have_content "Answer created successfully"
end
pending "Update"
pending "Destroy"
end

View File

@@ -73,7 +73,10 @@ feature 'Polls' do
end
scenario 'Non-logged in users' do
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
visit poll_path(poll)
expect(page).to have_content('Han Solo')
@@ -87,7 +90,11 @@ feature 'Polls' do
scenario 'Level 1 users' do
poll.update(geozone_restricted: true)
poll.geozones << geozone
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
login_as(create(:user, geozone: geozone))
visit poll_path(poll)
@@ -103,7 +110,11 @@ feature 'Polls' do
scenario 'Level 2 users in an incoming poll' do
incoming_poll = create(:poll, :incoming, geozone_restricted: true)
incoming_poll.geozones << geozone
create(:poll_question, poll: incoming_poll, valid_answers: 'Rey, Finn')
question = create(:poll_question, poll: incoming_poll)
answer1 = create(:poll_question_answer, question: question, title: 'Rey')
answer2 = create(:poll_question_answer, question: question, title: 'Finn')
login_as(create(:user, :level_two, geozone: geozone))
visit poll_path(incoming_poll)
@@ -119,7 +130,11 @@ feature 'Polls' do
scenario 'Level 2 users in an expired poll' do
expired_poll = create(:poll, :expired, geozone_restricted: true)
expired_poll.geozones << geozone
create(:poll_question, poll: expired_poll, valid_answers: 'Luke, Leia')
question = create(:poll_question, poll: expired_poll)
answer1 = create(:poll_question_answer, question: question, title: 'Luke')
answer2 = create(:poll_question_answer, question: question, title: 'Leia')
login_as(create(:user, :level_two, geozone: geozone))
visit poll_path(expired_poll)
@@ -135,7 +150,11 @@ feature 'Polls' do
scenario 'Level 2 users in a poll with questions for a geozone which is not theirs' do
poll.update(geozone_restricted: true)
poll.geozones << create(:geozone)
create(:poll_question, poll: poll, valid_answers: 'Vader, Palpatine')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Vader')
answer2 = create(:poll_question_answer, question: question, title: 'Palpatine')
login_as(create(:user, :level_two))
visit poll_path(poll)
@@ -149,7 +168,11 @@ feature 'Polls' do
scenario 'Level 2 users reading a same-geozone poll' do
poll.update(geozone_restricted: true)
poll.geozones << geozone
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
login_as(create(:user, :level_two, geozone: geozone))
visit poll_path(poll)
@@ -158,7 +181,10 @@ feature 'Polls' do
end
scenario 'Level 2 users reading a all-geozones poll' do
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
login_as(create(:user, :level_two))
visit poll_path(poll)
@@ -167,7 +193,9 @@ feature 'Polls' do
end
scenario 'Level 2 users who have already answered' do
question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
user = create(:user, :level_two)
create(:poll_answer, question: question, author: user, answer: 'Chewbacca')
@@ -182,7 +210,11 @@ feature 'Polls' do
scenario 'Level 2 users answering', :js do
poll.update(geozone_restricted: true)
poll.geozones << geozone
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
user = create(:user, :level_two, geozone: geozone)
login_as user
@@ -197,7 +229,11 @@ feature 'Polls' do
scenario 'Level 2 users changing answer', :js do
poll.update(geozone_restricted: true)
poll.geozones << geozone
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Han Solo')
answer2 = create(:poll_question_answer, question: question, title: 'Chewbacca')
user = create(:user, :level_two, geozone: geozone)
login_as user

View File

@@ -16,7 +16,12 @@ feature "Voter" do
end
scenario "Voting via web", :js do
question = create(:poll_question, poll: poll, valid_answers: 'Yes, No')
poll = create(:poll)
question = create(:poll_question, poll: poll)
answer1 = create(:poll_question_answer, question: question, title: 'Yes')
answer2 = create(:poll_question_answer, question: question, title: 'No')
user = create(:user, :level_two)
login_as user
@@ -50,7 +55,12 @@ feature "Voter" do
context "Trying to vote the same poll in booth and web" do
let(:question) { create(:poll_question, poll: poll, valid_answers: 'Yes, No') }
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, poll: poll) }
let!(:answer1) { create(:poll_question_answer, question: question, title: 'Yes') }
let!(:answer2) { create(:poll_question_answer, question: question, title: 'No') }
let!(:user) { create(:user, :in_census) }
scenario "Trying to vote in web and then in booth", :js do

View File

@@ -26,6 +26,7 @@ describe Poll::Answer do
end
it "should be valid for answers included in the Poll::Question's list" do
skip "review when removing valid_answers"
question = create(:poll_question, valid_answers: 'One, Two, Three')
expect(build(:poll_answer, question: question, answer: 'One')).to be_valid
expect(build(:poll_answer, question: question, answer: 'Two')).to be_valid