Merge pull request #309 from AyuntamientoMadrid/limit-votes

Limit votes
This commit is contained in:
Raimond Garcia
2015-09-01 19:50:16 +02:00
21 changed files with 290 additions and 23 deletions

View File

@@ -2,9 +2,11 @@ App.Votes =
hoverize: (votes) ->
$(votes).hover ->
$("div.not-logged", votes).show()
$("div.anonymous-votes", votes).show();
$("div.not-logged", votes).show();
, ->
$("div.not-logged", votes).hide()
$("div.anonymous-votes", votes).hide();
$("div.not-logged", votes).hide();
initialize: ->
App.Votes.hoverize votes for votes in $("div.votes")

View File

@@ -134,6 +134,31 @@
text-decoration: underline;
}
}
.anonymous-votes {
background: $warning-bg;
color: $warning-color;
height: 100%;
left: 0;
line-height: $line-height;
padding-top: rem-calc(12);
position: absolute;
text-align: center;
top: 0;
width: 100%;
p {
color: $warning-color;
margin: 0 rem-calc(12);
text-align: left;
}
a {
color: $warning-color;
font-weight: bold;
text-decoration: underline;
}
}
}
// 02. Index
@@ -354,6 +379,10 @@
line-height: $line-height;
padding-top: rem-calc(36);
}
.anonymous-votes {
padding-top: rem-calc(24);
}
}
}

View File

