From ed1ec1c55380c80cced933b294ada84eadae8ae2 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Tue, 29 Mar 2016 14:53:46 +0200 Subject: [PATCH] adds votes to spending proposals --- app/controllers/application_controller.rb | 4 ++ .../spending_proposals_controller.rb | 12 ++++ app/models/abilities/common.rb | 1 + app/models/spending_proposal.rb | 15 +++++ app/models/user.rb | 5 ++ app/views/_votes.html.erb/_form.html.erb | 51 +++++++++++++++ app/views/_votes.html.erb/index.html.erb | 16 +++++ app/views/_votes.html.erb/new.html.erb | 27 ++++++++ app/views/_votes.html.erb/show.html.erb | 34 ++++++++++ app/views/spending_proposals/_votes.html.erb | 47 ++++++++++++++ app/views/spending_proposals/show.html.erb | 5 ++ app/views/spending_proposals/vote.js.erb | 1 + config/initializers/vote_extensions.rb | 4 ++ config/locales/en.yml | 32 ++++++++++ config/routes.rb | 6 +- ...5418_add_votes_up_to_spending_proposals.rb | 5 ++ db/schema.rb | 3 +- spec/features/votes_spec.rb | 62 ++++++++++++++++++- 18 files changed, 327 insertions(+), 3 deletions(-) create mode 100644 app/views/_votes.html.erb/_form.html.erb create mode 100644 app/views/_votes.html.erb/index.html.erb create mode 100644 app/views/_votes.html.erb/new.html.erb create mode 100644 app/views/_votes.html.erb/show.html.erb create mode 100644 app/views/spending_proposals/_votes.html.erb create mode 100644 app/views/spending_proposals/vote.js.erb create mode 100644 db/migrate/20160329115418_add_votes_up_to_spending_proposals.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 280dd0c4d..640adf299 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -77,6 +77,10 @@ class ApplicationController < ActionController::Base @proposal_votes = current_user ? current_user.proposal_votes(proposals) : {} end + def set_spending_proposal_votes(spending_proposals) + @spending_proposal_votes = current_user ? current_user.spending_proposal_votes(spending_proposals) : {} + end + def set_comment_flags(comments) @comment_flags = current_user ? current_user.comment_flags(comments) : {} end diff --git a/app/controllers/spending_proposals_controller.rb b/app/controllers/spending_proposals_controller.rb index 76f230575..20add5c24 100644 --- a/app/controllers/spending_proposals_controller.rb +++ b/app/controllers/spending_proposals_controller.rb @@ -9,6 +9,8 @@ class SpendingProposalsController < ApplicationController feature_flag :spending_proposals + respond_to :html, :js + def index end @@ -16,6 +18,10 @@ class SpendingProposalsController < ApplicationController @spending_proposal = SpendingProposal.new end + def show + set_spending_proposal_votes(@spending_proposal) + end + def create @spending_proposal = SpendingProposal.new(spending_proposal_params) @spending_proposal.author = current_user @@ -34,6 +40,12 @@ class SpendingProposalsController < ApplicationController redirect_to user_path(current_user, filter: 'spending_proposals'), notice: t('flash.actions.destroy.spending_proposal') end + def vote + @spending_proposal.register_vote(current_user, 'yes') + set_spending_proposal_votes(@spending_proposal) + end + + private def spending_proposal_params diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 92eacea52..2438feadb 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -43,6 +43,7 @@ module Abilities if user.level_two_or_three_verified? can :vote, Proposal can :vote_featured, Proposal + can :vote, SpendingProposal can :create, SpendingProposal can :destroy, SpendingProposal, author_id: user.id end diff --git a/app/models/spending_proposal.rb b/app/models/spending_proposal.rb index a084a800e..561e6ab52 100644 --- a/app/models/spending_proposal.rb +++ b/app/models/spending_proposal.rb @@ -4,6 +4,7 @@ class SpendingProposal < ActiveRecord::Base include Taggable apply_simple_captcha + acts_as_votable belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :geozone @@ -80,6 +81,10 @@ class SpendingProposal < ActiveRecord::Base valuation_finished end + def total_votes + cached_votes_up + end + def code "#{id}" + (administrator.present? ? "-A#{administrator.id}" : "") end @@ -89,4 +94,14 @@ class SpendingProposal < ActiveRecord::Base update(unfeasible_email_sent_at: Time.now) end + def votable_by?(user) + user && user.level_two_or_three_verified? + end + + def register_vote(user, vote_value) + if votable_by?(user) + vote_by(voter: user, vote: vote_value) + end + end + end diff --git a/app/models/user.rb b/app/models/user.rb index 3b58fb607..bc4b0f120 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -83,6 +83,11 @@ class User < ActiveRecord::Base voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } end + def spending_proposal_votes(spending_proposals) + voted = votes.for_spending_proposals(spending_proposals) + voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } + end + def comment_flags(comments) comment_flags = flags.for_comments(comments) comment_flags.each_with_object({}){ |f, h| h[f.flaggable_id] = true } diff --git a/app/views/_votes.html.erb/_form.html.erb b/app/views/_votes.html.erb/_form.html.erb new file mode 100644 index 000000000..0d6ef0402 --- /dev/null +++ b/app/views/_votes.html.erb/_form.html.erb @@ -0,0 +1,51 @@ +<%= form_for(@spending_proposal, url: form_url) do |f| %> + <%= render 'shared/errors', resource: @spending_proposal %> + +
+
+ <%= f.label :title, t("spending_proposals.form.title") %> + <%= f.text_field :title, maxlength: SpendingProposal.title_max_length, placeholder: t("spending_proposals.form.title"), label: false %> +
+ +
+ <%= f.label :description, t("spending_proposals.form.description") %> + <%= f.cktext_area :description, maxlength: SpendingProposal.description_max_length, ckeditor: { language: I18n.locale }, label: false %> +
+ +
+ <%= f.label :external_url, t("spending_proposals.form.external_url") %> + <%= f.text_field :external_url, placeholder: t("spending_proposals.form.external_url"), label: false %> +
+ +
+ <%= f.label :geozone_id, t("spending_proposals.form.geozone") %> + <%= f.select :geozone_id, geozone_select_options, {include_blank: t("geozones.none"), label: false} %> +
+ +
+ <%= f.label :association_name, t("spending_proposals.form.association_name_label") %> + <%= f.text_field :association_name, placeholder: t("spending_proposals.form.association_name"), label: false %> +
+ +
+ <% if @spending_proposal.new_record? %> + <%= f.label :terms_of_service do %> + <%= f.check_box :terms_of_service, title: t('form.accept_terms_title'), label: false %> + + <%= t("form.accept_terms", + policy: link_to(t("form.policy"), "/privacy", target: "blank"), + conditions: link_to(t("form.conditions"), "/conditions", target: "blank")).html_safe %> + + <% end %> + <% end %> +
+ +
+ <%= f.simple_captcha input_html: { required: false } %> +
+ +
+ <%= f.submit(class: "button", value: t("spending_proposals.form.submit_buttons.#{action_name}")) %> +
+
+<% end %> \ No newline at end of file diff --git a/app/views/_votes.html.erb/index.html.erb b/app/views/_votes.html.erb/index.html.erb new file mode 100644 index 000000000..00a1b6442 --- /dev/null +++ b/app/views/_votes.html.erb/index.html.erb @@ -0,0 +1,16 @@ +<% provide :title do %><%= t('spending_proposals.index.title') %><% end %> +
+
+
+

