diff --git a/app/assets/javascripts/votes.js.coffee b/app/assets/javascripts/votes.js.coffee
index 5005ad5b8..da3d55664 100644
--- a/app/assets/javascripts/votes.js.coffee
+++ b/app/assets/javascripts/votes.js.coffee
@@ -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")
diff --git a/app/assets/stylesheets/debates.scss b/app/assets/stylesheets/debates.scss
index 1fded1efa..a480f4870 100644
--- a/app/assets/stylesheets/debates.scss
+++ b/app/assets/stylesheets/debates.scss
@@ -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);
+ }
}
}
diff --git a/app/assets/stylesheets/participacion.scss b/app/assets/stylesheets/participacion.scss
index 6c498adcc..0b027001b 100644
--- a/app/assets/stylesheets/participacion.scss
+++ b/app/assets/stylesheets/participacion.scss
@@ -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 {
diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb
index 87df13d37..451d8e893 100644
--- a/app/controllers/debates_controller.rb
+++ b/app/controllers/debates_controller.rb
@@ -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
diff --git a/app/models/debate.rb b/app/models/debate.rb
index fdf939b2f..e088d8141 100644
--- a/app/models/debate.rb
+++ b/app/models/debate.rb
@@ -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
diff --git a/app/models/setting.rb b/app/models/setting.rb
index 1a52ebe60..8e1504bf3 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -1,4 +1,7 @@
class Setting < ActiveRecord::Base
+
+ validates :key, presence: true, uniqueness: true
+
default_scope { order(key: :desc) }
def self.value_for(key)
diff --git a/app/views/admin/moderators/search.js.erb b/app/views/admin/moderators/search.js.erb
index 5b8a61207..a6ac48206 100644
--- a/app/views/admin/moderators/search.js.erb
+++ b/app/views/admin/moderators/search.js.erb
@@ -1 +1 @@
-$("#search-result").html("
<%= j render 'moderator', moderator: @moderator %>
");
+$("#search-result").html("<%= j render 'moderator', moderator: @moderator %>
");
diff --git a/app/views/debates/_votes.html.erb b/app/views/debates/_votes.html.erb
index 462353ff9..5ce64d784 100644
--- a/app/views/debates/_votes.html.erb
+++ b/app/views/debates/_votes.html.erb
@@ -21,7 +21,14 @@
<%= t("debates.debate.votes", count: debate.total_votes) %>
- <% unless user_signed_in? %>
+ <% if user_signed_in? && current_user.unverified? %>
+
+
+ <%= t("votes.anonymous",
+ verify_account: link_to(t("votes.verify_account"), new_residence_path )).html_safe %>
+
+
+ <% elsif !user_signed_in? %>
<%= t("votes.unauthenticated",
signin: link_to(t("votes.signin"), new_user_session_path),
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 2c3765f54..fff118958 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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"
diff --git a/config/locales/es.yml b/config/locales/es.yml
index f4eece354..7c19f4ef0 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -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"
diff --git a/db/migrate/20150830212600_add_cached_anonymous_votes_total_to_debate.rb b/db/migrate/20150830212600_add_cached_anonymous_votes_total_to_debate.rb
new file mode 100644
index 000000000..196d5b517
--- /dev/null
+++ b/db/migrate/20150830212600_add_cached_anonymous_votes_total_to_debate.rb
@@ -0,0 +1,5 @@
+class AddCachedAnonymousVotesTotalToDebate < ActiveRecord::Migration
+ def change
+ add_column :debates, :cached_anonymous_votes_total, :integer, default: 0
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fd22d3092..883d2a7e1 100644
--- a/db/schema.rb
+++ b/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
diff --git a/db/seeds.rb b/db/seeds.rb
index d39b15dba..6158a2a8d 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -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')
\ No newline at end of file
+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')
\ No newline at end of file
diff --git a/lib/verification.rb b/lib/verification.rb
index 2e784d65c..1e2f7be81 100644
--- a/lib/verification.rb
+++ b/lib/verification.rb
@@ -16,5 +16,9 @@ module Verification
verified_at.present?
end
+ def unverified?
+ !level_two_verified? && !level_three_verified?
+ end
+
end
\ No newline at end of file
diff --git a/spec/controllers/debates_controller_spec.rb b/spec/controllers/debates_controller_spec.rb
index 9ab4f7544..1628c9bcf 100644
--- a/spec/controllers/debates_controller_spec.rb
+++ b/spec/controllers/debates_controller_spec.rb
@@ -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
diff --git a/spec/features/admin/organizations_spec.rb b/spec/features/admin/organizations_spec.rb
index adf014e67..f43c5d4dc 100644
--- a/spec/features/admin/organizations_spec.rb
+++ b/spec/features/admin/organizations_spec.rb
@@ -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)
diff --git a/spec/features/votes_spec.rb b/spec/features/votes_spec.rb
index 548c7e308..ea9761bde 100644
--- a/spec/features/votes_spec.rb
+++ b/spec/features/votes_spec.rb
@@ -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)
diff --git a/spec/models/debate_spec.rb b/spec/models/debate_spec.rb
index 5e1ed76c8..96e3b3c96 100644
--- a/spec/models/debate_spec.rb
+++ b/spec/models/debate_spec.rb
@@ -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
diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb
new file mode 100644
index 000000000..82ecab088
--- /dev/null
+++ b/spec/models/setting_spec.rb
@@ -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
\ No newline at end of file
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index d00ed71ca..87d0994bb 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -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
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index bf1a65776..cb6b56d42 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -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