I am currently attending a programming school called DMM WEB CAMP. Like being used on Instagram and Twitter for the portfolio, which is the issue for the third month Implemented hashtag-like. I hope it will be helpful for those who will implement it in the future.
Implement hashtags in Instagram-like captions made with Rails https://qiita.com/goppy001/items/791c946abdb41c9495bb
The general flow is the same as the above site, but the parts that did not work well have been changed.
Post table
Initially it was a table structure with only body and user_id, but we have added a hashbody column for entering hashtags. By the way, the image is saved in another table due to the specifications of the portfolio, but there is no problem even if the image column is in this table.
$rails g model Hashtag hashname:string
Create a model for hashtag storage. The hashtag is stored in the hashname column.
create_hashtags.rb
class CreateHashtags < ActiveRecord::Migration[5.2]
def change
create_table :hashtags do |t|
t.string :hashname
t.timestamps
end
add_index :hashtags, :hashname, unique: true
end
end
$ rails g model HashtagPostImage post_image:references hashtag:references
An intermediate table between the Hashtag table and the PostImage table. The command here is slightly different in the article I referred to. Since it is an intermediate table, bring the hashtag and postimage id as foreign keys. Since it is a references type, if you type hashtag_id at the time of creation Please note that the completed column name will be hashtag_id_id.
create_hashtag_post_images.rb
class CreateHashtagPostImages < ActiveRecord::Migration[5.2]
def change
create_table :hashtag_post_images do |t|
t.references :post_image, foreign_key: true
t.references :hashtag, foreign_key: true
end
end
end
My Great
$ rails db:migrate
hashtag.rb
class Hashtag < ApplicationRecord
validates :hashname, presence: true, length: { maximum: 50 }
has_many :hashtag_post_images, dependent: :destroy
has_many :post_images, through: :hashtag_post_images
end
For the time being, I set the upper limit to 50 characters.
hashtag_post_image.rb
class HashtagPostImage < ApplicationRecord
belongs_to :post_image
belongs_to :hashtag
validates :post_image_id, presence: true
validates :hashtag_id, presence: true
end
post_image.rb
class PostImage < ApplicationRecord
has_many :hashtag_post_images, dependent: :destroy
has_many :hashtags, through: :hashtag_post_images
end
post_image.rb
after_create do
post_image = PostImage.find_by(id: id)
#Detects hashtags typed into hashbody
hashtags = hashbody.scan(/[##][\w\p{Han}Ah-Gae-゚]+/)
hashtags.uniq.map do |hashtag|
#Hashtag is at the beginning#Save after removing
tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
post_image.hashtags << tag
end
end
#Update action
before_update do
post_image = PostImage.find_by(id: id)
post_image.hashtags.clear
hashtags = hashbody.scan(/[##][\w\p{Han}Ah-Gae-゚]+/)
hashtags.uniq.map do |hashtag|
tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
post_image.hashtags << tag
end
end
It is marked so that this action will be performed when creating and updating.
・ Post_image = PostImage.find_by (id: id) Lets you find the post you created.
・ Hashtags = hashbody.scan (/ [# #] [\ w \ p {Han} a-ga- ゚] +/) Here, search for the entered hashtag and the input value with [# #] at the beginning. hashbody is the column name of my DB, so this depends on the application. Any column for text entry in the post table will do.
・ Hashtags.uniq.map do |hashtag| #Hashtag is saved after removing the leading # tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#')) post_image.hashtags << tag end
By repeating with map, multiple hashtags are saved in postimage.
・ Post_image.hashtags.clear It seems that the hashtag is deleted once when updating.
routes.rb
get '/post_image/hashtag/:name' => 'post_images#hashtag'
get '/post_image/hashtag' => 'post_images#hashtag'
In my case, I wanted to create a hashtag list page, so I prepared two routes.
post_images_helper.rb
module PostImagesHelper
def render_with_hashtags(hashbody)
hashbody.gsub(/[##][\w\p{Han}Ah-Gae-゚]+/) { |word| link_to word, "/post_image/hashtag/#{word.delete("#")}",data: {"turbolinks" => false} }.html_safe
end
end
link_to word, "/post_image/hashtag/#{word.delete("#")}" The url here depends on the content of the application. It means that clicking the hashtag will take you to the url here. Type in the url you wrote in route earlier.
controllers/post_images_controller.rb
class PostImagesController < ApplicationController
def new
@postimagenew = PostImage.new
@postimagenew.post_image_images.new
end
def create
@postimagenew = PostImage.new(post_image_params)
@postimagenew.user_id = current_user.id
if @postimagenew.save
redirect_to post_images_path
else
render('post_images/new')
end
end
def destroy
@postimage = PostImage.find(params[:id])
@postimage.destroy
redirect_to post_images_path
end
private
def post_image_params
params.require(:post_image).permit(:body, :hashbody, :user_id, post_image_images_images: [], hashtag_ids: [])
end
Don't worry about post_image_images_images: [] for saving the images in the strong parameters as an array in another table. Hashtag_ids is entered because multiple hashtags are registered when creating PostImage.
View
views/post_images/new.html.erb
<div class= "row">
<div class="col-lg-2 col-md-2">
</div>
<div class="col-xs-12 col-lg-8 col-md-8 col-sm-12">
<div class= "postimage-new-box">
<% if @postimagenew.errors.any? %>
<div id="error_explanation">
<h3><%= @postimagenew.errors.count %>Could not post due to input error</h3>
<ul>
<% @postimagenew.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<h3>New post</h3>
<div class="previw">
</div>
<%= form_with model:@postimagenew, local: true do |f| %>
<div class="attachment-field">
<p>Select an image (multiple images can be specified)</p>
<%= f.attachment_field :post_image_images_images, multiple:true %>
</div>
<br>
<br>
<div class= "postimage-body-box">
<p>Please enter the details of the post</p>
<%= f.text_area :body, size:"55x12" %>
<br>
<p>Hashtag input field</p>
<%= f.text_area :hashbody, size:"55x3" %>
<br>
<div class= "postimage-new">
<%= f.submit "New post" ,class:'postimage-new-button' %>
</div>
<% end %>
</div>
</div>
</div>
<div class="col-lg-2 col-md-2">
</div>
</div>
The following is written in View.
ruby:post_images/show.html.erb
<%= render_with_hashtags(@postimage.hashbody) %>
The above calls the method created earlier with helper. By the way, the controller for post_images / show is here.
controllers/post_images_controller.rb
def show
@postimage = PostImage.find(params[:id])
end
I think that it is simply passing the information of the hashtag input field of @postimage to Hellber.
When you click it, it looks like this.
post_images_controller.rb
def hashtag
@user = current_user
if params[:name].nil?
@hashtags = Hashtag.all.to_a.group_by{ |hashtag| hashtag.post_images.count}
else
@hashtag = Hashtag.find_by(hashname: params[:name])
@postimage = @hashtag.post_images.page(params[:page]).per(20).reverse_order
@hashtags = Hashtag.all.to_a.group_by{ |hashtag| hashtag.post_images.count}
end
end
The notation here varies depending on the site you create. I wanted to create a page where you can see the hashtag list, so in the case of params [: name] .nil? There is a conditional branch that does not display post_image. Also, although it is group_by, hashtags can be displayed in the order of the number of posts associated with hashtag. It is written like this.
post_images/hashtag.html.erb
<div class="row">
<% if params[:name] == nil %>
<% else %>
<div class= "col-xs-12 col-lg-12 col-md-12 col-sm-12">
<div class="hashtag-post-box">
<h3 class="search-title">#<%= @hashtag.hashname %>: <%= @postimage.count %>Case</h3>
<div class="flex-box">
<% @postimage.each do |postimage| %>
<div class= "post-image-index-post-box">
<p class="index-post-box-top">
<%= postimage.created_at.strftime("%Y/%m/%d") %>
</p>
<span class='far fa-comments index-comment-count' id='comment-count_<%= postimage.id %>' style="color: #777777;">
<%= render 'post_image_comments/comment-count', postimage:postimage %>
</span>
<span id = "favorite-button_<%= postimage.id %>"class="post-box-top-favorite">
<%= render 'post_image_favorites/favorite',postimage: postimage %>
</span>
<%= link_to post_image_path(postimage),data: {"turbolinks" => false} do %>
<ul class="slider">
<% postimage.post_image_images.each do |post| %>
<li>
<%= attachment_image_tag post, :image ,size:'430x360', format:'jpg',class:"image" %>
</li>
<% end %>
</ul>
<% end %>
<p class="hashtag-post-box-name">
<%= link_to user_path(postimage.user) do %>
<%= attachment_image_tag postimage.user, :profile_image,size:'30x30', format:'jpg',fallback:'no_image.jpg',class:'min-image' %>
<span class="index-post-box-user"><%= postimage.user.name %>
</span>
<% end %>
</p>
<div class="image-show-body-hash" style="padding:2%">
<%= simple_format(postimage.body.truncate(50))%>
<% if postimage.body.length > 50 %>
<span class="text-prev"><%= link_to 'read more', post_image_path(postimage), data: {"turbolinks" => false} %>
</span>
<% end %>
</div>
</div>
<% end %>
</div>
</div>
<div class="image-index-pagination" data-turbolinks="false">
<%= paginate @postimage,class:"paginate" %>
</div>
</div>
<% end %>
</div>
<div class="row">
<div class= "col-xs-12 col-lg-12 col-md-12 col-sm-12">
<div class= "hashtag-name">
<% @hashtags.sort.reverse.each do |count| %>
<% count[1].each do |hashtag| %>
<p><%= link_to "##{hashtag.hashname} (#{hashtag.post_images.count})Case","/post_image/hashtag/#{hashtag.hashname}",data: {"turbolinks" => false} %>
</p>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
I'm sorry I'm confused because I've been using class names and so on. The important points are as follows.
post_images/hashtag.html.erb
<% if params[:name] == nil %>
<% else %>
<% end %>
With this notation, conditional branching is performed with post_image / hashtag and post_image / hashtag /: name written in route earlier. By writing the processing when params is nil in each of the controller and View, an error is prevented.
post_image/hashtag.html.erb
<div class= "hashtag-name">
<% @hashtags.sort.reverse.each do |count| %>
<% count[1].each do |hashtag| %>
<p><%= link_to "##{hashtag.hashname} (#{hashtag.post_images.count})Case","/post_image/hashtag/#{hashtag.hashname}",data: {"turbolinks" => false} %>
</p>
<% end %>
<% end %>
</div>
The hashtag list is displayed here. The display is displayed in descending order of posts linked to hashtags.
If you do not separate the hashtag input field, the hashtag will remain as a sentence in the description of the post, so I implemented it in the form of saving it in another column. The hashbody is intentionally hidden where the hashtag is displayed along with the post. It is turbolinks false that exists a lot on the view, but js does not work well and it is written, so you can ignore it.
I'm sorry if there are any parts that are difficult to understand in the first post. I hope it will be helpful for those who will create a portfolio from now on.
2020/7/1 Fixed the migration file of the intermediate table. There was a phenomenon that destroy was not possible when id False was set, so we have corrected it. At the same time, I added dependent :: destroy to the model has_many.
I also added a controller and a view for the hashtag and post storage part. We apologize for any inconvenience.
Recommended Posts