<%= t('spending_proposals.index.title') %>

+ +

<%= t('spending_proposals.index.text_html') %>

+ + <% if can? :create, SpendingProposal %> + <%= link_to t('spending_proposals.index.create_link'), new_spending_proposal_path, class: 'button' %> + <% else %> +

<%= t('spending_proposals.index.verified_only', verify_account: link_to(t('spending_proposals.index.verify_account'), verification_path)).html_safe %>

+ <% end %> +
+
+
\ No newline at end of file diff --git a/app/views/_votes.html.erb/new.html.erb b/app/views/_votes.html.erb/new.html.erb new file mode 100644 index 000000000..a8d35338f --- /dev/null +++ b/app/views/_votes.html.erb/new.html.erb @@ -0,0 +1,27 @@ +
+ +
+ <%= link_to spending_proposals_path, class: "back" do %> + + <%= t("spending_proposals.new.back_link") %> + <% end %> +

<%= t("spending_proposals.new.start_new") %>

+
+ <%= link_to "/spending_proposals_info", title: t('shared.target_blank_html'), target: "_blank" do %> + <%= t("spending_proposals.new.more_info")%> + <% end %> +
+ <%= render "spending_proposals/form", form_url: spending_proposals_url %> +
+ +
+ +

<%= t("spending_proposals.new.recommendations_title") %>

