Skip to content

Instantly share code, notes, and snippets.

@lortza
Last active February 26, 2018 19:04
Show Gist options
  • Save lortza/330ba4887b04964a55ee0ea3c881e32f to your computer and use it in GitHub Desktop.
Save lortza/330ba4887b04964a55ee0ea3c881e32f to your computer and use it in GitHub Desktop.
Setting up polymorphic "likes" on posts, photos, and comments
<!-- views/shared/_likes.html.erb -->
<% if object.has_likes? %>
<p class='likes'>
Likes: <%= object.likes_count %>
<%= list_likes(object.likes) %>...
</p>
<% end %>
# Use concerns to DRY up model associations
# models/likes.rb
class Like < ApplicationRecord
# ...
belongs_to :likeable, polymorphic: true
# ...
end
# models/comment.rb
class Comment < ApplicationRecord
#...
belongs_to :commentable, polymorphic: true
include Liking
#...
end
# models/photo.rb
class Photo < ApplicationRecord
# ...
# use concerns to DRY up model associations
include Liking
include Commenting
# ...
end
# models/post.rb
class Post < ApplicationRecord
# ...
# use concerns to DRY up model associations
include Liking
include Commenting
# ...
end
# models/concerns/liking.rb
module Liking
extend ActiveSupport::Concern
included do
has_many :likes, as: :likeable
end
def has_likes?
likes.any?
end
def likes_count
likes.count
end
end
# models/concerns/commenting.rb
module Commenting
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
def has_comments?
comments.any?
end
end
# controllers/likes_controller.rb
class LikesController < ApplicationController
def create
session[:return_to] ||= request.referer
set_parent
# create the like as a child in the collection of the parent class
@like = @parent.likes.new(user_id: current_user.id)
if @like.save
redirect_to session.delete(:return_to)
else
flash[:error] = "Something went wrong."
redirect_to session.delete(:return_to)
end
end
def destroy
session[:return_to] ||= request.referer
set_parent
author = @parent.user
like = Like.find(params[:id])
like.destroy
redirect_to session.delete(:return_to)
end
private
def like_params
params.require(:likeable).permit(:post_id, :comment_id, :user_id)
end
def set_parent
# Determine the parent class of the like by grabbing the text from the `likeable` param
klass_name = params[:likeable]
# Generate the name of the parent id param based on the likeable class
parent_key = params.keys.select{|k| k.match(klass_name.downcase + '_id')}.first
# Use the parent param name to get the value of the parent id
id = params[parent_key]
# Find the parent object by its id
@parent = klass_name.constantize.find(id)
end
end
# helpers/likes_helper.rb
module LikesHelper
def list_likes(likes)
# "likes" are limited to showing only 3, and the tricky part was displaying them as links that are comma separated
raw(likes.limit(3).map do |like|
link_to like.user.name, user_timeline_path(like.user)
end.join(", "))
end
# This method handles displaying links to create/destroy objects of multiple classes
def display_like_unlike(object)
klass = object.class.to_s
like = object.likes.to_a.find { |l| l.user_id == current_user.id }
if like == nil
# Use polymorphic_url instead of a url helper to build the url for liking and unliking of objects of different classes.
# The `likeable: klass` will be used by the controller to set the like's parent
link_to 'Like', polymorphic_url([current_user, object, :likes], likeable: klass), method: :post
else
link_to 'Unlike', polymorphic_url([current_user, object, like], likeable: klass), method: :delete
end
end
end
<!-- views/timelines/show.html.erb -->
...
# an "activity" can be a text "post" or a "photo" post
<% @timeline.activities.each do |activity| %>
<!-- this syntax will automatically render the correct text or photo post based on its class -->
<%= render activity %>
<% end %>
<!-- And those partials are: -->
<!-- 1) views/photos/_photo.html.erb -->
<% unless photo.new_record? %>
<article class="activity">
<%= render 'shared/activity_header', object: photo %>
<section class="activity-body">
<%= image_tag photo.photo_data.url(:feed) %>
</section>
<!-- the "liking" functionality lives here in the footer -->
<%= render 'shared/activity_footer', object: photo %>
</article>
<% end %>
<!-- 2) views/posts/_post.html.erb -->
<% unless post.new_record? %>
<article class="activity">
<%= render 'shared/activity_header', object: post %>
<section class="activity-body">
<p><%= post.body %></p>
</section>
<!-- the "liking" functionality lives here in the footer -->
<%= render 'shared/activity_footer', object: post %>
</article>
<% end %>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment