justindao
justindao

Reputation: 2391

Rails: Best way to implement a thumbs up/down for user/object interactions?

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

Answers (2)

OuttaSpaceTime
OuttaSpaceTime

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

Ilya Bodrov-Krukowski
Ilya Bodrov-Krukowski

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

Related Questions