# 20220810 Rails 無名小站實作 (Rich) JS處理留言板、按讚
###### tags: `Ruby` `Rails`
### SSR Server-Side Rendering
蝦皮與OB的差別:
蝦皮載入時,框框就會在了。
OB則是會將畫面撐開。
pchome是SSR
## 使用JS處理comment
action預設為找同名的view
其副檔名不一定要是html.erb ,也可以是js.erb
XXX.js.erb 可以在js放ruby code
<%= form_with model: {MODEL}, <mark>local: false</mark> do |f| %>
<% end%>
`<form ...... `<mark>data-remote="true"</mark>` ></form>`
之前我們是用轉址的方式去處理, 在form_with設定local :false之後, 我們原本打過去類型是html, 現在打過去在回傳的類型會變成js, 會找到js.erb檔案, 就可以做到前後端分離。

用這樣的方式會浪費記憶體空間
不要在迴圈裡面做render 效能不好
所以我們可以做的是用[render collection](https://guides.rubyonrails.org/layouts_and_rendering.html#rendering-collections)來改善
-----
### 補充:
完整寫法
```ruby=
<%= render partial: "comments/comment", collection: @comments, as: :comment %>
```
若滿足幾個條件便可寫簡化版
1. partial_view 剛好在 views/comments/_comment.html.erb
2. 區域變數為 comment
簡化版如下
```ruby=
<%= render @comments %>
```
---
_comment.html.erb 不只可以給 comment.html.erb, 也可以給 comment.js.erb 使用
## 按讚

https://drawsql.app/teams/kerkers-team/diagrams/first-diagram
- user跟LikeArticle多對多
- article跟LikeArticle多對多
為article新增按讚功能, 會需要第三方table LikeArticle, 同時紀錄user id 跟article id
model LikeArticle
```shell!
$rails g model LikeArticle user:belongs_to article:belongs_to
```
要在user.rb裡面加
```ruby
has_many :like_articles
has_many :liked_articles, through: :like_articles, source: :article
```
article.rb裡面加
```ruby
has_many :like_articles
has_many :users, through: :like_articles
```
### 資料表自我參照 self-reference
舉例:
主管跟員工都是員工, 所以只有一個員工的table就可以
留言跟回覆都是留言, 所以只有一個留言的table就可以
### namespace
想url做路徑:
>**api**/articles/:id/like
```ruby=
namespace :api do
resources :article, only:[] do
member do
post: like
end
end
end
```
```shell
rails g controller api/articles
```
設計api給別人用的時候,可以設計版1版2(v1, v2)
如果你要加功能時,就可以使用這些版號去區別新舊版, 釋出新功能時舊版本依舊可以使用, 舊的不會壞, 使用者經驗比較好
類似版本控制
> api/v1/articles/:id/like
```ruby=
namespace :api do
namespace :v1 do
resources :article, only:[] do
member do
post: like
end
end
end
end
```
rails g controller api/v1/articles
### 做按讚的按鈕
到app>javascript>packs>application.js寫js (全區都會被執行)

把import "channels"註解掉 因為這是做即時通訊功能用的
#### webpack 打包 加速效能
終端機下指令開啟功能 $ bin/webpack-dev-server 專門打包
(在資料夾bin 之下)
rails 不擅長打包,所以交給別人來做吧!
webpack -> 專門打包前端的工具, 有live reload的效果 VS code存檔畫面就會自動更新不用重新整理,可以加速執行效能
#### 安裝套件囉~~ <3 Foreman(可以雙開webpack 跟 rails server)
https://rubygems.org/gems/foreman/
```shell=
$ gem 'foreman', '~> 0.87.2'
$ bundle add foreman
```
開發用得到,但上線時用不到,所以將他放在Gemfile`development`
修改 gemfile 後:
```shell=
$ bundle install
```

所以下一次就不用在用rails s
避免部屬heroku會有問題蓋過原生Profile
- 改檔名Procfile => Procfile.dev

```shell=
$ foreman s -f
```
不打-f 的話,預設會去找procfile, 就會啟動失敗
但可以自己做一個`.foreman`檔
```yaml=
procfile: Procfile.dev
```
這樣的話只要啟動以下 就沒問題了
```shell=
$ foreman s
```
題外話:
**不要被副檔名騙了,重點是裡面寫了什麼**
### 監聽按鈕點擊事件
還記得DOMContentLoaded嗎?沒有的話抓不到按鈕~
```javascript=
document.addEventListener("DOMContentLoaded", () => {
// 監聽按鈕點擊事件
})
```
### 安裝套件囉~~ <3 Stimulus 可以導入ruby概念執行JS,簡化原生JS需要抓東西監聽的小框架
### [<img src="https://raw.githubusercontent.com/hotwired/stimulus/main/assets/logo.svg?sanitize=true" width="24" height="24" alt="Stimulus"> Stimulus](https://stimulus.hotwired.dev/)

可以先用rails --help去找看看有沒有可以裝的套件
```shell=
$ rails webpacker:install:stimulus
```
安裝完套件Javascript資料夾會多一個controllers資料夾
修改一下hello_controller.js(可自行改名)
```javascript=
export default class extends Controller {
connect(){
console.log(123);
}
}
```
在show.html.erb加上有data-controller的section把button包起來
```ruby=
<section data-controller="hello">
<button id="likeBtn">☆</button>
</section>
```
修改一下,把hello改成like
```ruby=
<section data-controller="like">
<button id="likeBtn" data-action="click->like#like_article">☆</button>
</section>
```
再修改一下hello_controller.js 名字改成like_controller.js
```javascript=
export default class extends Controller {
like_article(){
console.log("HI");
}
}
```
好多like喔~~所以命名很重要啊!!
:::warning
後端使用:
bundle add XXX => Gemfile
前端使用:
yarn add XXX => node_modules
:::
### 使用axios套件
`$ yarn add axios`
>Axios is a promise-based HTTP Client for node.js and the browser. It is isomorphic (= it can run in the browser and nodejs with the same codebase).
加上import axios from "axios" (前面的axios可自己改名,但後續引用也要跟著改)
```javascript=
import { Controller } from "stimulus"
import axios from "axios"
export default class extends Controller {
like_article(){
console.log('hi');
const token = document.querySelector("meta[name=csrf-token]").content
axios.default.headers.common["X-CSRF-Token"] = token
axios.post("/api/v1/articles/6/like").then((resp) => {
console.log(resp.data)
})
}
}
```
取得Token
```javascript=
document.querySelector("meta[name=csrf-token]").content
```
可以整理一下JS,在javascript的資料夾新增lib資料夾,再建一個http資料夾,再新建一個client.js
javascript/lib/http/client.js
增加的檔名可以自己取
把塞token的處理放到client.js裡面,後續其他地方要使用可以再import
```javascript
import ax from "axios";
const metaToken = document.querySelector("meta[name=csrf-token]")
if (metaToken) {
const token = metaToken.content
ax.defaults.headers.common["X-CSRF-Token"] = token
}
export default ax
```
外面導出後,datacontroller加上 data-article-id,才可以讓按鈕按下找到id編號
```ruby
# 兩個like都是指controller,like_article則是呼叫controller內的JS function
<section data-controller="like" data-article-id="<%= @article.id %>">
<button class="likeBtn" data-action="click->like#like_article">☆</button>
</section>
```
回去透過axios產生的controller,打API -> POST /api/v1/articles/:id/like
```javascript
import { Controller } from "stimulus"
import ax from "lib/http/client"
export default class extends Controller {
like_article() {
// 用this.element可以找到data controller控制的元素
//這裡的this是指like_controller.js
const articleID = this.element.dataset.articleId;
// 打API -> POST /api/v1/articles/:id/like
ax.post(`/api/v1/articles/${articleID}/like`).then((resp) => {
console.log(resp.data);
})
}
}
```
Ruby沒辦法把東西送給JS
所以用HTML當作媒介
