---
# System prepended metadata

title: 20220810 Rails 無名小站實作 (Rich) JS處理留言板、按讚
tags: [Rails, Ruby]

---

# 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檔案, 就可以做到前後端分離。

![](https://i.imgur.com/xwwwNPa.png)

用這樣的方式會浪費記憶體空間
不要在迴圈裡面做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://i.imgur.com/Mx60iKw.png)
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 (全區都會被執行)
![](https://i.imgur.com/6kchYGL.png)

把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
```

![](https://i.imgur.com/jZb6N7o.png)
所以下一次就不用在用rails s 


避免部屬heroku會有問題蓋過原生Profile

- 改檔名Procfile => Procfile.dev 
![](https://i.imgur.com/FBeHOKU.png)

```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/)

![](https://i.imgur.com/IN2oE4N.png =600x)

可以先用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當作媒介
![](https://i.imgur.com/SHLz4r8.png)