+
    +
  • <%= t("spending_proposals.new.recommendation_one") %>
  • +
  • <%= t("spending_proposals.new.recommendation_two") %>
  • +
  • <%= t("spending_proposals.new.recommendation_three") %>
  • +
+
+ +
\ No newline at end of file diff --git a/app/views/_votes.html.erb/show.html.erb b/app/views/_votes.html.erb/show.html.erb new file mode 100644 index 000000000..1aaa3d805 --- /dev/null +++ b/app/views/_votes.html.erb/show.html.erb @@ -0,0 +1,34 @@ +<% provide :title do %><%= @spending_proposal.title %><% end %> + +
+
+
+ +

<%= @spending_proposal.title %>

+ +
+ <%= render '/shared/author_info', resource: @spending_proposal %> + +  •  + <%= l @spending_proposal.created_at.to_date %> +  •  + <%= geozone_name(@spending_proposal) %> +
+ + <%= safe_html_with_links @spending_proposal.description.html_safe %> + + <% if @spending_proposal.external_url.present? %> + + <% end %> + +
+ +
+ <%= render 'votes', + { spending_proposal: @spending_proposal, vote_url: vote_spending_proposal_path(@spending_proposal, value: 'yes') } %> +
+ +
+
\ No newline at end of file diff --git a/app/views/spending_proposals/_votes.html.erb b/app/views/spending_proposals/_votes.html.erb new file mode 100644 index 000000000..652df8954 --- /dev/null +++ b/app/views/spending_proposals/_votes.html.erb @@ -0,0 +1,47 @@ +
+ + + <%= t("spending_proposals.spending_proposal.supports", count: spending_proposal.total_votes) %>  + + +
+ <% if voted_for?(@spending_proposal_votes, spending_proposal) %> +
+ <%= t("spending_proposals.spending_proposal.already_supported") %> +
+ <% else %> + <%= link_to vote_url, + class: "button button-support small expanded", + title: t('spending_proposals.spending_proposal.support_title'), method: "post", remote: true do %> + <%= t("spending_proposals.spending_proposal.support") %> + <% end %> + <% end %> +
+ + <% if user_signed_in? && current_user.organization? %> + + <% elsif user_signed_in? && !spending_proposal.votable_by?(current_user)%> + + <% elsif !user_signed_in? %> + + <% end %> + + <% if voted_for?(@spending_proposal_votes, spending_proposal) && setting['twitter_handle'] %> + + <% end %> +
diff --git a/app/views/spending_proposals/show.html.erb b/app/views/spending_proposals/show.html.erb index 987118a6a..1aaa3d805 100644 --- a/app/views/spending_proposals/show.html.erb +++ b/app/views/spending_proposals/show.html.erb @@ -25,5 +25,10 @@ +
+ <%= render 'votes', + { spending_proposal: @spending_proposal, vote_url: vote_spending_proposal_path(@spending_proposal, value: 'yes') } %> +
+ \ No newline at end of file diff --git a/app/views/spending_proposals/vote.js.erb b/app/views/spending_proposals/vote.js.erb new file mode 100644 index 000000000..4fa350aac --- /dev/null +++ b/app/views/spending_proposals/vote.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@spending_proposal) %>_votes").html('<%= j render("spending_proposals/votes", spending_proposal: @spending_proposal) %>'); \ No newline at end of file diff --git a/config/initializers/vote_extensions.rb b/config/initializers/vote_extensions.rb index 50fb60c9f..345cb8f01 100644 --- a/config/initializers/vote_extensions.rb +++ b/config/initializers/vote_extensions.rb @@ -7,6 +7,10 @@ ActsAsVotable::Vote.class_eval do where(votable_type: 'Proposal', votable_id: proposals) end + def self.for_spending_proposals(spending_proposals) + where(votable_type: 'SpendingProposal', votable_id: spending_proposals) + end + def value vote_flag end diff --git a/config/locales/en.yml b/config/locales/en.yml index 1f752a9f2..08e289109 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -426,6 +426,38 @@ en: recommendations_title: How to create a spending proposal start_new: Create spending proposal wrong_price_format: Only integer numbers + spending_proposal: + already_supported: You have already supported this. Share it! + comments: + one: 1 comment + other: "%{count} comments" + zero: No comments + proposal: Proposal + reason_for_supports_necessary: 2% of Census + support: Support + support_title: Support this proposal + supports: + one: 1 support + other: "%{count} supports" + zero: No supports + supports_necessary: "%{number} supports needed" + total_percent: 100% + show: + author_deleted: User deleted + back_link: Go back + code: 'Proposal code:' + comments: + one: 1 comment + other: "%{count} comments" + zero: No comments + comments_title: Comments + edit_proposal_link: Edit + flag: This proposal has been flagged as inappropriate by several users. + login_to_comment: You must %{signin} or %{signup} to leave a comment. + share: Share + update: + form: + submit_button: Save changes stats: index: visits: Visits diff --git a/config/routes.rb b/config/routes.rb index 680f657ab..0d704be04 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,7 +66,11 @@ Rails.application.routes.draw do end scope '/participatory_budget' do - resources :spending_proposals, only: [:index, :new, :create, :show, :destroy], path: 'investment_projects' + resources :spending_proposals, only: [:index, :new, :create, :show, :destroy], path: 'investment_projects' do + member do + post :vote + end + end end resources :stats, only: [:index] diff --git a/db/migrate/20160329115418_add_votes_up_to_spending_proposals.rb b/db/migrate/20160329115418_add_votes_up_to_spending_proposals.rb new file mode 100644 index 000000000..aa9397c2e --- /dev/null +++ b/db/migrate/20160329115418_add_votes_up_to_spending_proposals.rb @@ -0,0 +1,5 @@ +class AddVotesUpToSpendingProposals < ActiveRecord::Migration + def change + add_column :spending_proposals, :cached_votes_up, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index c6c5faa19..b5daf6abd 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: 20160328152843) do +ActiveRecord::Schema.define(version: 20160329115418) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -312,6 +312,7 @@ ActiveRecord::Schema.define(version: 20160328152843) do t.integer "price_first_year", limit: 8 t.string "time_scope" t.datetime "unfeasible_email_sent_at" + t.integer "cached_votes_up" end add_index "spending_proposals", ["author_id"], name: "index_spending_proposals_on_author_id", using: :btree diff --git a/spec/features/votes_spec.rb b/spec/features/votes_spec.rb index 3ccf6bcdb..c8164de2c 100644 --- a/spec/features/votes_spec.rb +++ b/spec/features/votes_spec.rb @@ -313,7 +313,7 @@ feature 'Votes' do scenario 'Not logged user trying to vote comments in proposals', :js do proposal = create(:proposal) comment = create(:comment, commentable: proposal) - + visit comment_path(comment) within("#comment_#{comment.id}_reply") do find("div.votes").hover @@ -361,4 +361,64 @@ feature 'Votes' do expect_message_only_verified_can_vote_proposals end end + + feature 'Spending Proposals' do + background { login_as(@manuela) } + + xscenario "Index shows user votes on proposals" do + proposal1 = create(:proposal) + proposal2 = create(:proposal) + proposal3 = create(:proposal) + create(:vote, voter: @manuela, votable: proposal1, vote_flag: true) + + visit proposals_path + + within("#proposals") do + within("#proposal_#{proposal1.id}_votes") do + expect(page).to have_content "You have already supported this proposal. Share it!" + end + + within("#proposal_#{proposal2.id}_votes") do + expect(page).to_not have_content "You have already supported this proposal. Share it!" + end + + within("#proposal_#{proposal3.id}_votes") do + expect(page).to_not have_content "You have already supported this proposal. Share it!" + end + end + end + + feature 'Single spending proposal' do + background do + @proposal = create(:spending_proposal) + end + + scenario 'Show no votes' do + visit spending_proposal_path(@proposal) + expect(page).to have_content "No supports" + end + + scenario 'Trying to vote multiple times', :js do + visit spending_proposal_path(@proposal) + + within('.supports') do + find('.in-favor a').click + expect(page).to have_content "1 support" + + expect(page).to_not have_selector ".in-favor a" + end + end + + scenario 'Create from proposal show', :focus, :js do + visit spending_proposal_path(@proposal) + + within('.supports') do + find('.in-favor a').click + + expect(page).to have_content "1 support" + expect(page).to have_content "You have already supported this. Share it!" + end + end + end + end end