This article is The first step for a fledgling engineer! This is his 16th day article on Advent Calendar 2020.
This is the first article to participate in Advent Calendar. I am in my second year as an engineer.
The article of the like function itself has a feeling of brewing, but what I wanted to do is that it is difficult to write in JS because the code increases.
Create a like function.
First of all, the model is common to both patterns and looks like this.
app/models/like.rb
class Like < ApplicationRecord
belongs_to :movie
belongs_to :user
LIKED_COLOR = '#ff3366'.freeze
UNLIKED_COLOR = '#A0A0A0'.freeze
end
app/models/movie.rb
class Movie < ApplicationRecord
has_many :likes, dependent: :destroy
def like_by(user)
likes.where(likes: { user_id: user }).last
end
def liked_by?(user)
like_by(user).present?
end
end
Define routing.
config/routes.rb
resources :movies, only: :show
resources :likes, only: %i[create destroy]
The point of the controller is that if ** asynchronous ** create
, destroy
actions occur,
Since create.js.slim`` destroy.js.slim
is automatically render
, @movie
to be passed to each is defined by before_action
.
app/controllers/likes_controller.rb
class LikesController < ApplicationController
before_action :set_movie
def create
Like.create!(user_id: current_user, movie_id: params[:movie_id])
end
def destroy
Like.find(params[:id]).destroy!
end
private
def set_movie
@movie = Movie.find(params[:movie_id])
end
end
view will be written in slim this time. It doesn't matter, but the heart symbol is supposed to use fontawesome.
link_to
is
movie_id: movie.id
is defined as an argument so thatparams [: movie_id]
can be passed to the controller.remote: true
.app/views/movies/show.html.slim
#js-like-button
= render 'like', movie: @movie
app/views/movies/_like.html.slim
- if movie.liked_by?(current_user)
= link_to like_path(movie.like_by(current_user).id, movie_id: movie.id), method: :delete, remote: :true do
i.fas.fa-heart style="color: #{Like::LIKED_COLOR}"
- else
= link_to likes_path(movie_id: movie.id), method: :post, remote: :true do
i.fas.fa-heart style="color: #{Like::UNLIKED_COLOR}"
The notation of js.slim may be a little unique. Both create
and destroy
render the same content.
app/views/likes/create.js.slim
| document.getElementById('js-like-button').innerHTML = "#{j(render 'movies/like', movie: @movie)}";
app/views/likes/destroy.js.slim
| document.getElementById('js-like-button').innerHTML = "#{j(render 'movie/like', movie: @user)}";
Pattern 1 is now complete. Thanks to Rails, you can do it quickly.
Define routing. The create
and destroy
actions for the like feature are defined as APIs that pass JSON to JavaScript.
config/routes.rb
resources :movies, only: :show
namespace :api, format: :json do
namespace :v1 do
resources :likes, only: %i[create destroy]
end
end
Defines a controller for the API.
If you do not write skip_forgery_protection
, you will be caught in CSRF and you will not be able to hit the API from the JavaScript side. * I don't know much in detail, so I would appreciate it if an expert could teach.
app/controllers/api/v1/likes_controller.rb
module Api
module V1
class LikesController < ApplicationController
skip_forgery_protection
def create
like = Like.create!(user: current_user, movie_id: params[:movie_id])
render json: { like_id: like.id }
end
def destroy
Like.find(params[:id]).destroy!
render json: { } #It's a bit ugly, but I couldn't process it on the js side unless I returned json.
end
end
end
end
The view once shows whether or not it is currently liked.
input type ='hidden'
defines the parameters like_id
and movie_id
to receive in JS.
app/views/movies/show.html.slim
#js-like-button
- like_button_color = @movie.liked_by?(current_user) ? Like::LIKED_COLOR : Like::UNLIKED_COLOR
input type='hidden' id='like_id' value="#{@movie.like_by(current_user).id}"
input type='hidden' id='movie_id' value="#{@movie.id}"
i.fas.fa-heart style="color: #{like_button_color}"
Write a JS that hits the create
and destroy
APIs when the like button is clicked.
rgbTo16 ()
converts RGB colors to hexadecimal for comparison.POST (create)
or DELETE (destroy)
, but it may have been a subtle method.app/javascript/likes.js
document.addEventListener('turbolinks:load', () => {
const LIKED_COLOR = '#ff3366';
const UNLIKED_COLOR = '#a0a0a0';
const LIKE_ENDPOINT = '/api/v1/likes';
const rgbTo16 = rgb => {
return '#' + rgb.match(/\d+/g).map((value) => {
return ('0' + parseInt(value).toString(16)).slice(-2)
}).join('');
}
const sendRequest = async (endpoint, method, json) => {
const response = await fetch(endpoint, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
method: method,
credentials: 'same-origin',
body: JSON.stringify(json)
});
if (!response.ok) {
throw Error(response.statusText);
} else {
return response.json();
}
}
const createLike = movieId => {
sendRequest(LIKE_ENDPOINT, 'POST', { movie_id: movieId })
.then(data => {
document.getElementById('like_id').value = data.like_id
});
}
const deleteLike = likeId => {
const DELETE_LIKE_ENDPOINT = LIKE_ENDPOINT + '/' + `${likeId}`;
sendRequest(DELETE_LIKE_ENDPOINT, 'DELETE', { id: likeId })
.then(() => {
document.getElementById('like_id').value = '';
});
}
const likeButton = document.getElementById('js-like-button');
if (!!likeButton) {
likeButton.addEventListener('click', () => {
const currentColor = rgbTo16(likeButton.style.color);
const likeId = document.getElementById('like_id').value;
const movieId = document.getElementById('movie_id').value;
if (currentColor === UNLIKED_COLOR) {
likeButton.style.color = LIKED_COLOR;
createLike(movieId);
}
else {
likeButton.style.color = UNLIKED_COLOR;
deleteLike(likeId);
}
});
}
});
Don't forget to read the written file with application.js.
I always think, is it okay to write application.js with import ~ from ~
?
app/javascript/packs/application.js
require('../likes')
Pattern 2 is over. It's a whole JS code, but it's long sweat
The easy thing is to use what Rails provides.
I will paste the reference link when I remember it.
We would appreciate it if you could give us your impressions and reviews.
Recommended Posts