Reputation: 2391
I'm trying to find a good method to implement a thumbs up/down feature for my Rails 4 app. The User can see multiple items on a page, and they can either thumbs up or down each item. I want them to be able to select the thumb up or down without the page reloading, while still storing this information in the database. And then, when the user revisits this page and find an item that they have already voted on, their vote should default to what they had said before.
I've looked into the Thumbs Up gem, but it's not exactly what I'm looking for. Is there an easier or more efficient way to do this?
Upvotes: -1
Views: 1415
Reputation: 966
I implemented it the following way for emojis, you just have to change it thumbs up emoji:
create vote migration
class CreateVotes < ActiveRecord::Migration[6.1]
def change
create_table :votes do |t|
t.boolean :like, default: false
t.boolean :celebration, default: false
t.references :user, null: false, foreign_key: true
t.integer :voteable_id
t.string :voteable_type
t.timestamps
end
add_index :votes, %i[user_id voteable_id], unique: true
end
end
vote model:
class Vote < ApplicationRecord
belongs_to :user
belongs_to :voteable, polymorphic: true
validates :user_id, uniqueness: { scope: %i[voteable_id voteable_type] }
end
voteable concern as model:
module Voteable
extend ActiveSupport::Concern
included do
has_many :votes, as: :voteable, dependent: :destroy
end
def total(icon)
icon_votes ||= Vote.where("#{icon}": true, voteable_id: id, voteable_type: self.class.name).count
icon_votes
end
end
then in routes add the following to class you want to be voteable:
resources :some_voteable_type do
member do
put :vote
end
...
end
in some_voteable_type model:
class some_voteable_type < ApplicationRecord
include Voteable
# more model code
end
in some_voteable_type controller:
#if you have like dislike you have to add dislike method and dislike route and adjust the logic accordingly
def vote
emoji_symbol = params[:emoji]
vote = Vote.find_or_create_by(voteable_id: @session.id, voteable_type: 'Session', user: current_user)
vote.send(emoji_symbol) ? vote.update(emoji_symbol.to_sym => false) : vote.update(emoji_symbol.to_sym => true)
end
now define _votes.html.slim
in some_voteable_type views. the id #show-session-votes might have to adjusted with when you have several of these on one page.
= link_to vote_backend_session_path(@session.id, emoji: :flash_on), method: :put, remote: true, class: "btn #{@session.active :flash_on} btn-vote btn-xs bg-dark body-text border-white hover-primary" do
i.material-icons-outlined.d-inline.align-middle.me-lg-2 flash_on
span.align-middle.text-white.ms-2 = @session.total(:flash_on)
do = render 'votes'
in your show view.
and then add vote.js.erb
here as well:
$("#show-session-votes").html("<%= escape_javascript(render 'votes') %>");
When you want to reuse vote for different pages you would have to make it a nested route instead.
Upvotes: 0
Reputation: 581
You can try to use ActiveRecord Reputation System (there is an episode on railscasts about it: http://railscasts.com/episodes/364-active-record-reputation-system, it is a nice way to start off). Still you will have to implement an authentication mechanism and use some AJAX to implement vote up/down system.
I do not think that there is a solution that will meet all your requirments (if ThumbsUp is not suitable, then you probably have some very specific requirments).
update
Voting in ThumbsUp is indeed made via controller, still you can use AJAX request to perform it asynchronously - in this case the page will not reload.
Upvotes: 2