ruby-on-railspins

Order votes system in rails


I implemented a simple way to let users upvotes pins, But I would like to order the pins by the number of upvotes, here is my upvote system:

app/controllers/pins_controller.rb

def upvote
  @pin = Pin.find(params[:id])
  @pin.votes.create
  redirect_to(pins_path)
end

app/models/vote.rb

class Vote < ActiveRecord::Base

belongs_to :pin


end

app/models/pin.rb

has_many :votes, dependent: :destroy

And my routes.rb looks like:

resources :pins do
  member do
    post 'upvote'
  end
en

So I would like my app/views/pins/index.html.erb

to show the pins by most upvoted to less upvoted.

I know that acts_as_votable provide a system to do that, but as I did not use that gem to implement my vote system, I am not sure it's a good idea to use it.

What do you think?

My app/views/pins/index.html.erb is as following:

<% @pins.each do |pin| %>
    <div class="box panel panel-default">
      <%= image_tag pin.image.url(:medium) %>
      <div class="panel-body">

        <p class="startupinfo">
          <%= pin.startupname %><span class="reward"><i class="fa fa-heart"></i> <%= pin.reward %></p></span>

        <p class="startupname"><%= pin.description %></p>

        <p class="startuptag"><i class="fa fa-map-marker turquoise"></i> <%= pin.tag %> <i class="fa fa-tags turquoise"></i> <%= pin.city %><%= link_to upvote_pin_path(pin), method: :post do %> <i class="fa fa-chevron-circle-up fa-1x" style="color:#28c3ab"></i>  <% end %>
            <%= pluralize(pin.votes.count, "upvote") %></p>

        <p class="users-edit">
        <%= link_to pin_path(pin), class: "btn btn-success" do %> Tweet <% end %>

        <% if pin.user == current_user %>
          <%= link_to 'Edit', edit_pin_path(pin) %>
          <%= link_to 'Delete', pin, method: :delete, data: { confirm: 'Are you sure?' } %></p>
        <% end %>
      </div>
    </div>
  <% end %>

BASED ON BALOO ANSWER:

I ran:

rails generate migration add_votescount_to_pins votes_count:integer

rails generate migration add_countercache_to_votes counter_cache:integer

I updated manually the migration _add_votescount_to_pins.rb so it looks like:

class AddVotescountToPins < ActiveRecord::Migration
  def change
    add_column :pins, :votes_count, :integer
    Pin.reset_column_information
        Pin.includes(:votes).all.each do |pin|
    Pin.update_counters(pin.id, :votes_count => pin.votes.size)
  end
end

I ran:

rake db:migrate

Then I added the following line into my pins.controller.rb :

def index
    @pins = Pin.all
    @pins = Pin.order(votes_count: :desc)
  end

Now my schema.rb file looks like this:

# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20140704125839) do

  create_table "pins", force: true do |t|
    t.string   "description"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "user_id"
    t.string   "image_file_name"
    t.string   "image_content_type"
    t.integer  "image_file_size"
    t.datetime "image_updated_at"
    t.string   "tag"
    t.string   "reward"
    t.string   "startupname"
    t.string   "city"
    t.string   "tweet"
    t.string   "logo"
    t.string   "website"
    t.string   "rewardcode"
    t.string   "logo_file_name"
    t.string   "logo_content_type"
    t.integer  "logo_file_size"
    t.datetime "logo_updated_at"
    t.integer  "votes_count"
  end

  add_index "pins", ["user_id"], name: "index_pins_on_user_id"

  create_table "startups", force: true do |t|
    t.string   "name"
    t.text     "description"
    t.text     "location"
    t.text     "tag"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "users", force: true do |t|
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string   "current_sign_in_ip"
    t.string   "last_sign_in_ip"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "name"
    t.string   "provider"
    t.string   "uid"
  end

  add_index "users", ["email"], name: "index_users_on_email", unique: true
  add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true

  create_table "votes", force: true do |t|
    t.integer  "pin_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "counter_cache"
  end

end

But my pin index still doesn't order by number of upvotes ! Any ideas?


Solution

  • So when you down vote it destroys?

    You can add votes_count integer column to pins then add a counter_cache to the vote.

    class Vote < ActiveRecord::Base
      belongs_to :pin, counter_cache: true
    end
    

    This will increment votes_count on a pin as someone votes for it.

    Then simply query based on that

    @pins = Pin.order(votes_count: :desc)
    

    In your migration you'll want to update votes_count based on the existing votes.

    add_column :pins, :votes_count, :integer, default: true
    Pin.reset_column_information
    Pin.all.each do |pin|
      Pin.reset_counters(pin.id, :votes)
    end