Rails: Allow users to upvote only once a pin
pins, ruby, ruby-on-rails, vote
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.
Problem
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 ```