Search code examples
ruby-on-railsrubyvoting-system

Voting update on Ruby on Rails


Right now, I'm in the middle of building a social media app on Ruby on Rails, i have implemented a 5 point voting system. Where you can vote the news posted on the site from 1-5, what I'd like to know is, What is the best approach at handling the updates on the voting system.

In example. If a user already voted in an article I'd like to bring back score he gave in the article and soft-lock the voting (since i only allow 1 vote per user and i allow to change your vote at any time), but if he hasn't I'll bring up the article with the the voting on 0.

I know a way to accomplish this, i could do it in the view, and check if the current user has already voted on this article i would send them to the EDIT view otherwise to the SHOW view. (I think)

Anyways, what would be the "correct" approach to do this?

EDIT: I forgot to say that the voting combo box it's a partial that I'm rendering. Am i suppose to just update the partial somehow?

EDIT2:

class Article < ActiveRecord::Base

  has_many :votes
  belongs_to :user

  named_scope :voted_by, lambda {|user| {:joins => :votes, :conditions => ["votes.user_id = ?",  user]}  }
end

class User < ActiveRecord::Base
  has_many :articles
  has_many :votes, :dependent => :destroy

  def can_vote_on?(article)
    Article.voted_by(current_user).include?(article) #Article.voted_by(@user).include?(article)
  end

end

Solution

  • Create a method in the User model that responds true if the user can vote on an article:

    class User < ActiveRecord::Base
    
    ...
    
    def can_vote_on?(article)
      articles_voted_on.include?(article) # left as an exercise for the reader...
    end
    
    end
    

    In the view, render a form if the user can edit, otherwise render a normal view:

    <% if @user.can_vote_on?(@article) %>
      <%= render :partial => "vote_form" %>
    <% else %>
      <%= render :partial => "vote_display" %>
    <% end %>
    

    Or you could handle the whole thing in the controller, and render separate templates for the form version and the normal version. The best approach depends on the specifics of your situation.

    EDIT2

    As you discovered, current_user doesn't work in the model. This makes sense, because the can be called from migrations, libraries, etc., where there is no concept of a session.

    There's no need to access the current user anyway, since your instance method is (by definition) being called on an instance. Just refer to self in the model, and call the method from the view on current_user, which is an instance of User:

    (in the model)

      def can_vote_on?(article)
        Article.voted_by(self).include?(article)
      end
    

    (in the view)

    <% if current_user.can_vote_on?(@article) %>
    

    Or you could substitute @user for current_user if the controller assigns it.

    One last thing, I think your named scope should use user.id, like so:

    named_scope :voted_by, lambda {|user| {:joins => :votes, :conditions => ["votes.user_id = ?",  user.id]}  }