LondonGuy
LondonGuy

Reputation: 11098

How do I limit users to "liking" a post only once in ruby on rails?

Basically I have a page that lists 10 most recent microposts. Each post has a like button. When this like button is clicked the likes table in my database is updated.

Likes table:

+----+------------+--------------+-------------------------+-------------------------+---------+
| id | likable_id | likable_type | created_at              | updated_at              | user_id |
+----+------------+--------------+-------------------------+-------------------------+---------+
| 2  | 5770       | Micropost    | 2012-06-09 11:30:55 UTC | 2012-06-09 11:30:55 UTC | 2       |
| 3  | 5770       | Micropost    | 2012-06-09 11:42:45 UTC | 2012-06-09 11:42:45 UTC | 2       |
+----+------------+--------------+-------------------------+-------------------------+---------+

A user must only be able to like a micropost once. I can make this possible with some jquery/js by displaying an unlike button that points to a destroy path when ever a micropost is liked.

But is there a way to do this server side too? Like not allow a micropost to be liked more than once by any means necessary? So if I was to go into rails console and try to manually like a micropost I already liked it wouldn't work because it would see that I had already liked the micropost?

Like model:

class Like < ActiveRecord::Base
  belongs_to :likable, :polymorphic => true
  attr_accessible :likable_id, :likable_type, :user_id
end

Micropost model:

class Micropost < ActiveRecord::Base
    belongs_to :user
    has_many :likes, :as => :likable
end

Likes controller:

class LikesController < ApplicationController
  def create
    micropost = Micropost.find(params[:micropost])
     like = micropost.likes.build(:user_id => current_user.id)
     like.save
  end
end

Likes form:

<%= form_tag likes_path, :remote => true, :class => "like_micropost" do %>  
   <%= hidden_field_tag :micropost, micropost.id %>
       <%= submit_tag '', :class => "likeMicropostSubmit"  %> 
<% end %>

I previous tried this with no luck:

class LikesController < ApplicationController
  def create
    micropost = Micropost.find(params[:micropost])
    if micropost.likes.where(:user_id => current_user.id).nil?
     like = micropost.likes.build(:user_id => current_user.id)
     like.save
    end
  end
end

Kind regards

Upvotes: 1

Views: 1472

Answers (3)

mdesantis
mdesantis

Reputation: 8517

I would do two things:

  1. Move the creation logic to the model (fat model, skinny controller)
  2. Add a validation at the like creation (as jdoe already said)

So:

class LikesController < ApplicationController
  def create
    micropost = Micropost.find(params[:micropost])
    current_user.likes! micropost
  end
end

class Like < ActiveRecord::Base
  validates_uniqueness_of :user_id, :scope => [:likable_id, :likable_type]
end

class User < ActiveRecord::Base
  # exclamation mark to avoid confusion between things that are liked by the user
  def likes!(likable_object)
    unless likable_object.likes.where(:user_id => self.id).exists?
      like = likable_object.likes.build(:user_id => self.id)
      like.save
    end
  end
end

EDIT: Explanation:

1) Managing the Like creation phase is a model duty -> move the code that manages the creation to the model

2) Find a good method name for describe the action -> User.likes! something sounds good

3) Implement the creation method ->

def likes!(likable_object)
  like = likable_object.likes.build(:user_id => self.id)
  like.save
end

4) Add validation in order to manage the model valid/model invalid logic -> class Like; validates_uniqueness_of ...

5) Make the likes! method conscious of the valid/invalid logic ->

def likes!(likable_object)
  unless likable_object.likes.where(:user_id => self.id).exists?
    like = likable_object.likes.build(:user_id => self.id)
    like.save
  end
end

Upvotes: 0

jdoe
jdoe

Reputation: 15781

# in Like class
validates_uniqueness_of :user_id, :scope => [:likable_id, :likable_type]

This way you'll get validation error on trying to like something more than once.

See docs for it.

Upvotes: 5

LondonGuy
LondonGuy

Reputation: 11098

Ok this worked for me:

class LikesController < ApplicationController
  def create
    micropost = Micropost.find(params[:micropost])
    unless micropost.likes.where(:user_id => current_user.id).exists?
     like = micropost.likes.build(:user_id => current_user.id)
     like.save
    end
  end
end

Not having a create template is what was stopping it from working properly but the template is empty and un-used so wondering why it is mandatory.

Upvotes: -1

Related Questions