# Ruby(11/14) 五倍紅寶石(第八屆共筆)(2021/8/19)
## simple project:module 匯入匯出
package.json
= ruby 的 gem file
```json=
"scripts": {
"dev": "parcel src/index.html",
"build": "parcel build src/index.html"
}
// 用 yarn run dev 可以執行dev後面那段
// run 可以省略
```
aaa.js
```js
function hello() {
console.log("hello")
}
function world() {
console.log("world")
}
function hi() {
console.log("hi")
}
// 下面沒寫 export there,所以 main.js 那邊收不到,只會收到有寫的
function there() {
console.log("there")
}
// 讓main.js預設匯出 hello
// 等等 import 的名字可隨意取,不管怎麼取名預設就是匯出 hello
// 一個檔案只能有一個預設值
export default hello
// 有寫 export 的才能匯出,import 名字要能搭得上
// there 相對來說是私有方法
// 除了{}寫法,也可以直接在 function 前面寫 export
// 只要是變數都可以匯出,不一定要是 function
export { world, hi }
```
main.js
```js
import dayjs from "dayjs"
const dd = dayjs("2018-08-08")
console.log(dd.year())
console.log("hi")
import hello from "./aaa.js"
// aaa 取名隨意,會預設hello
import aaa, { hi } from "./aaa.js"
hello()
console.log(hi)
console.log(aaa)
import { createButton } from "./ui"
const btn = createButton("hello")
document.body.appendChild(btn)
```
ui.js
```js
// 用JS產生一個btn
function createButton(wording) {
const btn = document.createElement("button")
btn.textContent = wording
return btn
}
export { createButton }
```
### webpack
打包工具,設定上有點麻煩
### parcel
打包工具,輕鬆設定,對新手友善
可以把多個相同格式檔案打包成一個檔案
### electron
把 HTML CSS JS 打包成應用程式
因為也把瀏覽器包進去所以很胖
### 模組化
- CommonJS(主流)
export default name
import name from "aaaa"
- AMD
var a = require("aaaa")
a()
### npm
License:MIT、BSD ok
如果是GPL要小心,如果你用他的東西,他有權利要你公開原始碼
#### dayjs
[dayjs](https://www.npmjs.com/package/dayjs)
顯示日期套件
#### 安裝套件
npm install dayjs
= yarn add dayjs
兩個都可以安裝套件
yarn 可以一次下載多個,下載速度快(fb做的)
不要兩個混用,兩個產生 package.lock 的方式不同,有可能會造成錯誤
- 參數:
--save只用在目前專案,開發過程跟上線都會用到
--saveDev:上線之後用不到,不需要上線(ex:parcel)
= -D
devDependencies 上線不需要
dependencies 上線需要
### Tailwindcss
推薦用,慢慢變主流了
[tailwindcss](https://tailwindcss.com/docs/installation#installing-tailwind-css-as-a-post-css-plugin)
- Tailwindui 收費的,用Tailwind幫你刻好很多東西
- tailwindui alternative
=> tailwindcomponent
- codepen 有支援 tailwindcss 方便自己練習:可以點css做設定
[tailwindplay](https://play.tailwindcss.com/) :可試玩
#### Tailwind、Bootstrap 差異:
- bootstrap給你大框架,整組包好
- tailwind給你小零件,一堆小工具
- @apply 可以組合多個 css ,讓他寫一個就好,類似 funciton 把要的東西都包好,方便使用
- 壓縮檔案,把不會用到的 css 去掉:
在 tailwind.config.js 檔案的 purge 檢查:
purge: [
'./src/**/*.html',
'./src/**/*.js',
]
可參考:[優化生產模式
](https://tailwindcss.tw/docs/optimizing-for-production)
## fetch:
抓網站的 json ,會得到 Promise 物件。
### CORS 跨領域請求:
為了安全,只會擋瀏覽器的 JS
會檢查你的來源,如果不是他允許的就會擋掉
非同源(不是同個網站)或是沒開放CORS政策都會被擋掉
但不要用瀏覽器的 JS 還是可以抓得到他的 json
放在前端的東西都是公開的,如果用後端拿看不到密碼
### foreman
[foreman](https://rubygems.org/gems/foreman/versions/0.82.0?locale=zh-TW)
進階版的 rails s,用 node.js 寫的套件
rails 不擅長編譯,可以請 foreman 幫忙
- 使用 foreman 的四大好處:
1. 啟動 server
2. 快速編譯 JS
3. 即時更新(live server)
4. 如果自己的 JS 寫錯會出現編譯失敗
#### 安裝 foreman:
1. 在 Gemfile 的group :development, :test 裡面
新增 gem 'foreman', '~> 0.87.2'
2. 輸入 bundle install
3. 根目錄新增 Procfile 檔案
4. Procfile 裡面輸入:
web: rails server -p 3000
webpack: bin/webpack-dev-server
- Gemfile
- development:本地端開發
- Production:上線會用到的
- Test:測試環境
其實都放在上線也可,但缺點是會讓效能變慢
### API(Application Programming Interface):
重點在介面,現在演變成網路服務,回傳 JSON
#### Endpoint:
資料傳輸接點 = 請求的資源本身
.com/後面的字是 Endpoint
前面指的是那個網址本體,後面的 Endpoint 代表橋梁,負責接收傳遞資料
- 設計:
- 讓使用者清楚知道是 api:
POST /api/note/2/favorite
- 加版本會更好,如果要更新會比較有彈性:
POST /api/v2/note/2/favorite
### axios
[axios](https://github.com/axios/axios)
打 api 的套件,因為 JS 的 fetch 太陽春了,用 axios 會比較方便
用 yarn add axios 安裝
網站上線後還是要抓東西,所以 axios 會放在 dependencies
- package.json
- dependencies:會上線的放這
- devDependencies:不會上線的放這
### csrf
跨站請求偽造
對方寄連結給你,借你的權限來做事
csrf token:防止 csrf 的發生,因為對方沒有 token 還是一樣沒用,不能借你的權限來做事
## evernote 專案
routes.rb
```rb
# 後台管理路徑
# /admin/notes
# 可以用 namespace 去包
# namespace 單純用來包路徑,不會產生新路徑
# 可以產生/api/v1/notes/:id/favorite 路徑
# 用 member 不用 collection 是因為針對個別文章做事,非全體
# 路徑可以多產生 /:id
namespace :api do
namespace :v1 do
resources :notes, only: [] do
member do
post :favorite
end
end
end
end
```
- 用指令建立 controller:
rails g controller api/v1/notes
api/v1/notes_controller.rb
```rb
# 登入後才能加最愛
before_action :check_login!
def favorite
# 把東西存到 bookmarks,只放 note 的 id 進去讓他查找就好
# 如果 note id 存在就移除最愛
# 不存在就新增到最愛
# user has_many :favorite_notes,所以可以用 current_user.favorite_notes
# delete 後面只有一個參數,不是hash
# 順便找到剛剛用 axios 送的 id 讓它顯示出來
# render json: { status: "removed" }
# { status: "removed" } 裡面可隨意取名
# 如果出現 500 錯誤可以去看終端機的 server 內容
note = Note.find(params[:id])
if Bookmark.exists?(note: note)
# 移除最愛
current_user.favorite_notes.delete(note)
render json: { status: "removed", id: params[:id] }
else
# 新增最愛
current_user.favorite_notes << note
render json: { status: "added", id: params[:id] }
end
end
```
show.html.erb
```rb
<%# 用ORM撈@note.id%>
<a href="#" id="favorite_btn" data-id = <%= @note.id %>>
<div class="favorite_icon"></div>
</a>
```
application.js
```js
import Rails from "@rails/ujs";
import Turbolinks from "turbolinks";
// 把所有東西匯進來叫做 ActiveStorage
import * as ActiveStorage from "@rails/activestorage";
import "channels";
Rails.start();
Turbolinks.start();
ActiveStorage.start();
// 沒加 { } = 有預設值
import ax from "axios"
// 定義一個 addFavorite 函式
// 定義 url = 要按讚的筆記的網址
// 定義 token = 瀏覽器 meta 標籤裡面的 name 屬性的 csrf-token 的內文
// 用 axios 拿到合法 token,才有辦法去打 API,不然會被 Rails 擋住(Rails 的 csrf token 防護機制)
// 如果狀態是 added 就移除 favorite-off,新增 favorite-on class
function addFavorite(id) {
const url = `/api/v1/notes/${id}/favorite`;
const token = document.querySelector("meta[name=csrf-token]").content;
ax.defaults.headers.common["X-CSRF-Token"] = token;
ax.post(url)
.then((res) => {
const icon = document.querySelector(".favorite_icon");
if (res.data.status === "added") {
icon.classList.remove("favorite-off");
icon.classList.add("favorite-on");
} else {
icon.classList.remove("favorite-on");
icon.classList.add("favorite-off");
}
})
.catch((err) => {
console.log(err);
});
}
// 這邊用DOM監聽器會失效,是因為 Rails 有用 turbolinks,
// DOM 是整個網站重新載入時會運作,但使用 turbolinks 換網頁時只會換裡面的 body,
// 並不會重新載入網站,所以用 DOM 不會運作
// 實際運作:
// 這邊第一次進到的畫面是沒有灰色格子的,DOM 跑完不會撈到 btn 按鈕資料
// 到下一頁的時候,因為使用 turbolinks 所以不會重新載入,故一樣不會撈到 btn 按鈕資料
// 最後導致無法正常運作,應該要改成聽 turbolinks 事件才會正常運作
document.addEventListener("turbolinks:load", () => {
const btn = document.querySelector("#favorite_btn");
if (btn) {
btn.addEventListener("click", (e) => {
e.preventDefault()
// 拿到指定id
addFavorite(e.currentTarget.dataset.id)
});
}
});
```
## 專案
建議每個人都要看過別人寫的 code 再 merge
---
###### tags: `Ruby` `Rails`