# Terminal
rails g scaffold comments user:belongs_to
rails g model emote user:belongs_to comment:belongs_to emoji
rails action_text:install
rails db:migrate
# models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :comments, dependent: :destroy
has_many :emotes, dependent: :destroy
# has_many :emoted_comments, through: :emotes, class_name: "Comment"
end
# models/emote.rb
class Emote < ApplicationRecord
belongs_to :user
belongs_to :comment
end
# models/emoji.rb
class Emoji
# [{key: emoji.png, text: Emoji}, {}]
# Emoji.all
def self.all
self.new.all
end
def all
list_of_emojis
end
private
def list_of_emojis
Dir.children(emojis_path).map { |file| emoji_hash(file) }
end
def emojis_path
Rails.root.join('app', 'assets', 'images', 'emojis')
end
def emoji_hash(file)
{ key: file, text: humanized(file) }
end
def humanized(file)
basename(file).humanize
end
def basename(file)
File.basename(file, File.extname(file))
end
end
# models/comment.rb
class Comment < ApplicationRecord
belongs_to :user
has_rich_text :content
has_many :emotes, dependent: :destroy
# has_many :emoters, through: :emotes, class_name: "User"
def emotes_size(key)
self.emotes.select { |e| e.emoji == key }.size
end
end
# views/comments/_form.html.erb
<%= form_with(model: comment, local: true) do |form| %>
<div class="field">
<%= form.rich_text_area :content %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
# views/comments/index.html.erb
<% @comments.each do |comment| %>
<div class='comment'>
<%= comment.content %>
<em class='meta'>
<%= comment.user.name %> |
<%= comment.created_at %> |
<%= link_to 'Destroy', comment, method: :delete, data: { confirm: 'Are you sure?' } %>
</em>
<div class='emojis'>
<% Emoji.all.each do |emoji| %>
<% size = comment.emotes_size(emoji[:key]) %>
<%= link_to comment_emote_path(comment, emote: emoji[:key]), class: "emoji #{size.zero? ? 'emoji-gray' : ''}" do %>
<%= image_tag File.join('emojis', emoji[:key]), size: '25x25', title: emoji[:text] %>
<%= content_tag :span, size, class: 'count' %>
<% end %>
<% end %>
</div>
</div>
<% end %>
<%= render 'form', comment: Comment.new %>
# controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :set_comment, only: :destroy
def index
@comments = Comment.includes(:emotes).all
end
def create
@comment = current_user.comments.new(comment_params)
if @comment.save
redirect_to comments_path, notice: 'Comment was successfully created.'
else
render :new
end
end
def destroy
@comment.destroy
redirect_to comments_url, notice: 'Comment was successfully destroyed.'
end
private
def set_comment
@comment = current_user.comments.find(params[:id])
end
def comment_params
params.require(:comment).permit(:content)
end
end
# controllers/emotes_controller.rb
class EmotesController < ApplicationController
def show
comment = Comment.find_by(id: params[:comment_id])
emote = current_user.emotes.find_or_initialize_by(comment: comment, emoji: params[:emote])
if emote.new_record?
emote.save
else
emote.destroy
end
redirect_to root_path
end
end
# routes.rb
resources :comments do
resource :emote, only: :show
end
# db/migrate/20200202022753_create_emotes.rb
class CreateEmotes < ActiveRecord::Migration[6.0]
def change
create_table :emotes do |t|
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :comment, null: false, foreign_key: true
t.string :emoji, null: false
t.timestamps
end
end
end
# comments.scss
.comment {
border: 1px dashed gray;
margin-bottom: 10px;
padding: 10px;
}
.comment .meta,
.comment .meta a {
color: gray;
}
.emojis {
margin-top: 10px;
}
.emoji img {
padding: 5px;
}
.emoji.emoji-gray img {
filter: grayscale(100%);
}
.emoji.emoji-gray img:hover {
filter: grayscale(0%);
}
.emoji {
position: relative;
}
.emoji .count {
position: absolute;
top: 10;
left: 0;
padding: 3px;
background-color: #269ba5;
text-decoration: none;
color: #ffffff;
border-radius: 20px;
font-size: 10px;
}