@@ -773,6 +773,12 @@ form {
background-color: $warning-bg;
border-color: $warning-border;
color: $warning-color;
a {
color: $warning-color;
font-weight: bold;
text-decoration: underline;
}
}
&.alert {

View File

@@ -53,7 +53,7 @@ class DebatesController < ApplicationController
end
def vote
@debate.vote_by(voter: current_user, vote: params[:value])
@debate.register_vote(current_user, params[:value])
set_debate_votes(@debate)
end

View File

@@ -31,7 +31,6 @@ class Debate < ActiveRecord::Base
scope :sort_by_total_votes, -> { reorder(cached_votes_total: :desc) }
scope :sort_by_likes , -> { reorder(cached_votes_up: :desc) }
scope :sort_by_created_at, -> { reorder(created_at: :desc) }
# Ahoy setup
visitable # Ahoy will automatically assign visit_id on create
@@ -56,6 +55,10 @@ class Debate < ActiveRecord::Base
cached_votes_total
end
def total_anonymous_votes
cached_anonymous_votes_total
end
def editable?
total_votes == 0
end
@@ -64,6 +67,24 @@ class Debate < ActiveRecord::Base
editable? && author == user
end
def register_vote(user, vote_value)
if votable_by?(user)
Debate.increment_counter(:cached_anonymous_votes_total, id) if (user.unverified? && !user.voted_for?(self))
vote_by(voter: user, vote: vote_value)
end
end
def votable_by?(user)
!user.unverified? ||
anonymous_votes_ratio < Setting.value_for('max_ratio_anon_votes_on_debates').to_i ||
user.voted_for?(self)
end
def anonymous_votes_ratio
return 0 if cached_votes_total == 0
(cached_anonymous_votes_total.to_f / cached_votes_total) * 100
end
def description
super.try :html_safe
end

View File

@@ -1,4 +1,7 @@
class Setting < ActiveRecord::Base
validates :key, presence: true, uniqueness: true
default_scope { order(key: :desc) }
def self.value_for(key)

View File

@@ -1 +1 @@
$("#search-result").html("<div class=\"panel column\"><%= j render 'moderator', moderator: @moderator %></div>");
$("#search-result").html("<div class=\"panel column\"><%= j render 'moderator', moderator: @moderator %></div>");

View File

@@ -21,7 +21,14 @@
<%= t("debates.debate.votes", count: debate.total_votes) %>
</span>
<% unless user_signed_in? %>
<% if user_signed_in? && current_user.unverified? %>
<div class="anonymous-votes" style='display:none'>
<p>
<%= t("votes.anonymous",
verify_account: link_to(t("votes.verify_account"), new_residence_path )).html_safe %>
</p>
</div>
<% elsif !user_signed_in? %>
<div class="not-logged" style='display:none'>
<%= t("votes.unauthenticated",
signin: link_to(t("votes.signin"), new_user_session_path),

View File

@@ -144,6 +144,8 @@ en:
unauthenticated: "You need to %{signin} or %{signup} before continuing."
signin: sign in
signup: sign up
anonymous: "This debate already has too much anonymous votes, %{verify_account} to vote."
verify_account: verify your account
account:
show:
title: "My account"

View File

@@ -144,6 +144,8 @@ es:
unauthenticated: "Necesitas %{signin} o %{signup} para continuar."
signin: iniciar sesión
signup: registrarte
anonymous: "Este debate ya tiene demasiados votos anónimos, para poder votar %{verify_account}."
verify_account: verifica tu cuenta
account:
show:
title: "Mi cuenta"

View File

@@ -0,0 +1,5 @@
class AddCachedAnonymousVotesTotalToDebate < ActiveRecord::Migration
def change
add_column :debates, :cached_anonymous_votes_total, :integer, default: 0
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: 20150828085718) do
ActiveRecord::Schema.define(version: 20150830212600) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -67,8 +67,8 @@ ActiveRecord::Schema.define(version: 20150828085718) do
t.integer "rgt"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "hidden_at"
t.integer "children_count", default: 0
t.datetime "hidden_at"
t.integer "flags_count", default: 0
t.datetime "ignored_flag_at"
t.integer "moderator_id"
@@ -87,20 +87,21 @@ ActiveRecord::Schema.define(version: 20150828085718) do
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
create_table "debates", force: :cascade do |t|
t.string "title", limit: 80
t.string "title", limit: 80
t.text "description"
t.integer "author_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "visit_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "hidden_at"
t.integer "flags_count", default: 0
t.integer "cached_votes_total", default: 0
t.integer "cached_votes_up", default: 0
t.integer "cached_votes_down", default: 0
t.string "visit_id"
t.integer "flags_count", default: 0
t.datetime "ignored_flag_at"
t.integer "comments_count", default: 0
t.integer "cached_votes_total", default: 0
t.integer "cached_votes_up", default: 0
t.integer "cached_votes_down", default: 0
t.integer "comments_count", default: 0
t.datetime "confirmed_hide_at"
t.integer "cached_anonymous_votes_total", default: 0
end
add_index "debates", ["cached_votes_down"], name: "index_debates_on_cached_votes_down", using: :btree
@@ -199,12 +200,13 @@ ActiveRecord::Schema.define(version: 20150828085718) do
t.string "unconfirmed_email"
t.boolean "email_on_debate_comment", default: false
t.boolean "email_on_comment_reply", default: false
t.string "phone_number", limit: 30
t.string "official_position"
t.integer "official_level", default: 0
t.datetime "hidden_at"
t.string "sms_confirmation_code"
t.string "phone_number", limit: 30
t.string "username"
t.datetime "confirmed_hide_at"
t.string "sms_confirmation_code"
t.string "document_number"
t.string "document_type"
t.datetime "residence_verified_at"
@@ -216,7 +218,6 @@ ActiveRecord::Schema.define(version: 20150828085718) do
t.string "unconfirmed_phone"
t.string "confirmed_phone"
t.datetime "letter_requested_at"
t.datetime "confirmed_hide_at"
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree

View File

@@ -5,4 +5,7 @@ Setting.create(key: 'official_level_1_name', value: 'Empleados públicos')
Setting.create(key: 'official_level_2_name', value: 'Organización Municipal')
Setting.create(key: 'official_level_3_name', value: 'Directores generales')
Setting.create(key: 'official_level_4_name', value: 'Concejales')
Setting.create(key: 'official_level_5_name', value: 'Alcaldesa')
Setting.create(key: 'official_level_5_name', value: 'Alcaldesa')
# Max percentage of allowed anonymous votes on a debate
Setting.create(key: 'max_ratio_anon_votes_on_debates', value: '50')

View File

@@ -16,5 +16,9 @@ module Verification
verified_at.present?
end
def unverified?
!level_two_verified? && !level_three_verified?
end
end

View File

@@ -12,7 +12,6 @@ describe DebatesController do
end
describe 'POST create' do
it 'should create an ahoy event' do
sign_in create(:user)
@@ -22,4 +21,26 @@ describe DebatesController do
expect(Ahoy::Event.last.properties['debate_id']).to eq Debate.last.id
end
end
describe "Vote with too many anonymous votes" do
it 'should allow vote if user is allowed' do
Setting.find_by(key: "max_ratio_anon_votes_on_debates").update(value: 100)
debate = create(:debate)
sign_in create(:user)
expect do
xhr :post, :vote, id: debate.id, value: 'yes'
end.to change { debate.reload.votes_for.size }.by(1)
end
it 'should not allow vote if user is not allowed' do
Setting.find_by(key: "max_ratio_anon_votes_on_debates").update(value: 0)
debate = create(:debate)
sign_in create(:user)
expect do
xhr :post, :vote, id: debate.id, value: 'yes'
end.to_not change { debate.reload.votes_for.size }
end
end
end

View File

@@ -70,6 +70,7 @@ feature 'Admin::Organizations' do
organization = create(:organization)
visit admin_organizations_path
expect(current_path).to eq(admin_organizations_path)
expect(page).to have_link('Verify')
expect(page).to have_link('Reject')
@@ -84,6 +85,7 @@ feature 'Admin::Organizations' do
organization = create(:organization, :verified)
visit admin_organizations_path
expect(current_path).to eq(admin_organizations_path)
expect(page).to have_content ('Verified')
expect(page).to_not have_link('Verify')
expect(page).to have_link('Reject')
@@ -99,6 +101,7 @@ feature 'Admin::Organizations' do
organization = create(:organization, :rejected)
visit admin_organizations_path
expect(current_path).to eq(admin_organizations_path)
expect(page).to have_link('Verify')
expect(page).to_not have_link('Reject', exact: true)

View File

@@ -5,7 +5,7 @@ feature 'Votes' do
feature 'Debates' do
background do
@manuela = create(:user)
@manuela = create(:user, verified_at: Time.now)
@pablo = create(:user)
@debate = create(:debate)

View File

@@ -78,6 +78,96 @@ describe Debate do
end
end
describe "#register_vote" do
let(:debate) { create(:debate) }
before(:each) do
Setting.find_by(key: "max_ratio_anon_votes_on_debates").update(value: 50)
end
describe "from level two verified users" do
it "should register vote" do
user = create(:user, residence_verified_at: Time.now, confirmed_phone: "666333111")
expect {debate.register_vote(user, 'yes')}.to change{debate.reload.votes_for.size}.by(1)
end
it "should not increase anonymous votes counter " do
user = create(:user, residence_verified_at: Time.now, confirmed_phone: "666333111")
expect {debate.register_vote(user, 'yes')}.to_not change{debate.reload.cached_anonymous_votes_total}
end
end
describe "from level three verified users" do
it "should register vote" do
user = create(:user, verified_at: Time.now)
expect {debate.register_vote(user, 'yes')}.to change{debate.reload.votes_for.size}.by(1)
end
it "should not increase anonymous votes counter " do
user = create(:user, verified_at: Time.now)
expect {debate.register_vote(user, 'yes')}.to_not change{debate.reload.cached_anonymous_votes_total}
end
end
describe "from anonymous users when anonymous votes are allowed" do
before(:each) {debate.update(cached_anonymous_votes_total: 42, cached_votes_total: 100)}
it "should register vote " do
user = create(:user)
expect {debate.register_vote(user, 'yes')}.to change {debate.reload.votes_for.size}.by(1)
end
it "should increase anonymous votes counter " do
user = create(:user)
expect {debate.register_vote(user, 'yes')}.to change {debate.reload.cached_anonymous_votes_total}.by(1)
end
end
describe "from anonymous users when there are too many anonymous votes" do
before(:each) {debate.update(cached_anonymous_votes_total: 52, cached_votes_total: 100)}
it "should not register vote " do
user = create(:user)
expect {debate.register_vote(user, 'yes')}.to_not change {debate.reload.votes_for.size}
end
it "should not increase anonymous votes counter " do
user = create(:user)
expect {debate.register_vote(user, 'yes')}.to_not change {debate.reload.cached_anonymous_votes_total}
end
end
end
describe "#votable_by?" do
let(:debate) { create(:debate) }
before(:each) do
Setting.find_by(key: "max_ratio_anon_votes_on_debates").update(value: 50)
end
it "should be true for level two verified users" do
user = create(:user, residence_verified_at: Time.now, confirmed_phone: "666333111")
expect(debate.votable_by?(user)).to be true
end
it "should be true for level three verified users" do
user = create(:user, verified_at: Time.now)
expect(debate.votable_by?(user)).to be true
end
it "should be true for anonymous users if allowed anonymous votes" do
debate.update(cached_anonymous_votes_total: 42, cached_votes_total: 100)
user = create(:user)
expect(debate.votable_by?(user)).to be true
end
it "should be false for anonymous users if too many anonymous votes" do
debate.update(cached_anonymous_votes_total: 52, cached_votes_total: 100)
user = create(:user)
expect(debate.votable_by?(user)).to be false
end
end
describe "#search" do
let!(:economy) { create(:debate, tag_list: "Economy") }
let!(:health) { create(:debate, tag_list: "Health") }
@@ -102,4 +192,11 @@ describe Debate do
end
end
describe '#anonymous_votes_ratio' do
it "returns the percentage of anonymous votes of the total votes" do
debate = create(:debate, cached_anonymous_votes_total: 25, cached_votes_total: 100)
expect(debate.anonymous_votes_ratio).to eq(25.0)
end
end
end

View File

@@ -0,0 +1,15 @@
require 'rails_helper'
describe Setting do
it "should be accesible by key" do
create(:setting, key: "Important Setting", value: "42")
expect(Setting.value_for("Important Setting")).to eq("42")
end
it "should be nil if key does not exist" do
expect(Setting.value_for("Undefined key")).to be_nil
end
end

View File

@@ -254,4 +254,49 @@ describe User do
end
end
describe "verification levels" do
it "residence_verified? is true only if residence_verified_at" do
user = create(:user, residence_verified_at: Time.now)
expect(user.residence_verified?).to eq(true)
user = create(:user, residence_verified_at: nil)
expect(user.residence_verified?).to eq(false)
end
it "sms_verified? is true only if confirmed_phone" do
user = create(:user, confirmed_phone: "123456789")
expect(user.sms_verified?).to eq(true)
user = create(:user, confirmed_phone: nil)
expect(user.sms_verified?).to eq(false)
end
it "level_two_verified? is true only if residence_verified_at and confirmed_phone" do
user = create(:user, confirmed_phone: "123456789", residence_verified_at: Time.now)
expect(user.level_two_verified?).to eq(true)
user = create(:user, confirmed_phone: nil, residence_verified_at: Time.now)
expect(user.level_two_verified?).to eq(false)
user = create(:user, confirmed_phone: "123456789", residence_verified_at: nil)
expect(user.level_two_verified?).to eq(false)
end
it "level_three_verified? is true only if verified_at" do
user = create(:user, verified_at: Time.now)
expect(user.level_three_verified?).to eq(true)
user = create(:user, verified_at: nil)
expect(user.level_three_verified?).to eq(false)
end
it "unverified? is true only if not level_three_verified and not level_two_verified" do
user = create(:user, verified_at: nil, confirmed_phone: nil)
expect(user.unverified?).to eq(true)
user = create(:user, verified_at: Time.now, confirmed_phone: "123456789", residence_verified_at: Time.now)
expect(user.unverified?).to eq(false)
end
end
end

View File

@@ -21,6 +21,7 @@ RSpec.configure do |config|
config.before(:each) do |example|
DatabaseCleaner.strategy = example.metadata[:js] ? :truncation : :transaction
DatabaseCleaner.start
load "#{Rails.root}/db/seeds.rb"
end
config.after(:each) do