ruby-on-railsrubyvotepins

Rails: Allow users to upvote only once a pin


I have an upvote system in my rails app that allow users to upvote a Pin. But I would like to restrict the ability to upvote only once a Pin.

app/controllers/pins_controller.rb

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

app/models/pin.rb

class Pin < ActiveRecord::Base

    belongs_to :user

    has_many :votes, dependent: :destroy

    has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
    has_attached_file :logo, :styles => { :medium => "300x300>", :thumb => "100x100>" }

    end

app/config/routes.rb

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

I am not sure how to implement that as I tried to implement a system that allowed users to upvote only once, it's not what I want, I want them to be able to upvote a "PIN" only once. I know that the acts_as_votable gem provide this feature, but since I am not using it, I wanted to know if there is a way to implement that on my own code.

Any ideas?

UPDATE: this method only allow one vote per pins. see @Ege solution

get it to works with this:

def upvote
  @pin = Pin.find(params[:id])

  if @pin.votes.count == 0
     @pin.votes.create
     redirect_to(pins_path)
  else flash[:notice] =  "You have already upvote this!"
    redirect_to(pins_path)
end
end

Solution

  • You selected beautifulcoder's answer as the correct one, but you should be aware that it is potentially incorrect and it may not be obvious if you're new to Rails.

    You say a Pin should only have one vote, but presumably what you mean is that it should have one vote per user, as in, each user should be able to upvote a Pin only once. This is how voting mechanisms typically work.

    With beautifulcoder's answer, if I upvote a Pin, you will not be able to upvote it, because your controller will count the number of votes on the Pin, return 1 (because I upvoted it) and prevent you from upvoting. Furthermore, it will flash a message saying you have upvoted it, whereas you haven't!

    How to solve this problem? Fortunately, Rails makes this super easy. Your Vote is actually a Join Model in disguise. It establishes a relationship (i.e. association) between users and pins. A user can upvote a pin, and a pin can be upvoted by a user. In other words, votes "connect" users and pins! What you need to do is define this relationship by leveraging ActiveRecord Associations.

    Your Pin model would have this association added:

    class Pin < ActiveRecord::Base
    
      has_many :votes, dependent: :destroy
      has_many :upvoted_users, through: :votes, source: :user
    
      ...
    
    end
    

    This allows you to do stuff like @pin.upvoted_users and get a list of users who have upvoted that pin. Pretty nice if you want to be able to notify the pin owner!

    You also want to add a reverse association to your User model:

    class User < ActiveRecord::Base
    
      has_many :votes, dependent: :destroy
      has_many :upvoted_pins, through: :votes, source: :pin
    
      ...
    
    end
    

    And then change the Vote model like this:

    class Vote < ActiveRecord::Base
    
      belongs_to :user
      belongs_to :pin
      validates_uniqueness_of :pin_id, scope: :user_id
    
    end
    

    And finally in your controller, you would do:

    def upvote
      @pin = Pin.find(params[:id])
    
      if @pin.votes.create(user_id: current_user.id)
        flash[:notice] =  "Thank you for upvoting!"
        redirect_to(pins_path)
      else 
        flash[:notice] =  "You have already upvoted this!"
        redirect_to(pins_path)
      end
    end
    

    Voila! You now have a solution where users can upvote items, but only once per item.