From 92b09c7de4298037514a99e5ca61dd148faa956e Mon Sep 17 00:00:00 2001 From: Rishabh Singh Date: Fri, 9 Aug 2019 17:33:42 +0530 Subject: [PATCH] Add vote functionality to tracks --- app/controllers/admin/events_controller.rb | 6 +- app/controllers/admin/tracks_controller.rb | 19 ++++++ app/models/event.rb | 2 +- app/models/track.rb | 21 ++++++ app/models/vote.rb | 4 +- .../admin/tracks/_datatable_row_rating.haml | 12 ++++ app/views/admin/tracks/_voting.html.haml | 62 +++++++++++++++++ app/views/admin/tracks/index.html.haml | 9 +++ app/views/admin/tracks/show.html.haml | 8 +++ app/views/admin/tracks/vote.js.erb | 10 +++ .../versions/_object_desc_and_link.html.haml | 2 +- config/routes.rb | 1 + .../20190807165246_add_polymorphic_to_vote.rb | 6 ++ db/schema.rb | 4 +- spec/factories/votes.rb | 4 +- spec/features/versions_spec.rb | 2 +- spec/models/event_spec.rb | 18 ++--- spec/models/track_spec.rb | 66 +++++++++++++++++++ 18 files changed, 237 insertions(+), 19 deletions(-) create mode 100644 app/views/admin/tracks/_datatable_row_rating.haml create mode 100644 app/views/admin/tracks/_voting.html.haml create mode 100644 app/views/admin/tracks/vote.js.erb create mode 100644 db/migrate/20190807165246_add_polymorphic_to_vote.rb diff --git a/app/controllers/admin/events_controller.rb b/app/controllers/admin/events_controller.rb index d8e9c1c9..ddec63e5 100644 --- a/app/controllers/admin/events_controller.rb +++ b/app/controllers/admin/events_controller.rb @@ -47,8 +47,8 @@ module Admin @versions = @event.versions | PaperTrail::Version.where(item_type: 'Commercial').where('object LIKE ?', "%commercialable_id: #{@event.id}\ncommercialable_type: Event%") | PaperTrail::Version.where(item_type: 'Commercial').where('object_changes LIKE ?', "%commercialable_id:\n- \n- #{@event.id}\ncommercialable_type:\n- \n- Event%") | - PaperTrail::Version.where(item_type: 'Vote').where('object_changes LIKE ?', "%\nevent_id:\n- \n- #{@event.id}\n%") | - PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%\nevent_id: #{@event.id}\n%") + PaperTrail::Version.where(item_type: 'Vote').where('object_changes LIKE ?', "%votable_type:\n- \n- Event\nvotable_id:\n- \n- #{@event.id}%") | + PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%votable_type: Event\nvotable_id: #{@event.id}%") end def edit @@ -141,7 +141,7 @@ module Admin def vote @votes = @event.votes.includes(:user) - if (votes = current_user.votes.find_by_event_id(params[:id])) + if (votes = current_user.votes.find_by(votable: @event)) votes.update_attributes(rating: params[:rating]) else @myvote = @event.votes.build diff --git a/app/controllers/admin/tracks_controller.rb b/app/controllers/admin/tracks_controller.rb index a4f4c69e..75f247f3 100644 --- a/app/controllers/admin/tracks_controller.rb +++ b/app/controllers/admin/tracks_controller.rb @@ -30,6 +30,7 @@ module Admin end def show + @votes = @track.votes.includes(:user) respond_to do |format| format.html { render } format.json { render json: @conference.tracks.to_json } @@ -65,6 +66,24 @@ module Admin end end + def vote + @votes = @track.votes.includes(:user) + + if (votes = current_user.votes.find_by(votable: @track)) + votes.update_attributes(rating: params[:rating]) + else + @myvote = @track.votes.build + @myvote.user = current_user + @myvote.rating = params[:rating] + @myvote.save + end + + respond_to do |format| + format.html { redirect_to admin_conference_program_track_path(@conference.short_title, @track) } + format.js + end + end + def destroy if @track.destroy redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title), diff --git a/app/models/event.rb b/app/models/event.rb index 7fbc007d..c3404fe1 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -18,7 +18,7 @@ class Event < ApplicationRecord has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser' has_one :submitter, through: :submitter_event_user, source: :user - has_many :votes, dependent: :destroy + has_many :votes, as: :votable has_many :voters, through: :votes, source: :user has_many :commercials, as: :commercialable, dependent: :destroy has_many :surveys, as: :surveyable, dependent: :destroy diff --git a/app/models/track.rb b/app/models/track.rb index d34bfbef..ad1adba2 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -15,6 +15,8 @@ class Track < ApplicationRecord has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } + has_many :votes, as: :votable + has_many :voters, through: :votes, source: :user before_create :generate_guid validates :name, presence: true validates :color, format: /\A#[0-9A-F]{6}\z/ @@ -100,6 +102,25 @@ class Track < ApplicationRecord short_name end + def user_rating(user) + (vote = votes.find_by(user: user)) ? vote.rating : 0 + end + + def voted?(user = nil) + return votes.where(user: user).any? if user + + votes.any? + end + + def average_rating + @total_rating = 0 + votes.each do |vote| + @total_rating += vote.rating + end + @total = votes.size + @total_rating.positive? ? number_with_precision(@total_rating / @total.to_f, precision: 2, strip_insignificant_zeros: true) : 0 + end + def transition_possible?(transition) self.class.state_machine.events_for(current_state).include?(transition) end diff --git a/app/models/vote.rb b/app/models/vote.rb index 2ce0e671..40021385 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -2,7 +2,7 @@ class Vote < ApplicationRecord belongs_to :user - belongs_to :event + belongs_to :votable, polymorphic: true has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } @@ -11,6 +11,6 @@ class Vote < ApplicationRecord private def conference_id - event.program.conference_id + votable.program.conference_id end end diff --git a/app/views/admin/tracks/_datatable_row_rating.haml b/app/views/admin/tracks/_datatable_row_rating.haml new file mode 100644 index 00000000..1e369fab --- /dev/null +++ b/app/views/admin/tracks/_datatable_row_rating.haml @@ -0,0 +1,12 @@ +- if show_votes + %div{ data: { toggle: 'tooltip' }, title: rating_tooltip(track, max_rating) }< + = rating_stars(track.average_rating, max_rating, avgrate: true) + +.clearfix + - if track.voted?(current_user) + %span.label.label-success + You voted: + = rating_fraction(track.user_rating(current_user), max_rating) + - else + %span.label.label-danger + Not rated diff --git a/app/views/admin/tracks/_voting.html.haml b/app/views/admin/tracks/_voting.html.haml new file mode 100644 index 00000000..184305fe --- /dev/null +++ b/app/views/admin/tracks/_voting.html.haml @@ -0,0 +1,62 @@ +%table.table#myrating + - if show_votes + %tr + %td.col-md-2 + %b Rating + %td + #{track.average_rating}/#{max_rating} + = rating_stars(track.average_rating, max_rating, avgrate: true) + %tr + %td + %b Voters + %td + = votes.length + - if votes.present? + ( + = votes.collect(&:name).to_sentence + ) + %tr + %td.col-md-2 + %b Your vote + %td + - if voting_period + - max_rating.times do |counter| + - if track.user_rating(current_user) > counter + = link_to '', vote_admin_conference_program_track_path(conference_id, track, rating: counter + 1), + remote: true, + id: "label#{counter + 1}", + class: 'rating myrating bright', + voted: true + - else + = link_to '', vote_admin_conference_program_track_path(conference_id, track, rating: counter + 1), + remote: true, + id: "label#{counter + 1}", + class: 'rating myrating' + - else + = rating_stars(track.user_rating(current_user), max_rating, voted: true) + (Voting period is closed) + + - if show_votes + - if votes.present? + - votes.each do |vote| + - unless vote.user_id == current_user.id + %tr + %td + = vote.name + %td + = rating_stars(vote.rating, max_rating) + +:javascript + $(".myrating").hover( + function() { // mouseover + $(this).prevAll().andSelf().addClass('glow'); + }, + function() { // mouseout + $(this).siblings().andSelf().removeClass('glow'); + } + ); + + $(".myrating").click(function() { + $(this).siblings().removeClass("bright"); + $(this).prevAll().andSelf().addClass("bright"); + }); diff --git a/app/views/admin/tracks/index.html.haml b/app/views/admin/tracks/index.html.haml index acdbfe0d..1832a90a 100644 --- a/app/views/admin/tracks/index.html.haml +++ b/app/views/admin/tracks/index.html.haml @@ -37,6 +37,9 @@ %thead %th ID %th Name + - if @program.rating_enabled? + %th + %b Rating %th Description %th Room %th Start Date @@ -54,6 +57,12 @@ = link_to admin_conference_program_track_path(@conference.short_title, track), class: 'btn' do %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"} = track.name + - if @program.rating_enabled? + %td.col-md-1{ data: { order: track.average_rating } } + = render 'datatable_row_rating', + track: track, + show_votes: @program.show_voting?, + max_rating: @program.rating %td %p = markdown(truncate(track.description)) diff --git a/app/views/admin/tracks/show.html.haml b/app/views/admin/tracks/show.html.haml index a7bb61a4..96c7c8f8 100644 --- a/app/views/admin/tracks/show.html.haml +++ b/app/views/admin/tracks/show.html.haml @@ -104,6 +104,14 @@ %b Relevance %td = markdown(@track.relevance) + - if @conference.program.rating_enabled? + = render 'voting', + track: @track, + show_votes: @program.show_voting?, + max_rating: @program.rating, + voting_period: @program.voting_period?, + votes: @votes, + conference_id: @conference.short_title .tab-pane#events .col-md-12 diff --git a/app/views/admin/tracks/vote.js.erb b/app/views/admin/tracks/vote.js.erb new file mode 100644 index 00000000..67afad9f --- /dev/null +++ b/app/views/admin/tracks/vote.js.erb @@ -0,0 +1,10 @@ +$('table#myrating').replaceWith( + "<%= escape_javascript(render 'voting', \ + track: @track, \ + show_votes: @program.show_voting?, \ + max_rating: @program.rating, \ + voting_period: @program.voting_period?, \ + votes: @votes, \ + conference_id: @conference.short_title \ + ) %>" +); diff --git a/app/views/admin/versions/_object_desc_and_link.html.haml b/app/views/admin/versions/_object_desc_and_link.html.haml index 1728b999..3632030c 100644 --- a/app/views/admin/versions/_object_desc_and_link.html.haml +++ b/app/views/admin/versions/_object_desc_and_link.html.haml @@ -70,7 +70,7 @@ - when 'EventsRegistration', 'Comment', 'Vote', 'Event' event - - event_id = object.try(:event_id) || object.try(:commentable_id) || object.id + - event_id = object.try(:votable_id) || object.try(:event_id) || object.try(:commentable_id) || object.id = link_to (current_or_last_object_state('Event', event_id).try(:title) || 'deleted event'), admin_conference_program_event_path(conference_short_title, event_id) diff --git a/config/routes.rb b/config/routes.rb index 57bfa630..b1d9fc83 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,6 +87,7 @@ Osem::Application.routes.draw do patch :reject patch :cancel patch :update_selected_schedule + get :vote end resources :roles, only: [:show, :edit, :update] do member do diff --git a/db/migrate/20190807165246_add_polymorphic_to_vote.rb b/db/migrate/20190807165246_add_polymorphic_to_vote.rb new file mode 100644 index 00000000..d7edeb4d --- /dev/null +++ b/db/migrate/20190807165246_add_polymorphic_to_vote.rb @@ -0,0 +1,6 @@ +class AddPolymorphicToVote < ActiveRecord::Migration[5.2] + def change + add_column :votes, :votable_type, :string + add_column :votes, :votable_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index b199367e..c977b0ad 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_06_03_143107) do +ActiveRecord::Schema.define(version: 2019_08_07_165246) do create_table "answers", force: :cascade do |t| t.string "title" @@ -660,6 +660,8 @@ ActiveRecord::Schema.define(version: 2019_06_03_143107) do t.datetime "created_at" t.datetime "updated_at" t.integer "user_id" + t.string "votable_type" + t.integer "votable_id" end create_table "vpositions", force: :cascade do |t| diff --git a/spec/factories/votes.rb b/spec/factories/votes.rb index 038b6afe..9306307b 100644 --- a/spec/factories/votes.rb +++ b/spec/factories/votes.rb @@ -2,8 +2,10 @@ FactoryBot.define do factory :vote do - event user rating { 1 } + + association :votable, factory: :event + end end diff --git a/spec/features/versions_spec.rb b/spec/features/versions_spec.rb index ec443fd0..768b7b07 100644 --- a/spec/features/versions_spec.rb +++ b/spec/features/versions_spec.rb @@ -406,7 +406,7 @@ feature 'Version' do conference.program.rating = 1 create(:event, program: conference.program, title: 'My first event') event = create(:event, program: conference.program, title: 'My second event') - create(:vote, user: organizer, event: event) + create(:vote, user: organizer, votable: event) Vote.last.destroy PaperTrail::Version.last.reify.save diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 92768222..ab0ee9b5 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -227,13 +227,13 @@ describe Event do end it 'returns 0 if the event has no votes from that user' do - create(:vote, user: another_user, event: event) + create(:vote, user: another_user, votable: event) expect(event.user_rating(user)).to eq 0 end it 'returns the rating if the event has votes from that user' do - create(:vote, user: another_user, event: event, rating: 3) - create(:vote, user: user, event: event, rating: 2) + create(:vote, user: another_user, votable: event, rating: 3) + create(:vote, user: user, votable: event, rating: 2) expect(event.user_rating(user)).to eq 2 end end @@ -244,17 +244,17 @@ describe Event do end it 'returns false if the event has no votes by that user' do - create(:vote, user: another_user, event: event) + create(:vote, user: another_user, votable: event) expect(event.voted?(user)).to eq false end it 'returns true when the event has votes' do - create(:vote, user: another_user, event: event) + create(:vote, user: another_user, votable: event) expect(event.voted?).to eq true end it 'returns true when the event has votes by that user' do - create(:vote, user: user, event: event) + create(:vote, user: user, votable: event) expect(event.voted?(user)).to eq true end end @@ -269,8 +269,8 @@ describe Event do context 'returns the average voting' do before :each do another_user = create(:user) - create(:vote, user: user, event: event, rating: 1) - create(:vote, user: another_user, event: event, rating: 3) + create(:vote, user: user, votable: event, rating: 1) + create(:vote, user: another_user, votable: event, rating: 3) end it 'when there are votes and the average is integer' do @@ -279,7 +279,7 @@ describe Event do it 'when there are votes and the average is float' do new_user = create(:user) - create(:vote, user: new_user, event: event, rating: 3) + create(:vote, user: new_user, votable: event, rating: 3) expect(event.average_rating).to eq '2.33' end end diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index b58fc622..f27886d5 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -6,6 +6,8 @@ describe Track do subject { create(:track) } let(:track) { create(:track) } let(:self_organized_track) { create(:track, :self_organized) } + let(:user) { create(:user) } + let(:another_user) { create(:user) } describe 'association' do it { is_expected.to belong_to(:program) } @@ -110,6 +112,70 @@ describe Track do end end + describe '#user_rating' do + it 'returns 0 if the track has no votes' do + expect(track.user_rating(user)).to eq 0 + end + + it 'returns 0 if the track has no votes from that user' do + create(:vote, user: another_user, votable: track) + expect(track.user_rating(user)).to eq 0 + end + + it 'returns the rating if the track has votes from that user' do + create(:vote, user: another_user, votable: track, rating: 3) + create(:vote, user: user, votable: track, rating: 2) + expect(track.user_rating(user)).to eq 2 + end + end + + describe '#voted?' do + it 'returns false if the track has no votes' do + expect(track.voted?).to eq false + end + + it 'returns false if the track has no votes by that user' do + create(:vote, user: another_user, votable: track) + expect(track.voted?(user)).to eq false + end + + it 'returns true when the track has votes' do + create(:vote, user: another_user, votable: track) + expect(track.voted?).to eq true + end + + it 'returns true when the track has votes by that user' do + create(:vote, user: user, votable: track) + expect(track.voted?(user)).to eq true + end + end + + describe '#average_rating' do + context 'returns 0' do + it 'when there are no votes' do + expect(track.average_rating).to eq 0 + end + end + + context 'returns the average voting' do + before :each do + another_user = create(:user) + create(:vote, user: user, votable: track, rating: 1) + create(:vote, user: another_user, votable: track, rating: 3) + end + + it 'when there are votes and the average is integer' do + expect(track.average_rating).to eq '2' + end + + it 'when there are votes and the average is float' do + new_user = create(:user) + create(:vote, user: new_user, votable: track, rating: 3) + expect(track.average_rating).to eq '2.33' + end + end + end + describe '#start_date_before_end_date' do before :each do @conference = create(:conference, start_date: 1.day.ago, end_date: 2.days.from_now)