简体   繁体   中英

Rails Polymorphic Voting Structure

In my app there will be many things that users can vote up or down (in a Reddit like manner). This will include an upvote/downvote structure for jokes , recipes , rules , etc. Right now I am trying to set up a polymorphic votes using the joke model as an example.

I have added votes as a polymorphic variable, as can be seen in my schema :

create_table "votes", force: :cascade do |t|
    t.integer  "value"
    t.integer  "user_id"
    t.integer  "voteable_id"
    t.string   "voteable_type"
    t.datetime "created_at",    null: false
    t.datetime "updated_at",    null: false
  end

  add_index "votes", ["user_id"], name: "index_votes_on_user_id"
  add_index "votes", ["voteable_type", "voteable_id"], name: "index_votes_on_voteable_type_and_voteable_id"

And I added a float column to my jokes table for rank .

I created a vote model:

class Vote < ActiveRecord::Base
  belongs_to :user
  belongs_to :voteable, polymorphic: true
  after_save :update_vote

  private
  def update_joke
    vote.update_rank
  end
end

And made the appropriate updates to my joke model:

class Joke < ActiveRecord::Base
  belongs_to :user
  has_many :votes, as: :voteable, dependent: :destroy
  default_scope { order('rank DESC')}
  def up_votes
    votes.where(value: 1).count
  end
  def down_votes
    votes.where(value: -1).count
  end
  def points
    votes.sum(:value)
  end
  def update_rank
    new_rank = points
    update_attribute(:rank, new_rank)
  end
end

And my user model:

class User < ActiveRecord::Base
  ...
  has_many :jokes, dependent: :destroy
  has_many :votes, dependent: :destroy
end

I have a votes_controller as follows:

class VotesController < ApplicationController

  def up_vote
    update_vote(1)
    redirect_to :back
  end

  def down_vote
    update_vote(-1)
    redirect_to :back
  end

  private
  def update_vote(new_value)
    @joke = Joke.find(params[:joke_id])
    @vote = @joke.votes.where(user_id: current_user.id).first

    if @vote
      @vote.update_attribute(:value, new_value)
    else
      @vote = current_user.votes.create(value: new_value, joke: @joke)
    end
  end
end

And finally this _voter.html.erb partial:

<div class="text-center col-xs-1">
  <% if current_user %>
    <div class="width: 100%"><%= link_to " ", joke_up_vote_path(joke), class: 'glyphicon glyphicon-chevron-up', method: :post, style: "margin-right: 0; margin-left: 0" %></div>
  <% else %>
    <div class="width: 100%"><%= link_to " ", new_user_session_path, class: 'glyphicon glyphicon-chevron-up', method: :post, style: "margin-right: 0; margin-left: 0" %></div>
  <% end %>
  <div class="width: 100%"><h3 style="margin-top: 0; margin-bottom: 0"><strong><%= joke.points %></strong></h3></div>
  <% if current_user %>
    <div class="width: 100%"><%= link_to " ", joke_up_vote_path(joke), class: 'glyphicon glyphicon-chevron-down', method: :post, style: "margin-right: 0; margin-left: 0" %></div>
  <% else %>
    <div class="width: 100%"><%= link_to " ", new_user_session_path, class: 'glyphicon glyphicon-chevron-down', method: :post, style: "margin-right: 0; margin-left: 0" %></div>
  <% end %>
</div>

Is shown on my otherjokes/kids.html.erb page:

<% @jokes.each do |joke| %>
  <div class="row">
    <% if joke.approved == true && joke.kids == true %>

    <%= render partial: 'votes/voter', locals: { joke: joke } %>

    <div class="col-xs-11"> <!-- non vote container -->

      <h2 id="joke-title" style="margin-top: 0px">
        <%= joke.title %>
        <% if joke.kids == true %>
          <%= image_tag 'icon_kids.jpg', style: "height: 25px; margin-left: 10px" %>
        <% end %>
        <% if joke.mixed == true %>
          <%= image_tag 'icon_mixed.jpg', style: "height: 25px; margin-left: 10px" %>
        <% end %>
      </h2>

      <p id="joke-body"><%= joke.body %></p>
      <p><strong>Submitted by: <%= joke.user.first_name %></strong></p>
      <% if current_user && (current_user == joke.user || current_user.admin) %>
        <%= link_to edit_joke_path(joke) do %>
          <span style="color: blue" class="glyphicon glyphicon-pencil" aria-hidden="true"></span><span style="color: blue">Edit Joke</span>
        <% end %>
        <%= link_to joke_path(joke), data: {:confirm => 'Are you sure?'}, :method => :delete do %>
          <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>Delete Joke
        <% end %>
      <% end %>
    </div> <!-- non vote container -->
  </div> <!-- joke row -->
  <% end %>
  <div class="row text-center">
    <hr style="width: 50%; margin-top: 30px; margin-bottom: 30px">
  </div> <!-- row -->

<% end %>

I have made the following adjustments to my routes as well:

  resources :jokes do
    patch :approve, on: :member
    patch :reject, on: :member
    post '/up-vote' => 'votes#up_vote', as: :up_vote
    post '/down-vote' => 'votes#down_vote', as: :down_vote
  end

Right now when I try to vote on a joke, I get unknown attribute 'joke' for Vote. called on the @vote = current_user.votes.create(value: new_value, joke: @joke) line at the end of my votes_controller .

I would love to get this system running in a way that it could be applied to other future models that will require voting and also (if possible) to do it "the Ruby way". Can anyone help me structure this properly?

As the error states, joke is not an attribute of Vote . Because it is a polymorphic association you gave it the generic name votable . Use this to create a Vote for a Joke .

@vote = current_user.votes.create(value: new_value, votable: @joke)

votable_id and votable_type will be derived automatically by the framework.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM