Merge pull request #309 from AyuntamientoMadrid/limit-votes
Limit votes
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
class Setting < ActiveRecord::Base
|
||||
|
||||
validates :key, presence: true, uniqueness: true
|
||||
|
||||
default_scope { order(key: :desc) }
|
||||
|
||||
def self.value_for(key)
|
||||
|
||||
@@ -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>");
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddCachedAnonymousVotesTotalToDebate < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :debates, :cached_anonymous_votes_total, :integer, default: 0
|
||||
end
|
||||
end
|
||||
29
db/schema.rb
29
db/schema.rb
@@ -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
|
||||
|
||||
@@ -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')
|
||||
@@ -16,5 +16,9 @@ module Verification
|
||||
verified_at.present?
|
||||
end
|
||||
|
||||
def unverified?
|
||||
!level_two_verified? && !level_three_verified?
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
15
spec/models/setting_spec.rb
Normal file
15
spec/models/setting_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user