Add vote functionality to tracks

This commit is contained in:
Rishabh Singh 2019-08-09 17:33:42 +05:30
parent 8b2f2b538e
commit 92b09c7de4
18 changed files with 237 additions and 19 deletions

View file

@ -47,8 +47,8 @@ module Admin
@versions = @event.versions | @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 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: '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_changes LIKE ?', "%votable_type:\n- \n- Event\nvotable_id:\n- \n- #{@event.id}%") |
PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%\nevent_id: #{@event.id}\n%") PaperTrail::Version.where(item_type: 'Vote').where('object LIKE ?', "%votable_type: Event\nvotable_id: #{@event.id}%")
end end
def edit def edit
@ -141,7 +141,7 @@ module Admin
def vote def vote
@votes = @event.votes.includes(:user) @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]) votes.update_attributes(rating: params[:rating])
else else
@myvote = @event.votes.build @myvote = @event.votes.build

View file

@ -30,6 +30,7 @@ module Admin
end end
def show def show
@votes = @track.votes.includes(:user)
respond_to do |format| respond_to do |format|
format.html { render } format.html { render }
format.json { render json: @conference.tracks.to_json } format.json { render json: @conference.tracks.to_json }
@ -65,6 +66,24 @@ module Admin
end end
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 def destroy
if @track.destroy if @track.destroy
redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title), redirect_to admin_conference_program_tracks_path(conference_id: @conference.short_title),

View file

@ -18,7 +18,7 @@ class Event < ApplicationRecord
has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser' has_one :submitter_event_user, -> { where(event_role: 'submitter') }, class_name: 'EventUser'
has_one :submitter, through: :submitter_event_user, source: :user 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 :voters, through: :votes, source: :user
has_many :commercials, as: :commercialable, dependent: :destroy has_many :commercials, as: :commercialable, dependent: :destroy
has_many :surveys, as: :surveyable, dependent: :destroy has_many :surveys, as: :surveyable, dependent: :destroy

View file

@ -15,6 +15,8 @@ class Track < ApplicationRecord
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } 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 before_create :generate_guid
validates :name, presence: true validates :name, presence: true
validates :color, format: /\A#[0-9A-F]{6}\z/ validates :color, format: /\A#[0-9A-F]{6}\z/
@ -100,6 +102,25 @@ class Track < ApplicationRecord
short_name short_name
end 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) def transition_possible?(transition)
self.class.state_machine.events_for(current_state).include?(transition) self.class.state_machine.events_for(current_state).include?(transition)
end end

View file

@ -2,7 +2,7 @@
class Vote < ApplicationRecord class Vote < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :event belongs_to :votable, polymorphic: true
has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id } has_paper_trail ignore: [:updated_at], meta: { conference_id: :conference_id }
@ -11,6 +11,6 @@ class Vote < ApplicationRecord
private private
def conference_id def conference_id
event.program.conference_id votable.program.conference_id
end end
end end

View file

@ -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

View file

@ -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");
});

View file

@ -37,6 +37,9 @@
%thead %thead
%th ID %th ID
%th Name %th Name
- if @program.rating_enabled?
%th
%b Rating
%th Description %th Description
%th Room %th Room
%th Start Date %th Start Date
@ -54,6 +57,12 @@
= link_to admin_conference_program_track_path(@conference.short_title, track), class: 'btn' do = 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) }"} %span.label{style: "background-color: #{track.color}; color: #{ contrast_color(track.color) }"}
= track.name = 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 %td
%p %p
= markdown(truncate(track.description)) = markdown(truncate(track.description))

View file

@ -104,6 +104,14 @@
%b Relevance %b Relevance
%td %td
= markdown(@track.relevance) = 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 .tab-pane#events
.col-md-12 .col-md-12

View file

@ -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 \
) %>"
);

View file

