Acts As Votable

Written by Luka Kerr on March 31, 2018

The Acts As Votable gem allows for any model to be voted on. For example, if you had an image model, you could use this gem to setup upvoting and downvoting.

Firstly, you must add the gem to your Gemfile and run bundle install:

gem 'acts_as_votable', '~> 0.10.0'

Next you need to generate the Acts As Votable migrations and migrate the database:

rails g acts_as_votable:migration
rake db:migrate

Next you need to decide what model you want to allow voting on. For this example we are using an image model. For you this may differ, in which you should replace each instance of image with whatever model you are using. You need to add the line acts_as_votable to your image.rb model file.

In the routes.rb file you need to add the following for the images resource. The syntax here is important:

resources :images do
  member do
      put "upvote", to: "images#upvote"
      put 'downvote', to: 'images#downvote'
  end
end

Next, inside the images_controller.rb you need to setup the methods for upvoting and downvoting. This assumes you have the helper method current_user:

def upvote
  @image.upvote_by current_user
  redirect_to :back
end

def downvote
    @image.downvote_by current_user
    redirect_to :back
end

You also need to make sure you have a before_action to find the image. If you do, add the :upvote, :downvote methods to the before filter. If you don’t, add this line at the top of each method:

@image = Image.find(params[:id])

In your show.html.erb inside the images/ folder, add the following lines:

<%= link_to "Upvote", upvote_image_path(@image), method: :put %>
<%= link_to "Downvote, downvote_image_path(@image), method: :put %>

Next, before we show the score, we should add caching to out voting. Do this by generating an empty migration:

rails g migration add_cached_votes_to_images

Inside the generated migration, paste the following code:

def self.up
  add_column :images, :cached_votes_total, :integer, :default => 0
  add_column :images, :cached_votes_score, :integer, :default => 0
  add_column :images, :cached_votes_up, :integer, :default => 0
  add_column :images, :cached_votes_down, :integer, :default => 0
  add_column :images, :cached_weighted_score, :integer, :default => 0
  add_column :images, :cached_weighted_total, :integer, :default => 0
  add_column :images, :cached_weighted_average, :float, :default => 0.0
  add_index  :images, :cached_votes_total
  add_index  :images, :cached_votes_score
  add_index  :images, :cached_votes_up
  add_index  :images, :cached_votes_down
  add_index  :images, :cached_weighted_score
  add_index  :images, :cached_weighted_total
  add_index  :images, :cached_weighted_average

  Image.find_each(&:update_cached_votes)
  end

def self.down
  remove_column :images, :cached_votes_total
  remove_column :images, :cached_votes_score
  remove_column :images, :cached_votes_up
  remove_column :images, :cached_votes_down
  remove_column :images, :cached_weighted_score
  remove_column :images, :cached_weighted_total
  remove_column :images, :cached_weighted_average
end

Run rake db:migrate after this.

Finally, inside the show.html.erb add the following line which will show the score (upvotes - downvotes):

<%= @image.weighted_score %>