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