@ -70,7 +70,7 @@
- when 'EventsRegistration', 'Comment', 'Vote', 'Event' - when 'EventsRegistration', 'Comment', 'Vote', 'Event'
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'), = link_to (current_or_last_object_state('Event', event_id).try(:title) || 'deleted event'),
admin_conference_program_event_path(conference_short_title, event_id) admin_conference_program_event_path(conference_short_title, event_id)

View file

@ -87,6 +87,7 @@ Osem::Application.routes.draw do
patch :reject patch :reject
patch :cancel patch :cancel
patch :update_selected_schedule patch :update_selected_schedule
get :vote
end end
resources :roles, only: [:show, :edit, :update] do resources :roles, only: [:show, :edit, :update] do
member do member do

View file

@ -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

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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| create_table "answers", force: :cascade do |t|
t.string "title" t.string "title"
@ -660,6 +660,8 @@ ActiveRecord::Schema.define(version: 2019_06_03_143107) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "user_id" t.integer "user_id"
t.string "votable_type"
t.integer "votable_id"
end end
create_table "vpositions", force: :cascade do |t| create_table "vpositions", force: :cascade do |t|

View file

@ -2,8 +2,10 @@
FactoryBot.define do FactoryBot.define do
factory :vote do factory :vote do
event
user user
rating { 1 } rating { 1 }
association :votable, factory: :event
end end
end end

View file

@ -406,7 +406,7 @@ feature 'Version' do
conference.program.rating = 1 conference.program.rating = 1
create(:event, program: conference.program, title: 'My first event') create(:event, program: conference.program, title: 'My first event')
event = create(:event, program: conference.program, title: 'My second 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 Vote.last.destroy
PaperTrail::Version.last.reify.save PaperTrail::Version.last.reify.save

View file

@ -227,13 +227,13 @@ describe Event do
end end
it 'returns 0 if the event has no votes from that user' do 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 expect(event.user_rating(user)).to eq 0
end end
it 'returns the rating if the event has votes from that user' do 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: another_user, votable: event, rating: 3)
create(:vote, user: user, event: event, rating: 2) create(:vote, user: user, votable: event, rating: 2)
expect(event.user_rating(user)).to eq 2 expect(event.user_rating(user)).to eq 2
end end
end end
@ -244,17 +244,17 @@ describe Event do
end end
it 'returns false if the event has no votes by that user' do 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 expect(event.voted?(user)).to eq false
end end
it 'returns true when the event has votes' do 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 expect(event.voted?).to eq true
end end
it 'returns true when the event has votes by that user' do 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 expect(event.voted?(user)).to eq true
end end
end end
@ -269,8 +269,8 @@ describe Event do
context 'returns the average voting' do context 'returns the average voting' do
before :each do before :each do
another_user = create(:user) another_user = create(:user)
create(:vote, user: user, event: event, rating: 1) create(:vote, user: user, votable: event, rating: 1)
create(:vote, user: another_user, event: event, rating: 3) create(:vote, user: another_user, votable: event, rating: 3)
end end
it 'when there are votes and the average is integer' do 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 it 'when there are votes and the average is float' do
new_user = create(:user) 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' expect(event.average_rating).to eq '2.33'
end end
end end

View file

@ -6,6 +6,8 @@ describe Track do
subject { create(:track) } subject { create(:track) }
let(:track) { create(:track) } let(:track) { create(:track) }
let(:self_organized_track) { create(:track, :self_organized) } let(:self_organized_track) { create(:track, :self_organized) }
let(:user) { create(:user) }
let(:another_user) { create(:user) }
describe 'association' do describe 'association' do
it { is_expected.to belong_to(:program) } it { is_expected.to belong_to(:program) }
@ -110,6 +112,70 @@ describe Track do
end end
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 describe '#start_date_before_end_date' do
before :each do before :each do
@conference = create(:conference, start_date: 1.day.ago, end_date: 2.days.from_now) @conference = create(:conference, start_date: 1.day.ago, end_date: 2.days.from_now)