We are creating a portfolio for job change activities. This time, we have implemented a like function that allows users to register articles as favorites. First, check if it works synchronously, and then move to asynchronous. I am writing an article for memorandum and review.
Ruby on Rails '6.0.0' Ruby '2.6.5' Introduced Font Awesome
-The user management function (user model) has already been implemented, and new registration and login are possible. -The article posting function (article model) has already been implemented, and users can browse articles.
Terminal
% rails g model like
Create a Like model and describe the migration file. This time, the columns will be the foreign keys use_id and article_id to store the user ID and post ID.
db/migrate/20201111111111_create_likes.rb
class CreateLikes < ActiveRecord::Migration[6.0]
def change
create_table :likes do |t|
t.references :article, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
In addition, the association will be as shown in the figure below. (One user can have multiple likes, and one article can have multiple likes)
app/models/like.rb
class Like < ApplicationRecord
belongs_to :article
belongs_to :user
end
app/models/user.rb
(Omitted)
has_many :article
has_many :likes, dependent: :destroy
app/models/article.rb
(Omitted)
has_many :likes, dependent: :destroy
By writing "dependent:: destroy", the associated likes will be deleted when the article is deleted or when the user is deleted.
Also, this time, by defining the "liked_by?" Method in the user model, we can determine whether the user likes it.
app/models/user.rb
(Omission)
has_many :article
has_many :likes, dependent: :destroy
def liked_by?(article_id)
likes.where(article_id: article_id).exists?
end
By using the where method, we are looking for the existence of "article_id" in the likes table. Also, the exists? Method is "true" if there is a corresponding value, otherwise " A method that returns "false". It seems that the method related to the interaction with the table will be put in the model. ..
Terminal
% rails g controller likes
Execute the command to generate a likes controller. Describe the create action and destroy action in the created controller, and set the grant and release of likes.
app/controllers/likes_controller.rb
class LikesController < ApplicationController
def create
Like.create(user_id: current_user.id, article_id: params[:id])
redirect_to root_path
end
def destroy
Like.find_by(user_id: current_user.id, article_id: params[:id]).destroy
redirect_to root_path
end
end
The find_by method used in the destroy action is a method that allows you to specify multiple search conditions.
config/routes.rb
post 'like/:id' => 'likes#create', as: 'create_like'
delete 'like/:id' => 'likes#destroy', as: 'destroy_like'
You can set a name for the routing by using the as option.
This time, in my implementation, I want to implement the like function in multiple places, so I use a partial template to describe the like view.
ruby:app/views/likes/_like.html.erb
<div class="likes_buttons">
<% if user_signed_in? %>
<% if current_user.liked_by?(@article.id) %>
<%= link_to destroy_like_path(@article.id, current_user.likes.find_by(article_id: @article.id).id), method: :delete do %>
<p class="like-button"><i class="fas fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
<% end %>
<% else %>
<%= link_to create_like_path(@article.id), method: :post do %>
<p class="like-button"><i class="far fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
<% end %>
<% end %>
<% end %>
</div>
I'm using Font Awesome to display a heart emoji. Also, <% = @ article.likes.count%> is used to display the number of likes.
This completes the synchronization like. Then move to asynchronous.
To implement asynchronously, add "remote: true" to the link_to method from the previous description. By adding remote: true, it seems that the parameters will be sent in JS format instead of HTML format. By giving this time, views/likes/create.js.erb will be called after the create action of likes_controller.rb.
ruby:app/views/likes/_like.html.erb
<div class="likes_buttons">
<% if user_signed_in? %>
<% if current_user.liked_by?(@article.id) %>
<%= link_to destroy_like_path(@article.id, current_user.likes.find_by(article_id: @article.id).id), method: :delete, remote: true do %>
<p class="like-button"><i class="fas fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
<% end %>
<% else %>
<%= link_to create_like_path(@article.id), method: :post, remote: true do %>
<p class="like-button"><i class="far fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
<% end %>
<% end %>
<% end %>
</div>
Here, the description of "redirect_to" that was described in the controller was deleted. If you leave it as it is, screen transition will occur and asynchronous processing will not be possible. Also, use "before_action" to get the id of the post.
app/controllers/likes_controller.rb
class LikesController < ApplicationController
before_action :article_params
def create
Like.create(user_id: current_user.id, article_id: params[:id])
end
def destroy
Like.find_by(user_id: current_user.id, article_id: params[:id]).destroy
end
def article_params
@article = Article.find(params[:id])
end
end
Generate the following two files and write the same.
ruby:app/views/likes/create.js.erb
$('.likes_buttons').html("<%= j(render partial: 'likes/like', locals: {article: @article}) %>");
ruby:app/views/likes/destroy.js.erb
$('.likes_buttons').html("<%= j(render partial: 'likes/like', locals: {article: @article}) %>");
Only the HTML of the likes_buttons part describes the process of partially updating with render.
After that, if you call the partial template in the part you like, it is completed.
ruby:app/views/articles/show.html.erb
<%= render partial: "likes/like", locals: { article:@article } %>
It seems that webpacker has become the standard from Rails6, and I had a hard time introducing jQuery, so I will describe it at the end. As a premise, it is assumed that yarn is already installed in mac. Also, this time my implementation also uses bootstrap 4, so I will proceed on that premise.
Terminal
% yarn add bootstrap jquery popper.js
First, execute the above command in the application directory you are creating.
Next, I will describe the introduction.
app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
...
//Fill in below
require("bootstrap");
...
Since bootstrap and jQuery are dependent, it seems that "require (" jquery ")" is also described at the same time as "require (" bootstrap ")".
Finally, I will describe the webpacker.
config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack');
environment.plugins.prepend(
'Provide',
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jQuery: 'jquery/src/jquery',
Popper: ['popper.js', 'default']
})
)
module.exports = environment
Now you can use bootstrap and jQuery on Rails.
I think there are some expressions that are difficult to understand, but I would like to make corrections as appropriate.
The articles that I referred to this time are as follows. Thank you very much. https://techtechmedia.com/favorite-function-rails/#i-2 https://qiita.com/naberina/items/c6b5c8d7756cb882fb20
Recommended Posts