diff --git a/Gemfile b/Gemfile index a9b355c..278c7b1 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem 'omniauth-twitter' # Misc gem 'activeadmin', github: 'activeadmin/activeadmin' +gem 'acts_as_votable', '~> 0.11.0' gem 'paperclip' gem 'acts-as-taggable-on' diff --git a/Gemfile.lock b/Gemfile.lock index 72d7d8f..1faf831 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -55,14 +55,15 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - acts-as-taggable-on (4.0.0) - activerecord (>= 4.0) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - annotate (2.7.2) - activerecord (>= 3.2, < 6.0) - rake (>= 10.4, < 13.0) - arbre (1.1.1) + add-event-voting + acts-as-taggable-on (3.4.4) + activerecord (>= 3.2, < 5) + acts_as_votable (0.11.0) + addressable (2.3.8) + annotate (2.6.5) + activerecord (>= 2.3.0) + rake (>= 0.8.7) + arbre (1.0.2) activesupport (>= 3.0.0) arel (6.0.4) ast (2.3.0) @@ -382,6 +383,7 @@ DEPENDENCIES active_record-acts_as activeadmin! acts-as-taggable-on + acts_as_votable (~> 0.11.0) annotate bootstrap-sass (~> 3.3.3) bootstrap3-datetimepicker-rails (~> 4.14.30) diff --git a/app/assets/javascripts/votes.js.coffee b/app/assets/javascripts/votes.js.coffee new file mode 100644 index 0000000..da6d3c0 --- /dev/null +++ b/app/assets/javascripts/votes.js.coffee @@ -0,0 +1,5 @@ +$ -> + $('.vote-container').on 'ajax:success', 'form.vote-form', (event, data) -> + form = event.target + container = form.closest('.vote-container') + container.innerHTML = data diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 416cb29..065f1ed 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -28,6 +28,7 @@ @import "pages"; @import "people"; @import "scaffolds"; +@import "votes"; .font-heading { font-family: $font-heading; } .font-body { font-family: $font-body; } diff --git a/app/assets/stylesheets/votes.css.scss b/app/assets/stylesheets/votes.css.scss new file mode 100644 index 0000000..59f0d30 --- /dev/null +++ b/app/assets/stylesheets/votes.css.scss @@ -0,0 +1,8 @@ +.vote-header { + display: inline-block; + margin-right: 0.5em; +} + +.vote-form { + display: inline-block; +} diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb new file mode 100644 index 0000000..82e7657 --- /dev/null +++ b/app/controllers/votes_controller.rb @@ -0,0 +1,42 @@ +class VotesController < ApplicationController + before_filter :authenticate_user! + before_filter :set_votable + before_filter :ensure_votable + + def create + @votable.liked_by current_user + + if @votable.vote_registered? + render partial: 'votes/form', locals: { votable: @votable } + else + head :unprocessable_entity + end + end + + def destroy + if current_user.voted_for?(@votable) + @votable.unvote_by current_user + end + + render partial: 'votes/form', locals: { votable: @votable } + end + + private + + def set_votable + model = if params[:votable_type] == "Event" + Event + end + + if model + @votable = model.find_by_id(params[:votable_id]) + end + end + + def ensure_votable + return head(:not_found) unless @votable + + # Disallow users to vote on their own items: + head :forbidden if can_edit?(@votable) + end +end diff --git a/app/helpers/votes_helper.rb b/app/helpers/votes_helper.rb new file mode 100644 index 0000000..eaed9ac --- /dev/null +++ b/app/helpers/votes_helper.rb @@ -0,0 +1,17 @@ +module VotesHelper + def upvote_button_class(votable) + if current_user.voted_up_on?(votable) + "btn bg-orange" + else + "btn bg-grey" + end + end + + def upvote_form_method(votable) + if current_user.voted_up_on?(votable) + :delete + else + :post + end + end +end diff --git a/app/models/event.rb b/app/models/event.rb index 2e79adf..a22488c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -11,6 +11,7 @@ class Event < ActiveRecord::Base acts_as :topic acts_as_taggable + acts_as_votable has_and_belongs_to_many :people has_many :indications, as: :questionable diff --git a/app/models/user.rb b/app/models/user.rb index c990186..c2bd608 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,7 @@ class User < ActiveRecord::Base has_one :person has_many :topics accepts_nested_attributes_for :person + acts_as_voter scope :admins, -> { where('admin = ?', true).all } @@ -53,5 +54,4 @@ def self.new_with_session(params, session) end end end - end diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index adfc94f..e13ed61 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -19,7 +19,7 @@ <% end %> <%= link_to indications_path(id: @event.id, type: :event), :method => :post, :class => "btn" do %> - Flag For Spam + Flag For Spam <% end %> <% end %> @@ -54,6 +54,11 @@ + <% if user_signed_in? && !can_edit?(@event) %> +
+ <%= render partial: "votes/form", locals: { votable: @event } %> +
+ <% end %> <%= render partial: 'topics/disqus' %> diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index 597d5ab..bdfb2a7 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -19,7 +19,7 @@ <% end %> <%= link_to indications_path(id: @news.id, type: :news), :method => :post, :class => "btn" do %> - Flag For Spam + Flag For Spam <% end %> <% end %> diff --git a/app/views/resources/show.html.erb b/app/views/resources/show.html.erb index 72bac35..38ede21 100644 --- a/app/views/resources/show.html.erb +++ b/app/views/resources/show.html.erb @@ -17,7 +17,7 @@ <% end %> <%= link_to indications_path(id: @resource.id, type: :resource), :method => :post, :class => "btn" do %> - Flag For Spam + Flag For Spam <% end %> <% end %> diff --git a/app/views/votes/_form.html.erb b/app/views/votes/_form.html.erb new file mode 100644 index 0000000..ae9c0f4 --- /dev/null +++ b/app/views/votes/_form.html.erb @@ -0,0 +1,13 @@ +Would you recommend this? +<%= form_tag votes_path, method: upvote_form_method(votable), class: "vote-form", "data-remote" => true do %> + + + +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 2cf2759..acfe861 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,6 +11,12 @@ resources :at_who resources :indications, only: :create + resources :votes, only: [:create] do + collection do + delete :destroy + end + end + resources :people do collection do post :send_message diff --git a/db/migrate/20171014222022_create_votes.rb b/db/migrate/20171014222022_create_votes.rb new file mode 100644 index 0000000..6b3d470 --- /dev/null +++ b/db/migrate/20171014222022_create_votes.rb @@ -0,0 +1,17 @@ +class CreateVotes < ActiveRecord::Migration + def change + create_table :votes do |t| + t.references :votable, polymorphic: true + t.references :voter, polymorphic: true + + t.boolean :vote_flag + t.string :vote_scope + t.integer :vote_weight + + t.timestamps + end + + add_index :votes, [:voter_id, :voter_type, :vote_scope] + add_index :votes, [:votable_id, :votable_type, :vote_scope] + end +end diff --git a/db/schema.rb b/db/schema.rb index f83b938..48652ca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,6 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. +ActiveRecord::Schema.define(version: 20171014222022) do ActiveRecord::Schema.define(version: 20171009191656) do create_table "events", force: true do |t| @@ -174,4 +175,19 @@ add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true add_index "users", ["uid"], name: "index_users_on_uid" + create_table "votes", force: true do |t| + t.integer "votable_id" + t.string "votable_type" + t.integer "voter_id" + t.string "voter_type" + t.boolean "vote_flag" + t.string "vote_scope" + t.integer "vote_weight" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "votes", ["votable_id", "votable_type", "vote_scope"], name: "index_votes_on_votable_id_and_votable_type_and_vote_scope" + add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope" + end diff --git a/spec/controllers/votes_controller_spec.rb b/spec/controllers/votes_controller_spec.rb new file mode 100644 index 0000000..209c2da --- /dev/null +++ b/spec/controllers/votes_controller_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe VotesController, type: :controller do + render_views + let(:votable_owner) { FactoryGirl.create(:user) } + let(:votable) { FactoryGirl.create(:event, user: votable_owner) } + + context "authenticated" do + let(:user) { FactoryGirl.create(:user) } + let(:my_votable) { FactoryGirl.create(:event, user: user) } + + before(:each) do + request.env["devise.mapping"] = Devise.mappings[:user] + sign_in user + end + + describe "#create" do + it "upvotes item the current user cannot edit" do + expect(user.voted_up_on?(votable)).to eq(false) + + post :create, votable_type: votable.class.name, votable_id: votable.id + + expect(response).to have_http_status(:ok) + expect(user.voted_up_on?(votable)).to eq(true) + end + + it "does not upvote item the current user can edit" do + expect(user.voted_up_on?(my_votable)).to eq(false) + + post :create, votable_type: my_votable.class.name, votable_id: my_votable.id + + expect(response).to have_http_status(:forbidden) + expect(user.voted_up_on?(my_votable)).to eq(false) + end + + it "404s when invalid votable type is given" do + post :create, votable_type: user.class.name, votable_id: user.id + + expect(response).to have_http_status(:not_found) + end + + it "404s when invalid votable ID is given" do + post :create, votable_type: votable.class.name, votable_id: votable.id + 100 + + expect(response).to have_http_status(:not_found) + end + end + + describe "#destroy" do + it "removes an upvote" do + votable.liked_by user + expect(user.voted_for?(votable)).to eq(true) + + delete :destroy, votable_type: votable.class.name, votable_id: votable.id + + expect(response).to have_http_status(:ok) + expect(user.voted_for?(votable)).to eq(false) + end + end + end + + context "unauthenticated" do + describe "#create" do + it "redirects" do + post :create, votable_type: votable.class.name, votable_id: votable.id + + expect(response).to have_http_status(:redirect) + end + end + + describe "#destroy" do + it "redirects" do + delete :destroy, votable_type: votable.class.name, votable_id: votable.id + + expect(response).to have_http_status(:redirect) + end + end + end +end diff --git a/spec/models/indication_spec.rb b/spec/models/indication_spec.rb index 063d9a9..84e4013 100644 --- a/spec/models/indication_spec.rb +++ b/spec/models/indication_spec.rb @@ -1,4 +1,4 @@ -require 'rails_helper' +require 'spec_helper' RSpec.describe Indication, :type => :model do pending "add some examples to (or delete) #{__FILE__}"