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
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.