# 自學筆記
###### tags: `Web` `CSS` `5xRuby`
**所有內容版權均屬 eddie@5xruby.tw 所有**
# 目錄
[TOC]
---
# 1 - 打穩基本功的 10 堂課
## 1 - 1 相關資源
[讚放vue.js](https://cythilya.github.io/2017/04/11/vue-instance/)
免費圖庫:
* [Unsplash 高解析度免費圖庫素材](https://unsplash.com/)
* [Pexels](https://www.pexels.com/)
聲音檔:
* [Youtube 音效庫](https://www.youtube.com/audiolibrary/music)
影像檔:
* [Pexels](https://videos.pexels.com/)
畫圖工具:
* [Wireframe](https://whimsical.co/)
假圖產生器:
* [fakeimg](https://fakeimg.pl/)
線上icon:
- [cdn](https://cdnjs.com/libraries/font-awesome)
- [the icons](https://fontawesome.com/v4.7.0/icons/)
css-跨瀏覽器整合:
- [Normalize.css](https://necolas.github.io/normalize.css/)
javascrpit
- [js code視覺化流程](https://bogdan-lyashenko.github.io/js-code-to-svg-flowchart/docs/live-editor/)
Chart.js
- [json線上圖表製作](https://www.chartjs.org/)
---
## 1 - 2 html
### 1. lorem 標籤
常用於排版設計領域的拉丁文文章,主要目的是測試文章、或是文字在不同排版下看起來的效果,中文則被稱為「亂數假文」

### 2. List 標籤
清單內還可以再放其他清單
```html=
ul (unordered list):無順序
ol (ordered list):有順序
<ul>
<li>
<ol>
<li>
</ol>
</u>
```
---
### 3. 語意化標籤:
- 正確的標籤與結構, 可讓文字閱讀器或是搜尋引擎看得懂,提高被搜尋機會。
- **標籤 < strong >** =重要 ≠ < b >=標記粗體。
- **標籤 < em >** =強調 ≠ < i >=標記斜體。
- **標籤 < header >** =通常用於標題,可重複使用。
- **標籤 < nav >** =導覽列。
- **標籤 < articler >** =標示文章的主體部分,可獨立展示的文章or新聞。
- **標籤 < section >** =表示文章內容一部分如下圖,一篇文章有三個段落。
- **標籤 < aside> >** =用來作為側邊列/附屬的內容。
- **標籤 < footer >** =通常用於結尾,可重複使用。
範例網址:https://codepen.io/kaochenlong/pen/MLZyxm?editors=1000

---
### 4. 表單標籤
- HTML 表單
- **標籤 < form > :** 是用來讓使用者輸入資料,這些資料可以用來和使用者互動,例如表單內容填完後可以傳回遠端伺服器 (web server),像常見的連絡表單。
- **action:** 用來指定一個位址 (URL),告訴瀏覽器 (browser) 當 user 按送出表單後,要將表格的內容送去哪邊。
- **method:** 用來指定資料傳輸時用的 HTTP 協議,最常用的是 get 或 post
- **get** 通常用在資料量較小或非敏感的資料,因為資料會被放在網址中直接傳出,容易被直接看到資料。
- **post:** 會將表單資料放在 HTTP 傳輸封包 body 中送出。post 通常用在表單資料量比較大、有夾帶檔案上傳 (file upload) 或隱私性考量的資料。
- **表單欄位標題 < label > :** 用來給表單的控制元件一個說明標題,可以搭配的有 input, textarea, select, button, meter, output, progress 這些表單元件。
- **輸入欄位 < input > :** 用來建立不同用途的表單控制元件 (form control),主要是使用 < input> 上的 type 屬性來指明不同的用途功能,功能上例如可以用來做表單文字輸入欄位、表單核取方塊、表單送出按鈕等。
- **多行文字輸入欄位< textarea > :** 可以用來在表單 (form) 中,建立一個可以輸入多行文字的輸入框。
- **radio (radio button) 是選項按鈕**,用來處理表單中有多選一時的情況
- 同一組 radio button 的選項,需要都是一樣的 name。
- **核取方塊 (check box)** 用來讓使用者勾選某個選項是否成立,可以再搭配 value 屬性,來指定當使用者勾選此方塊時要傳送給遠端伺服器什麼值。
- **< select >下拉式選單**,讓使用者可以從一堆選項中選擇出一個或多個選項。
- < select > 本身做為選單的容器,在 select 裡面用 < option > 標籤來建立個別選項。
範例網址:https://codepen.io/Q_Qa/pen/VwaBBvE?editors=1000
---
### 4. 表格標籤
< table > 標籤 (tag) 用來建立表格,也就是用來呈現二維的資料表資訊。
< table > 標籤做為表格的容器 (container),裡面有不同用途的標籤像是 < tr >, < td > 組成一個完整的表格。

- 標籤 **< table >:** 來包著整個表格的結構和內容
- 標籤**t**able **r**ow < tr >:用來定義表格有幾個橫列 (row)
- 標籤**t**able **d**ata < td >:裡面就是放實際單元格的資料。
- 標籤**t**able **h**eading < th >: 用來表示表格欄位的標題,< th > 可以用來替代 < td > 使用,用來在語意上更明確的聲明這一格是一個標題。
- 主要用來增強表格 HTML 的語意性 (semantic),分別用來明確區分表格中的不同目的區塊
- 標籤 **< thead >**:表格表頭區塊
- 標籤 **< tbody >**:表格主要內容區塊
- 標籤 **< tfoot >**:表格頁腳區塊
範例網址:https://codepen.io/kaochenlong/pen/bzOwax?editors=1000
---
## 1 - 3 Css
### 1. 字型相關
- **font-size**: 文字大小
- **px**: 像素(pixel)
- **em**: 外層元素的 1.2 倍大(相對單位)
- **rem**: 最外層元素的 1.2 倍大(相對單位)
- **xx%**: 外層元素的 1.2 倍大(相對單位)
- **font-weight**: 粗細
- **normal**:正常字型,等於 400
- **bold**:粗體字型,等於 700
- **數字 200**:從 100 ~ 900 不等,數字越小字體越細
- **text-align**: 文字對齊
- **left**: 靠左
- **right**: 靠右
- **center**: 置中
- **justify**: 分散
- RGB = **R**ed, **G**reen, **B**lue:顏色
- **red**: 紅色
- **#ff0000**: 紅色
- **rgb(255, 0, 0)**: 紅色
- **grba(255, 0, 0, 0.7)**: 紅色 + 70%不透明度
- **background-color**: red; 紅色
- **寬度(width)、高度(height)**
- width: 300px : 設定寬度為300像素
- width: 80% : 設定寬度為 **上 層** 的 80% 大小
- height: 400px : 設定高度為400像素
- height: auto : 瀏覽器自己選擇適合的高度
### 2. **Box Model(邊框模型)**
- **inline-block**: 不能用width,height更改寬高度,須將屬性改成display,才能變更。
- 兩個 A 元素中間其實有「換行(Enter 鍵)」,雖然說 100 個換行也不會造成 100 行的間距,但 1 個換行卻會造成 1 個間距
-解結辦法 使用 flexbox 排版 (div4) 或是 把 font-size 設定成 0,再把 A 標籤的字型設定回來 (div)
-範例連結 : https://codepen.io/kaochenlong/pen/OqgKLM
- **box-sizing: border-box**
- 為了解決padding影響寬度造成破版的問題,Box-Sizing只決定一事件: 矩型元素在計算寬度及高度時,border及padding為內含還是外加?

---
### 3. 排版
- **float浮動排列**
CSS float 浮動功能用來設定網頁中各獨立元素與其他元素的關係,特別是 DIV 區塊的浮動排版更加實用,float 可以讓圖片、區塊、影片、欄位、表單、表格 ... 等元素,進行浮動排列
- CSS **float 浮動基本語法**
- 基本語法中的浮動參數有 left、right 以及 none
- ,『float:left;』代表向左浮動,『float:right;』代表像右浮動,而『float:none;』則代表元素不要浮動,在 DIV 區域使用 float 技巧,通常還會搭配 DIV 的寬度,來達成每個 DIV 正確排列的效果。
- **float浮動之後的物件,高度常常不能計算到所以搭配以下方法消除浮動效果。**
- css:`.clearfix{clear: both;}` 與html:`<div class="clearfix"></div>`
- **Flexbox 排版**
Flexbox 排版是個簡單又強大的排版工具,透過指定頁面上各組成部份佔據的空間、內容的對齊方式及元素視覺上的順序進行排版,能夠輕易的將內容進行水平和垂直的調整,也能夠沿著單一軸向排列,或是跨行等等。
- [FLEXBOX FROGGY 遊戲連結](https://codepip.com/games/)
- [教學連結](https://medium.com/%E7%8B%97%E5%A5%B4%E5%B7%A5%E7%A8%8B%E5%B8%AB/css-flexbox-%E4%BC%B8%E7%B8%AE%E8%87%AA%E5%A6%82%E7%9A%84%E6%8E%92%E7%89%88-%E5%9F%BA%E7%A4%8E%E8%A7%80%E5%BF%B5-85ffe3ae44b3)
- **RWD**
[**範例網址**](https://codepen.io/frank890417/pen/ALqNyG)


- **Bootstrap**
[**Bootstrap4 教程**](https://www.runoob.com/bootstrap4/bootstrap4-tutorial.html)

- **使用Skrollr.js**
Parallax Scrolling又名視差滾動,在江湖上已經流傳了這麼久,雖然是老梗但用在網頁上的效果一直很不錯,作前端工程師應該總會遇過設計或是需求用閃亮期待的眼神問你作不作得到。我們會在接下來的章節中利用工具實作視差滾動的的效果。
[**教學連結**](http://lala0812.logdown.com/posts/240837-skrollr-easily-implement-css3-html5-parallax-scrolling)

---
## 1 - 4 JavaScript
### 1. **Variable 變數**
- (**變數名稱**)本身不算資料型態。

- **資料型態(Data Type)**

- **undefined vs null**
- **undefined**意思是變數沒有被宣告,或者是已經宣告了,但是沒有賦值。
- **null**意思是「沒有值」的值。

- **Boolean / 布林值 / 真假值**

- **a = a + 1**

- **Javascript == 與 === 的差別**
- <font color=red>**=**</font> 這裡指的是指派不是<font color=red>**等於**</font>的意思,類似為**指派或是定義**的意思。

- <font color=red>**==**</font> 左右兩邊的值相等就回傳true
- <font color=red>**===**</font> 左右兩邊的值以及type均相等才回傳true

### 2. **if / switch條件**
- **關於布林值**:**0 / undefined / null / 空字串 / NaN** 以上這些被放到邏輯判斷的時候會被當成 <font color=red>**false**</font>
[**if / switch 教學連結**](https://audi.tw/Blog/JavaScript/javascript.conditional.statements.asp)
- **函數 function**
函數 (function) 用來將會**重複使用的程式碼封裝在一起,方便重複執行**

- **回傳值 return**
[**教學連結**](https://www.fooish.com/javascript/function.html)

- **變數提昇 Hoisti**
[**教學連結**](https://medium.com/@hugh_Program_learning_diary_Js/%E9%A1%8D%E5%A4%96%E8%A3%9C%E5%85%85-3779505a3216)
### 3. **陣列 Array**
JavaScript 中的 Array 全域物件被用於建構陣列;陣列為高階(high-level)、似列表(list-like)的物件。陣列在Javascript 裡面並沒有固定的長度與型別
[**詳細教學連結**](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array)
```javascript=
var myHeroes = ['孫悟空','魯夫','宇智波佐助','一拳超人'];
console.log(myHeroes[0]); // 印出第 1 位「孫悟空」
console.log(myHeroes[1]); // 印出第 2 位「魯夫」
console.log(myHeroes[2]); // 印出第 3 位「宇智波佐助」
var myHeroes = ['孫悟空','魯夫','宇智波佐助','一拳超人'];
myHeroes.push('布羅利'); // push 進來的會加在最後面
myHeroes.unshift('美國隊長'); // unshift 進來的會放在最前面
var myHeroes = ['孫悟空','魯夫','宇智波佐助','一拳超人'];
myHeroes.pop(); // 從最後面取出一個元素,原陣列元素會少一個
myHeroes.shift(); // 從最前面取出一個元素,原陣列元素會少一個
// 可以尋找陣列裏面是否有該值
var myHeroes = ['孫悟空','魯夫','宇智波佐助','一拳超人'];
console.log(myHeroes.indexOf('達爾')); // 印出 -1(代表沒有該值)
console.log(myHeroes.indexOf('宇智波佐助')); // 印出 2
```
- **迴圈 for/while**
[**詳細教學連結**](https://medium.com/%E9%A6%AC%E6%A0%BC%E8%95%BE%E7%89%B9%E7%9A%84%E5%86%92%E9%9A%AA%E8%80%85%E6%97%A5%E8%AA%8C/js-%E8%BF%B4%E5%9C%88%E7%AD%86%E8%A8%98-23defbe9e871)

### 4. **Event Listene 事件監聽者**
- **Event Listene 事件監聽者**
[**詳細教學連結**](https://ithelp.ithome.com.tw/articles/10219521)
事件是 JavaScript 應用的心臟,也是把所有東西黏在一起的膠水,當我們與網頁進行某些互動時,事件就發生了。
- **preventDefault()** 是幹嘛的? :
- [**教學連結**](https://ithelp.ithome.com.tw/aticles/10198999)
- 他的作用是停止事件的默認動作,例如有時候我們會利用連結的 **< a >**,他本身DOM就擁有連結的功能,但是有時候我們會為他新增類似**onclick**的事件,而只要在該 **< a >** 觸發的事件中加入<font color=red>**event.preventDefault()**</font>,就不會在執行他默認的動作,也就是不會再執行「連結到某個網址」這個動作。
**基本語法:**
```javascript=
//------------寫法1-----------//
//先用js, 把要操作html抓出來。
var someID = document.getElementById('someID');
//事件監聽( 要做什麼事件 , 事件發生之後做的動作 )
someID.addEventListener("click", function(event) {
//停止事件的默認動作
event.preventDefault();
console.log("我被執行了 QQ")
});
//------------寫法2-----------//
//先用js, 把要操作html抓出來。
var someID = document.getElementById('someID');
someID.onclick = function (event) {
//停止事件的默認動作
event.preventDefault();
console.log("我被執行了 YY")
};
//--寫法3 非侵入式JavaScript---//
var zone = document.querySelector('#zone');
//!!注意!! sayHello 函數 不需要加();
zone.onclick = sayHello; // 寫法 1
zone.addEventListener("click", sayHello); // 寫法 2
function sayHello() {
console.log('Hello');
}
```
- **JavaScript Object (物件)**
JavaScript 物件 (object) 是一個複合資料型態 (composite data type),可以儲存不定數量的鍵值對 (key-value paris),而一組鍵值對我們稱做物件的一個屬性 (property)。一個屬性的值 (value) 可以是任何資料型態 (也可以是函數);而屬性的名稱 (key / name) 是一個字串型態。
**基本寫法:**
```javascript=
var hero1 = {
name: '孫悟空',
skill: ['龜派氣功','元氣彈','瞬間移動'],
power: 1000000
}
console.log(hero1['name']); // 印出「孫悟空」
console.log(hero1.name); // 印出「孫悟空」(推薦寫法)
console.log(hero1.skill[1]); // 印出「元氣⽟」
```
### 5. **JavaScript 函數建構式**
- **JavaScript 物件導向**
使用 function 來建立物件樣板 (只能使用 function,亦不能使用箭頭函式)。
這個結構與前方所使用的結構接近,不同的是:
[**教學連結**](https://wcc723.github.io/javascript/2017/12/18/javascript-constructor/)
- **this 則是代表此物件的屬性**
- **可以透過參數來傳入數值**
- **使用 new 來套用此樣板,且最終一樣會產生物**
**基本寫法:**
```javascript=
function Pokemon(name, skill, sex, weight) {
//this.---> 指的是pokemon本身
this.name = name;
this.skill = skill;
this.sex = sex;
this.weight = weight;
// -----寫法 "1" 裡面也可以寫函數;---------//
this.attack = function(){
console.log(`攻擊吧! ${this.name} 使用${this.skill}`);
}
}
// --寫法 "2" 用prototype幫函數增加額外功能--//
Pokemon.prototype.body = function(){
console.log(`基本資料 : ${this.name} 性別 : ${this.sex} 體重 : ${this.weight}`);
}
// new 不要忘了加,會出錯
var pikachu = new Pokemon("皮卡丘","十萬伏特","男","30");
var frog = new Pokemon("妙蛙種子","藤鞭","女","45");
console.log(pikachu.name); // 印出 "皮卡丘"
console.log(pikachu.skill); // 印出 "十萬伏特"
pikachu.attack(); // 印出 "攻擊吧! 皮卡丘 使用十萬伏特"
frog.body(); // 印出 "基本資料 : 妙蛙種子 性別 : 女 體重 : 45"
```
### 6. **JS - ES6基礎**
- **JS - ES6基礎**
- <font color="red">**Var vs let的差別**</font>
之前寫 JavaScript 總是用 var 宣告變數,但使用 var 宣告的變數,會汙染全域變數。
let的作用域不一樣,**var的作用域在函數 (function) 裡,let的作用域則是在區塊 (block) 裡。**
```javascript=
//使用var的情況
var i = 20;
for(var i = 0; i < 3; i++){
console.log(`hi, ${i}`);
}
// 這裡會印出迴圈的"i" = 5 , 用var宣告的話會汙染上面宣告的"i"
console.log(i);
//------------------------------------//
//使用let的情況
let j = 20;
for(let j = 0; j < 3; j++) {
console.log(`hi, ${j}`);
}
// 這裡會印最上面宣告的"j" = 20,用let宣告的話只會存在區塊裏
// 不會汙染到上面宣告的"j"
console.log(j);
```
-
- **const 的特性**
const 是唯讀變數(不能去做修改),常用在一些不能被變更的變數。
例如: url 網址、圓周率 ( PI = 3.14159 )。
不過有例外,就是 { 物件 } 跟 [ 陣列 ] 還是會被變更,這方面可用freeze()方法解決。
```javascript=
const age = 18;
console.log(age); // 印出 18
age = 20; // 試著把 age 改成 20 (會發生錯誤)
console.log(age); // 還是印出 18,都不會老!
```
-
- **ES6版本 陣列簡化寫法**
使用 <font color="red">**forEach**</font> 可以很方便地用來索引陣列資料結構裡的元素。其語法如下:
[**教學連結**](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
```javascript=
var friends = ['Ross', 'Joey', 'Andy', 'Monica'];
//---基本 for 迴圈寫法---//
for (var i = 0; i < friends.length; i++) {
console.log(friends[i]); //印出上面名子
};
//---forEach 簡化寫法---//
friends.forEach(function(friend){
console.log(friend); //印出上面名子
});
//---箭頭函數 指向性簡化寫法---//
friends.forEach((friend) => {
console.log(friend); //印出上面名子
});
```
-
- **ES6版本 <font color="red">物件導向(class) {}</font> 寫法**
[**教學連結**](https://shubo.io/javascript-class/)
基本語法:
```javascript=
//----------基本寫法 1----------//
function Pokemon(name, skill) {
this.name = name;
this.skill = skill;
}
//----------物件導向寫法 2----------//
class Pokemon {
constructor(name, skill) {
this.name = name;
this.skill = skill;
}
Pokemon.prototype.attack = function() {
//slice(從0,到2)=>擷取字串 repeat(2)=>重複兩次
var sound = this.name.slice(0, 2).repeat(2);
console.log("攻擊吧!" + this.name + "!使出" + this.skill +
" 「" + sound + "」");
}
var pikachu = new Pokemon("皮卡丘","十萬伏特");
pikachu.attack(); // 印出 "攻擊吧!皮卡丘!使出十萬伏特 「皮卡皮卡」"
```
### 7. JS 基礎 - AJAX
全名叫『**A**synchronous **J**avaScript **a**nd **X**ML』
- 什麼是同步?與非同步?
- 同步(synchronous)需要等對方回應再做下一件事。
- 非同步(asynchronous)不需要等對方回應就能做下一件事。
- 如果想等 A 結束之後再進行 B..?**(Promise)**
```javascript=
// 成功會執行的function↓↓ ↓↓失敗會執行的function
let skills = new Promise((resolve, reject) => {
// 可能是個需要花時間的動作...
setTimeout(() => resolve('元氣彈'), 3000)
})
skills
// ↓↓如果 promise 成功的話...
.then(招式 => console.log(`使用招式:${招式}`))
// ↓↓如果 promise 失敗的話...
.catch(err => console.log(`${err}無法使用`))
```
- **什麼是 API?**
API = **A**pplication **P**rogramming **I**nterface,最後一段字為重點Interface(介面),現在好像都被當成「資料來源」、以說好的格式json、xml等等,來互相交換。
- 常見的 Web API 回應格式 **JSON / CSV / XML**
- **JSON** = JavaScript Object Notation ( JavaScript物件表示法 )
- JSON 是個以純文字為基底去儲存和傳送簡單結構資料,你可以透過特定的格式去儲存任何資料(字串,數字,陣列,物件),也可以透過物件或陣列來傳送較複雜的資料。
```json=
{
"name" : "Molecule Man",
"age" : 29,
"secretIdentity" : "Dan Jukes",
"powers" : [
"Radiation resistance",
"Turning tiny",
"Radiation blast"
]
}
```
- **CSV** = Comma-Separated Values
- 使用逗點符號分開的資料,一個欄位一個逗點,每個逗點代表一個欄位。
```csv=
Joanne,1,2016/7/6,副總,0928666777,台北市衡陽路 7 號 5 樓
Sherly,2,2014/3/3,行政,0928666888,台北市衡陽路 7 號 5 樓
Kitty,3,2015/7/8,財務,0928666999,台北市衡陽路 7 號 5 樓
```
- **XML** = Extensible Markup Language 「可延伸標示語言」
```xml=
<news>
<to>日本</to>
<from>台灣</from>
<heading>鈴木一朗要退休了</heading>
<body>不.................................!</body>
</news>
```
- 抓取網路資料,使用**fetch**()抓取資料,並且回傳一個 **Promise**,接著使用**then**()可以接著處理 Promise。
```javascript=
function get_data(){
fetch("網址")
.then(function (res){
return res.json()
})
.then(function(data_not){
console.log(data_not)
})
}
get_data();
```
## 1 - 5 Git
git 是一個分散式版本控制軟體,最初由林納斯·托瓦茲創作,於2005年以GPL釋出。最初目的是為更好地管理Linux核心開發而設計。應注意的是,這與GNU Interactive Tools[5](一個類似Norton Commander介面的檔案管理器)不同。
### 1. 相關資源
- [Git安裝教學](https://gitbook.tw/)
- [介面Sourcetree 安裝教學](https://ithelp.ithome.com.tw/articles/10206852)
- [Git小遊戲](https://learngitbranching.js.org/)
### 2. 常用終端機指令介紹

### 3. 設定 Git
- [使用者設定](https://gitbook.tw/chapters/config/user-config.html)
- [工作區、暫存區與儲存庫](https://gitbook.tw/chapters/using-git/working-staging-and-repository.html)
- [把檔案給Git控管(add / commit)](https://gitbook.tw/chapters/using-git/add-to-git.html)
- [檢視紀錄](https://gitbook.tw/chapters/using-git/log.html)
- [如何在 Git 裡刪除檔案或變更檔名?](https://gitbook.tw/chapters/using-git/rename-and-delete-file.html)
- [新增目錄?](https://gitbook.tw/chapters/using-git/add-folder-to-git.html)
- [等等,這行程式誰寫的?](https://gitbook.tw/chapters/using-git/git-blame.html)
### 4. 使用分支
- [為什麼要使用分支?](https://gitbook.tw/chapters/branch/why-use-branch.html)
- [開始使用分支](https://gitbook.tw/chapters/branch/using-branch.html)
- [合併分支](https://gitbook.tw/chapters/branch/merge-branch.html)
- [另一種合併方式(使用 rebase)](https://gitbook.tw/chapters/branch/merge-with-rebase.html)
- [剛才的 Commit 後悔了,想要拆掉重做…](https://gitbook.tw/chapters/using-git/reset-commit.html)
- [不小心使用 hard 模式 Reset 了某個 Commit,救得回來嗎?](https://gitbook.tw/chapters/using-git/restore-hard-reset-commit.html)
- [使用標籤](https://gitbook.tw/chapters/tag/using-tag.html)
- [修改歷史訊息](https://gitbook.tw/chapters/rewrite-history/change-commit-message.html)
- [把多個 Commit 合併成一個 Commit](https://gitbook.tw/chapters/rewrite-history/merge-multiple-commits-to-one-commit.html)
- [把一個 Commit 拆解成多個 Commit](https://gitbook.tw/chapters/rewrite-history/split-one-commit-to-many-commits.html)
### 5. 遠端共同協作 - 使用 GitHub
- [GitHub 是什麼?](https://gitbook.tw/chapters/github/what-is-github.html)
- [Push 上傳到 GitHub](https://gitbook.tw/chapters/github/push-to-github.html)
- [Pull 下載更新](https://gitbook.tw/chapters/github/pull-from-github.html)
- [怎麼有時候推不上去…](https://gitbook.tw/chapters/github/fail-to-push.html)
- [與其它開發者的互動 - 使用 Pull Request(PR)](https://gitbook.tw/chapters/github/pull-request.html)
- [有些檔案我不想放在 Git 裡面…](https://gitbook.tw/chapters/using-git/ignore.htmlhttps://)
- [聽說 git push -f 這個指令很可怕,什麼情況可以用它呢?](https://gitbook.tw/chapters/github/using-force-push.html)
- [使用 GitHub 免費製作個人網站](https://ray247k.blogspot.com/2018/05/github-pages.html)
## 1 - 6 Ruby
### 1. 印出 Hello, World與註解
[<font color="green">**教學連結**</font>](https://railsbook.tw/chapters/05-ruby-basic-1.html#getting-started)
```ruby=
# 印出 Hello, World 字樣
print "Hello, World"
# 印出 Hello, World 字樣,並在結尾加上換⾏
puts "Hello, World"
# 印出 "Hello, World" 字樣(含雙引號),並在結尾加上換⾏
p "Hello, World"
=begin
這是多行註解
這裡的內容不會被執⾏
這裡的內容不會被執⾏
這裡的內容不會被執⾏
=end
# 這是單行註解
# 這裡的內容不會被執⾏
# 這裡的內容不會被執⾏
# 這裡的內容不會被執⾏
```
### 2. 變數與常數
[<font color="green">**教學連結**</font>](https://railsbook.tw/chapters/05-ruby-basic-1.html#variable-and-constant)
- 依照下面程式碼變數就像<font color="red">**標籤**</font>,本身沒有型態
>
```ruby=
# 標籤 = ""
message = "你好"
puts message
# 印出「你好」字樣
# 變數指定
a = 1
comic = "七龍珠"
# 一次指定多個變數 x, y, z =[1,2,3]
# 使用變數
puts a # 印出 1
puts z # 印出 3
puts comic # 印出七龍珠
```
- **變數種類**
| | 區域變數 | 全域變數| 實體變數 | 實體變數 |
| -------- | ------ | ------ | ------- | ------- |
| 命名樣式 |username|$username|@username|@@username|
- **使用常數**
在Ruby中, 大寫字母開頭的就是常數。
<font color="red">**注意**</font> Ruby 世界的常數是可以被修改的
```ruby=
BOOK = "ruby book"
User = "hello, user"
```
### 3. 字串與數字
```ruby=
name = "kk"
age = 18
puts "hi, I am #{name}, and I am #{age} years old"
# 那如果這樣呢?
# ''單引號不會帶入跟翻譯,執行錯誤。
puts 'hi, I am #{name}, and I am #{age} years old'
# -----字串的引號-----
# 正常執行
puts "Hi, I'm 28 years old"
# 語法錯誤,執行錯誤 ''單引號會先找最近的做為結束。
# puts 'Hi, I'm 28 years old'
# 正常執行, I\'M >可以跳脫字元
puts 'Hi, I\'m 28 years old'
# -----單引號與雙引號-----
# %Q 等於 ""雙引號
puts %Q(Hi, I'm 28 years old) # 正常執行
# %q 等於 ''單引號
puts %q(Hi, I'm 28 years old) # 正常執行
```
- **整數與浮點數**
<font color="red">**注意**</font>在 Ruby 裡,數字其實也是「物件」
```ruby=
# -----整數與浮點數-----
puts 3.55.round # 轉成整數,四捨五入
puts 3.74.floor # 轉成整數,無條件捨去
puts 3.14.ceil # 轉成整數,無條件進位
puts 3.14.to_i # 轉成整數,無條件捨去
# -----整數除法-----
puts 10/3 # 整數除以整數得到整數 3
puts 10.0/3 # 3.3333333
puts 10/3.0 # 3.3333333
puts 10.0/3.0 # 3.3333333
```
#### 3-2. 邏輯判斷與流程控制(if /case..when)
[<font color="green">**教學連結**</font>](https://railsbook.tw/chapters/05-ruby-basic-1.html#flow-control)
在Ruby中只有 <font color="red">**nil(沒有/不存在)**</font> 跟 <font color="red">**false**</font> 是假的其它的都是真的。(**例:空字串,空集合,-1,0**)
```ruby=
# 一個=是指定、二個==是比對、三個===有時是比對有時是別的事情。
a = 10 #數字10
b = "10" #字串10
p a == b # 印出 false
p a === b # 印出 false
# -----if 倒裝句-----
weather = "下雨"
if weather == "下雨"
puts "宅在家裡"
end
# 與以下程式相等(在ruby裡常用) 倒裝句↓
puts "宅在家裡" if weather == "下雨"
# -----unless-----
weather="出太陽"
if not weather =="下雨"
puts "耶!出去玩!"
end
# 與以下程式相等
unless weather =="下雨"
puts "耶!出去玩!"
end
# 與以下程式相等 倒裝句↓
puts "耶!出去玩!" unless weather == "下雨"
# -----三元運算子-----
age = 19
if age >= 18
status = "已成年"
else
status = "未成年"
end
# 與以下程式相等
status = (age >= 18) ? "已成年" : "未成年"
# ---if .. elsif .. else ..---
weather = "下雨"
if weather == "下雨"
puts "宅在家裡"
elsif weather == "出太陽"
puts "出去玩!"
else
puts "睡覺!"
end
# -----case... when...-----
age= 10
# if寫法
# ↓而且的意思
if age >=0 && age <=3
puts "嬰兒"
elsif age >=4 && age <= 10
puts "兒童"
elsif age >= 11 && age <= 17
puts "青少年"
else
puts "成年"
end
# case寫法1
case
when age >=0 && age <=3
puts "嬰兒"
when age >=4 && age <= 10
puts "兒童"
when age >= 11 && age <= 17
puts "青少年"
else
puts"成年"
end
# case-ruby寫法
case age
# 0..3>範圍
when 0..3
puts "嬰兒"
when 4..10
puts "兒童"
when 11..17
puts "青少年"
else
puts "成年"
end
```
<font color="blue">**【冷知識1】**</font>事實上 Ruby 裡其實並沒有 case .. when ..,這樣的寫法其實只
是 if .. elsif .. else 的語法糖衣。
<font color="blue">**【冷知識2】**
</font>• 其實 true, false 跟 nil 都是物件。
• nil 是什麼東西?<font color="red">也是一個物件,代表**不存在**</font>
- **例外處理**
有時候在撰寫程式碼,語法沒有錯,但在代入值帶了一些奇怪數值。
```ruby=
def bmi_calculator(height, weight)
weight / (height * height)
end
# 算bmi,代0不能當分母所以錯誤
p bmi_calculator(0, 80) # 出現 ZeroDivisionErro
# -----例外處理-----
def bmi_calculator(height, weight)
begin
weight / (height * height)
#有例外的時候
rescue "輸入的數字有問題"
end
end
p bmi_calculator(0, 80) # 印出「輸入的數字有問題」
```
### 4. 迴圈與迭代
[<font color="green">**教學連結**</font>](https://railsbook.tw/chapters/05-ruby-basic-1.html#loop-and-iteration)
在Ruby中,沒有一般的那種for迴圈寫法。


<font color="blue">**迴圈跟迭代有什麼不同?**</font>
迴圈->你就跑個 5 圈吧!
迭代->你把這 5 個元素全部都看過一次吧!
```ruby=
# -----for..in 迴圈-----
# 如果想要把裡面的元素一個一個印出來,可以使用 for ... in ... 的寫法有點類似迭代,先把東西全看一遍在印出
names = ["eddie", "joanne", "john", "sherly"]
# name在迴圈過程中,有個變數可以用,names則是上面宣告的名稱
for name in names
puts name
end # 印出物件中名子。
# -----while迴圈-----
# while = until not
# 在設置條件時,小心不要變成無窮迴圈。
x = 0
while x < 10
puts x
x +=1
end # 印出0,1,2,3,4...
# -----until迴圈-----
# until = while not
x = 0
until x => 10
puts x
x +=1
end # 印出0,1,2,3,4...
# -----loop 迴圈-----
i = 0
# do.....end 是標準寫法,是程式碼區塊
loop do
puts i
i +=1
# 結束條建設在 break 之後!!
break if i> 10
end
```
- **times, upto, downto** 方法
在 Ruby 的世界裡,幾乎所有的東西都是物件,包括數字也是。而在 Ruby 的數字物件裡有一個 **times** 方法,可以讓我們指定迴圈要跑幾次:
```ruby=
# -----method 式迴圈-----
# 又稱方法式迴圈
5.times do
puts "hello, ruby"
end
# 執行後得到結果:
# hello, ruby * 5
# 語法一開始可能會看不太習慣,但光從字面應該很容易猜到它的意思。後面那個 do..end 在 Ruby 裡稱之 Block
# -----upto 正向的執行迴圈-----
1.upto(5) do |i|
puts "hi, ruby #{i}"
end
# 執行後得到結果:
# hi, ruby 1 hi, ruby 2 hi, ruby 3 hi, ruby 4 hi, ruby 5
# -----downto 反向執行迴圈-----
5.downto(1) do |i|
puts "hi, ruby #{i}"
end
# 執行後得到結果:
# hi, ruby 5 hi, ruby 4 hi, ruby 3 hi, ruby 2 hi, ruby 1
```
- **迭代**(iteration)
除了用 for..in 的方式把陣列裡的東西印出來,更常使用 **each** 方法來做這件事:
```ruby=
# 迭代式迴圈
friends = ["eddie", "joanne", "john", "mark"]
friends.each do |friend|
puts friend
end
# 如果想要印出索引值?
# 方法一:
names = ["eddie", "joanne", "john", "sherly"]
x = 0
names.each do |name|
puts "#{x} #{name}"
x += 1
end
# 方法二:
names = ["eddie", "joanne", "john", "sherly"]
names.each.with_index do |name, x|
puts "#{x} #{name}"
end
```
### 4. 陣列與範圍
[<font color="green">**陣列教學**</font>](https://railsbook.tw/chapters/06-ruby-basic-2.html#array_class)
[<font color="green">**範圍教學**</font>](https://railsbook.tw/chapters/06-ruby-basic-2.html#range_class)
[<font color="green">**更多陣列使用方法1**</font>](https://www.runoob.com/ruby/ruby-array.html)
[<font color="green">**ruby使用手冊**</font>](https://ruby-doc.org/core-3.0.0/Enumerable.html#method-i-reduce)
```ruby=
# 使用 Array 類別
a = Array.new
# 使用中括號<<<推薦
b = []
# 使用%W,注意這會使裡面的值都變成字串
list = %w(ruby php python 1)
p list # 印出 ["ruby", "php", "python", "1"]
heroes = ['孫悟空','魯夫','宇智波佐助','一拳超人','流川楓','劍心'];
puts heroes[0] # 印出 孫悟空
puts heroes[1] # 印出 魯夫
puts heroes[-1] # 印出 劍心
puts heroes[-2] # 印出 流川楓
puts heroes.first # 印出 孫悟空
puts heroes.last # 印出 劍心
puts heroes.length # 印出 6
heroes << '漩渦鳴人' # 在最後面加上漩渦鳴人
puts heroes.length # 印出 7
heroes.push('布羅利') # 在最後面加上布羅利
puts heroes.length # 印出 8
```
```ruby=
# -----map 方法-----
# 對集合裡的每個元素進行運算,並收集成一個新的集合。
p (1..10).map { |x| x * 2 }
# 印出 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# -----select 方法------
# 從集合裡挑選符合條件的元素,並收集成一個新的集合。
p (1..10).select { |x| x < 5 }
# 印出 [1, 2, 3, 4]
# select反義詞
p (1..10).reject { |x| x < 5 }
# 印出[5, 6, 7, 8, 9, 10]
#
# -----reduce 方法------
# 對集合裡的每個元素進行運算,並將所有的運算結果歸納成一個單一結果。
p (1..10).reduce { |sum, n| sum + n }
# 印出 55
```
```ruby=
# 【練習題】
# 1.把陣列 [1, 2, 3, 4, 5] 變成 [1, 3, 5, 7, 9]
list = [1, 2, 3, 4, 5]
p list.map { |x| x * 2 - 1}
# 2.把陣列[1, 3, 4, 1, 7, nil, 7] 由小到大排序,並且移除 nil 以及重複的數字。
list = [1, 3, 4, 1, 7, nil, 7]
p list.compact.uniq.sort # 印出[1, 3, 4, 7]
# array.compact返回 self 的副本,移除了所有的 nil 元素
# array.uniq返回一个新的数组,移除了 array 中的重复值。
# array.sort [or] array.sort { | a,b | block }返回一个排序的数组。
```
- 範圍(Range)
使用方法
Ruby 的範圍有「二個點」 .. 跟「三個點」 ... 這兩種寫法。**二個點的 1..5 會做出 1 ~ 5 的範圍,而 1...5 則是產生 1 ~ 4 的範圍**,不包含 5。通常會使用 <font color="red">**to_a**</font> 方法把範圍轉換成**陣列**:
```ruby=
puts (1..10).to_a # 印出 1 ~ 10
puts (1...10).to_a # 印出 1 ~ 9
('a'..'z') # 範圍 a ~ z
('A'..'Z') # 範圍 A ~ Z
for i in 1..10
puts i
end # 印出 1 ~ 10
# case...when
age = 10
case age
when 0..3 #>> Range範圍
puts "嬰兒"
when 4..10
puts "兒童"
when 11..17
puts "青少年"
else
puts "成年"
end # 印出 兒童
```
```ruby=
# 【練習題】
# • 請利用 Range 的特性,完成下列問題:
# • 印出 1 ~ 100 之間所有的單數
p (1..100).select{ |x| x % 2 ==1 }
p (1..100).select{ |x| x.odd? }
# .odd?取奇數,even?取偶數,?代表是與否在回傳
# • 計算 1 ~ 100 的總和
p (1..100).reduce { |sum, n| sum + n }
p (1..100).sum # .sum 可以算出集合的加總
# • 印出 5 個小於 100 且不重複的亂數
p (1..100).to_a.shuffle.fist(5)
p (1..100).to_a.sample(5)
```
### 5. Hash 雜湊符號
[<font color="green">**Hash教學**</font>](https://railsbook.tw/chapters/06-ruby-basic-2.html#range_classhttps://www.runoob.com/ruby/ruby-hash.html)
[<font color="green">**Hash教學2**</font>](https://www.runoob.com/ruby/ruby-hash.html)
什麼時候會用到?
像查字典一樣,當你查hello時候,會回傳哈囉,查name時候,回傳名子。
(Hash)是類似 "key" => "value" 這樣的鍵值對集合。Hash類似於一個數組,只不過它的索引不局限於使用數字。
Hash 的索引(或者叫"鍵")幾乎可以是任何對象。
Hash 雖然和數組類似,但卻有一個很重要的區別:Hash 的元素沒有特定的順序。如果順序很重要的話就要使用數組了
```ruby=
# 使用 Hash 類別
a = Hash.new
# 使用大括號
b = {}
# 舊式 Hash 寫法
profile = { :name => 'eddie', :age => 18 }
# Ruby 1.9 之後的新式寫法(類似 JSON 格式)
profile = { name: 'eddie', age: 18 }
p profile.keys # 印出 [:name, :age]
p profile.values # 印出 ["eddie", 18]
p profile[:name] # 印出 "eddie"
```
### 6. 符號
什麼是符號?,就是有**名子的物件**,符號不是變數,它就只是**一個值**(或說是一個物件)。
<font color="red">**1**</font> 是一個數字物件,
<font color="red">**"aa"**</font> 是一個字串物件
<font color="red">**:hello**</font> 是一個符號物件
```ruby=
# 舊式 Hash 寫法
# :name , :age 就是符號
profile = { :name => 'eddie', :age => 18 }
# :name 本身是一個值(物件)不能當變數用
:name = "123" # 語法錯誤
my_name = "孫悟空" # my_name 變數指向一個字串
your_name = :someone # your_name 變數指向一個符號
```
- **字串跟符號的差別**
字串的內容可以變,但 Symbol(符號) 不行
- 該用字串還是符號?
- **不可變**,選擇符號
- **可變**,選擇字串
```ruby=
# name 宣告的字串內容可以更變。
name = "abcd"
name[1] = "x"
p name # 印出 "axcd"
# name 宣告的符號是不可更變。
name = :abcd
name[1] = "x"
p name # 執行錯誤
# .object_id會印出該字串或是符號的序號(記憶體的位置)。
puts "hello".object_id # 印出 70213995743900
puts "hello".object_id # 印出 70213995743760
puts "hello".object_id # 印出 70213995743700
puts :hello.object_id # 印出 1055708
puts :hello.object_id # 印出 1055708
puts :hello.object_id # 印出 1055708
# -----「冷凍」字串-----
# 通常不可變的情況下都使用符號或是冷凍字串。
# 這東西需要組合或是變化的話請使用字串。
# 使用.freeze.object_id這一個物件就不能在被修改
puts "hello".freeze.object_id # 印出 70164486689200
puts "hello".freeze.object_id # 印出 70164486689200
puts "hello".freeze.object_id # 印出 70164486689200
# -----字串與符號互相轉換-----
# 字串轉符號
p "name".to_sym #**常用** 印出 :name
p "name".intern # 印出 :name
# 符號轉字串
p :name.to_s #**常用** 印出 "name"
p :name.id2name # 印出 "name"
```
### 7. 方法(Method)
為什麼要使用方法?
- 希望可以把可能可以重複使用的東西,把它包起來並給他名子,比較容易理解。
[<font color="green">**方法與程式碼區塊(block)**</font>](https://railsbook.tw/chapters/07-ruby-basic-3.html#method)
```ruby=
#def..end ↓名稱 ↓參數(parameter)
def say_hello_to(someone)
puts "Hello, #{someone}"
end
# ↓引數(argument)
say_hello_to('孫悟空') # 印出 Hello, 孫悟空
# ()省略掉也可以跟上面效果一樣。
say_hello_to '魯夫' # 印出 Hello, 魯夫
# -----參數與引數-----
def say_hello_to(someone)
puts "Hello #{someone}"
end
def greeting
puts "你好"
end
say_hello_to # 發生引數個數錯誤(少1個)
greeting '孫悟空' # 發生引數個數錯誤(多1個)
# -----參數預設值-----
# ↓預設值
def say_hello_to(someone = '一拳超人')
puts "Hello #{someone}"
end
say_hello_to '孫悟空' # 印出 Hello 孫悟空
say_hello_to # 印出 Hello 一拳超人
```
<font color="blue">**【冷知識】**</font>在 Rails 的 View 裡常看到這樣的寫法(產生連結):請問這個 link_to 方法有幾個參數?。
```ruby=
link_to "⾸⾴", root_path, class:"btn btn-default", method: "post", confirm: true
```
如果<font color="red">**最後一個參數**</font>是一個 Hash 的話,那個 Hash(雜湊) 的大括號可省略

- **回傳值**
剛剛不是有印東西出來了嗎?
還要「回傳」什麼?
回傳 = 交回**控制權**

```ruby=
# 第一種A方法只有印出完之後就不保留
def doubleA(num)
p num * 2 # 印出 2 倍的值,但沒有回傳值
end
# 第二種B方法,return把控制權還給妳的時候,結果也會回傳給呼叫他的人,讓他可以繼續往下做
# 這裡就是P這個方法
def doubleB(num)
return num * 2 # 回傳 2 倍的值(結果),但不會印出內容
end
doubleA(5) # 呼叫 doubleA 方法
p doubleB(5) # 印出呼叫 doubleB 方法的內容
# -----回傳值-----
# 1
def doubleC(num)
puts num * 2 # puts 印出 2 倍的值,但沒有回傳值
end
p doubleC(3) # 會印出什麼?
# 因為沒有回傳任何值所以印出 : 6(doubleC裡的puts) nil(p)
# 2
def doubleC(num)
p num * 2 # p 印出 2 倍的值,但有回傳值
end
p doubleC(3) # 會印出什麼?
# 因為 doubleC 裡面p回傳值所以印出 : 6(doubleC p) 6(p)
```
- **return** 可**適時**省略,會自動回傳**最後一行**的**執行結果**
```ruby=
def add_three(n)
n + 3 # 回傳 +3 之後的結果
end
def age
20 # 回傳 20
end
p add_three(2) # 印出 5
p age # 印出 20
```
- 問號與驚嘆號
- ? 跟 ! 可以是命名的一部份,但只能放在最後面
- **問號**通常會回傳**真假值**
- **驚嘆號**通常表示**要注意!**
```ruby=
def is_adult?(age)
if age >= 18
return true
else
return false
end
end
p is_adult?(20) # 印出 ture
p is_adult(18) # 執行錯誤? 也是本身方法的一部分,不要忘了加
# -----------
list = [9, 5, 2, 7]
# 進行排序,但不會改變 list 的內容
p list.sort # 印出 [2, 5, 7, 9]
p list # 印出 [9, 5, 2, 7]
# 進行排序,並且直接改變 list 的內容
p list.sort! # 印出 [2, 5, 7, 9]
p list #因為上方.sort!改變list內容,印出 [2, 5, 7, 9]
```
```ruby=
【練習題】• 看看這個方法是否可以再精簡一些?
def is_adult? (age)
if age >= 18
return true
else
return false
end
end
# 方法一(三元運算子)
def is_adult? (age)
(age >= 18) ? true : false
end
# 方法二
def is_adult? (age)
# ↓(可省略)
return age >= 18
end
```
#### 7-2 模組化
**為什麼要使用模組化?**
把全部程式碼寫在同一個檔案裏面、不方便管理、使用模組化可以將程式碼歸檔、要用時候在讀進來就可以。
```ruby=
1. 檔案名稱:bmi.rb
# 以下為檔案裡的內容↓↓↓
def bmi_calculator(height, weight)
weight / (height * height)
end
2. (require方法)檔案名稱:main.rb
# 準備引入BMI.rb的程式碼
# 以下為檔案裡的內容↓↓↓
# ↓引入↓ ↓檔案路徑.rb可加可不加
require './bmi.rb'
require './bmi.rb'
require './bmi.rb'
# ↑↑↑require方法不可重複呼叫檔案裏面的方法
p bmi_calculator(1.78, 80)
# 因為require方法不可重複呼叫檔案裏面的方法結果為
# 25.249337
3. (load方法)檔案名稱:main.rb
# 準備引入BMI.rb的程式碼
# 以下為檔案裡的內容↓↓↓
# ↓引入 ↓檔案路徑.rb一定要加
load './bmi.rb'
load './bmi.rb'
load './bmi.rb'
# ↑↑↑load方法可重複呼叫檔案裏面的方法
p bmi_calculator(1.78, 80)
# 因為呼叫了三次結果為
# 25.249337
# 25.249337
# 25.249337
```
### 8. 實用工具Rake
[上課pdf檔](https://cdn.fs.teachablecdn.com/r6bHHYALSBuDUOT0kqda)
在軟體開發中,Rake是一個工具程式,經由讀取叫
做「Rakefile」的檔案,自動化建構軟體。


### 9. 程式碼區塊(Block)
[<font color="green">**程式碼區塊教學連結**</font>](https://railsbook.tw/chapters/07-ruby-basic-3.html#method)
程式碼區塊(Block)是一段**不會被主動執行的程式碼**:

**Block 無法單獨存活:do .. end 與 { } 兩種寫法**
```ruby=
# 執行錯誤
{
puts "我是大括號型的 Block"
}
# 執行錯誤
do
puts "我是 do end 型的 Block"
end
```
Block 就像**寄生蟲**一樣,依附在**方法**後面
**yield =把控制權轉讓給 Block**
```ruby=
def say_hello
puts "Hello, 你好"
# yield = 把控制權轉讓給 Block
yield
puts "Hello, 大家好"
end
say_hello() { puts "here!" }
puts "there!"
# 1. 先呼叫say_hello執行區塊內容。印出 "Hello, 你好"
# 2. 下一個yield轉讓控制權給 Block 印出 here!
# 3. 在交還say_hello 印出 Hello, 大家好
# 4. 執行完say_hello 內容在交給 puts 印出 there!
```
- 控制權轉讓的時候

- 自動回傳 Block 的最後一行執行結果
- 用 return 回傳 Block 的結果?
Block 的最後一行執行結果自動會變成 Block 的回傳值,這裡並不是省略了 return, 而是不能使用 return 回傳結果。
```ruby=
def test_two
# 2.遇到yield把控制權交回,並回傳(3)到3.block{}內做判斷
# 4.回傳結果false並帶入if做判斷,並印出結果。
if yield(3)
puts "yes, it is 2"
else
puts "no, it is not 2"
end
end
# 1.執行 test_two()到2.if
test_two(){ |n|
# 3.(3)帶進來並判斷,得到false
# 並自動回傳 Block 的最後一行執行結果
n ==2 }
# ---------------------------------------------
# 練習題 • 請試著完成自己土砲陣列的 select 方法:
def my_select(my_list)
# 2-1.宣告空陣列,供後面結果值存入
new_list = []
# 2-2.將帶入的陣列的代稱(my_list)使用迭代迴圈並用n帶入
# 並將值用yield控制權與值交還給{.odd?}執行判斷
my_list.each do |n|
# 4.使用if判斷,將ture的值帶回(n),並<<丟到new_list[]空陣列裡
new_list << (n) if yield(n)
end
# 5.程式碼區塊特性將值,回傳到最上面宣告的空陣列裡面(return可省略)。
return new_list
end
# 1.執行my_select()並帶入陣列
# 3.執行{程式碼區塊},判斷之後因{}特性會自動回傳值,回傳true / false
# 6.將if判斷結果並傳入空陣列的值,用p印出結果。
p my_select([1, 2, 3, 4, 5]) { |i| i.odd? }# 印出 [1, 3, 5]
# p ([1, 2, 3, 4, 5]).select { |i| i.odd? }# 印出 [1, 3, 5]
```
- Block 其實不是參數

- 如果沒有 Block 但卻 yield 的話...

- Block 的有**大括號以及 do ... end 這兩種寫法,有什麼不同**?
- **結合度的強度不同!!**

- **物件化的 Block**
雖然 Block 沒辦法單獨存活
但被**物件化**之後就可以了
使用Proc方法:

使用Lambda方法:

- Proc 跟 Lambda 看起來很像,用起來也很像,但有什麼差別嗎?

### 10. 物件導向程式設計(**O**bject-**O**riented **P**rogramming)
[<font color="red">**教學連結**</font>](https://railsbook.tw/chapters/08-ruby-basic-4.html#class)
[<font color="red">**什麼是物件導向中的封裝、繼承和多型特**</font>](https://medium.com/w-learning-note/%E4%BB%80%E9%BA%BC%E6%98%AF%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E4%B8%AD%E7%9A%84%E5%B0%81%E8%A3%9D-%E7%B9%BC%E6%89%BF%E5%92%8C%E5%A4%9A%E5%9E%8B%E7%89%B9%E6%80%A7-c15d4e8c567a)
有點類似於物件 = 狀態(名詞) + 行為(動詞)
- 在 Ruby 裡「幾乎」什麼都是物件,那什麼不是物件?
在Ruby裡,{程式碼區塊}及do..end
都不是物件,但使用Proc與Lambda方法
可以讓{程式碼區塊}變成物件。
- 類別與實體(class and instance)

```ruby=
# 如何定義
# !!類別↓的命名規定 = 必須是常數!!
# 1.定義類別(烤盤)
class Cat
def eat(food)
puts "#{food} 好好吃!!"
end
end
# 2.用一個kitty的東西用(實體名).new方法做出kitty出來
kitty = Cat.new
# 3.使用kitty就可以使用類別內定義的功能
kitty.eat "魚" # 印出 魚 好吃!
# 實體↓ 類別↓
nancy = Cat.new
nancy.eat "小魚餅乾" #=> 印出「小魚餅乾 好好吃!!」
```
- 繼承(inheritance)
把共同的特徵放在同一個分類(class)
```ruby=
class Animal
def eat(name,food)
puts "#{name} 正在吃 #{food} "
end
def walk(name,run)
puts "#{name} 正在 #{run}"
end
end
class Cat < Animal
end
class Dog < Animal
end
miki = Cat.new
miki.eat "miki","魚" # 印出 miki 正在吃 魚
miki.walk "miki","跳"# 印出 miki 正在 跳
black = Dog.new
black.eat "black","骨頭"# 印出 black 正在吃 骨頭
black.walk "black","跑"# 印出 black 正在 跑
```
- 初始化(initialize)
initialize有點像是,在呼叫class(烤盤)時候,所做的第一件事情:
```ruby=
class Cat
def initialize
puts "hello 你好"
end
end
# 什麼都不用做,一印出就會打招呼
kitty = Cat.new # 印出 hello 你好
# -----------
# 傳給 new 方法的引數,後續會由 initialize 方法收下
class Cat
def initialize(name, age)
puts "hello 你好"
puts @name = name
puts @age = age
end
end
kitty = Cat.new("kitty", 18)
# 印出 hello 你好
# kitty
# 18
```
- 實體方法與類別方法
```ruby=↓
class Cat
# ↓方法
def say_hello
puts "你好"
end
end
kitty = Cat.new
# say_hello這個方法作用在kitty實體上面所以叫實體方法
#↓實體 + ↓方法 = 實體方法
kitty.say_hello #印出 你好
```
**類別方法(class method) = 作用在類別上方法**
方法前面加上 **self**
```ruby=↓
class Cat
#類別方法定義
# self↓ + ↓自訂名稱
def self.all
puts "全部的貓"
end
end
# 類別↓ + ↓方法 =類別方法
Cat.all # 印出 全部的貓
```
- 實體變數與類別變數
| | 區域變數 | 全域變數| 實體變數 | 類別變數 |
| -------- | ------ | ------ | ------- | ------- |
| 命名樣式 |username|$username|@username|@@username|
- 實體變數 (instance variable)
實體變數存活在每個 **獨立** 的實體內
實體變數 = 在實體裡面可自由取用的變數
```ruby=
class Cat
def initialize(name)
@name = name
end
# ↓在實體內,可以取用。
def say_my_name
return @name
end
end
kitty = Cat.new('kitty')
puts kitty.say_my_name() # 會印出kitty
```
- 實體變數在實體裡可自由取用,但在實體外就不行了
```ruby=
class Human
def initialize(name)
@name = name
end
#回傳 @name 稱為 getter
def name
@name
end
#設定 @name 稱為 setter
def name=(new_name)
@name = new_name
end
end
eddie= Human.new('eddie')
puts eddie.name 印出 eddie
puts eddie.name=('andy') 印出andy
```
- 只是要取個值,每次都要這樣寫不覺得煩嗎?
- **attr_reader(讀) / attr_writer(寫) / attr_accessor(可讀可寫)**
```ruby=
class Cat
def initialize(name)
@nameyy = name
end
# 讀
attr_reader :nameyy
# ↓↓↓這是原始寫法↓↓↓
# def name
# return @name
# end
# 寫
attr_writer :nameyy
# ↓↓↓這是原始寫法↓↓↓
# def nameyy=(n_name)
# return @name = n_name
# end
# 可讀可寫
attr_accessor :nameyy
end
kitty = Cat.new("kitty")
puts kitty.nameyy()
puts kitty.nameyy = ("andy")
```
- **冷知識** Ruby 裡,你以為 1 + 2 = 3 是加法嗎?
```ruby=
# 在ruby裡面看到的很多都是語法糖衣,其原型很多都是方法
puts 1 + 2
puts 1.+(2) # 結果都一樣 印出 3
puts !!true
puts true.!.!# 結果都一樣 印出true
```
- **類別變數**
類別變數 = 在類別方法裡面可取用的變數:
```ruby=
class Cat
# 建立@@count類別變數
@@count = 0
# 在初始化中,在類別有人用new呼叫,就累加1。
def initialize
@@count += 1
end
#建立類別方法,回傳值self.名子
def self.counter
return @@count
end
end
# 做了五圈
5.times {Cat.new}
# 這樣直接叫不出來需要類別方法
p Cat.counter() # 印出 5
```
- 開放類別
兩個同名的類別撞在一起不會覆蓋而是融合!
Rails 大量的使用了開放類別的技巧
```ruby=
class Cat
def hello
end
end
# 重複定義
class Cat
def world
end
end
kitty= Cat.new
kitty.hello # 不會發生錯誤
kitty.world # 不會發生錯誤
```
- 幫現有的類別加功能,甚至內建的類別也做得到!
```ruby=
# 把原有字串類別,新增功能
class String
def say_hello
"オッス!オラ#{self}"
end
end
puts ("悟空").say_hello #=> 印出「オッス!オラ悟空」
```
- **存取控制**
有些功能,不想讓外部直接存取,這時候要使用所謂的**封裝(encapsulation)**
- 封裝三種方法:public / private / protected
```ruby=
# -----public(公開)-----
class Cat
# 這種外部可存取的方法沒有特別宣告默認就是,public(公開)
def say_hello
puts "hi"
end
end
kitty= Cat.new
kitty.say_hello
# -----private(私有)-----
class Cat
private
#private以下所有方法都是變為私人,只能在類別內使用。
def gossip
puts "我跟你說,你不能跟別⼈說"
end
def A_plan
puts "A"
end
def B_plan
puts "B"
end
#或是定義下最下方也可以設為私人。
private :gossip
private :A_plan
private :B_plan
end
kitty = Cat.new
kitty.gossip #失敗
```

- 其實 Ruby 的存取控制跟別人不一樣,特別是 **private**。
- 定義:private =不能有**明確的訊息接收者(receiver)**
- private =呼叫方法的時候不會有小數點。
- puts前面沒有receiver(接收者),所以也算私人方法。

```ruby=
class Cat
def say_hello
puts "公開"
#↓前面沒有接受者(receiver),成功
gossip
end
private
def gossip
puts "私人"
end
end
kitty= Cat.new
# 在cat類別裡,用public方法裡,增加gossip的話也可以成功呼叫。
kitty.say_hello # 印出 公開 私人
# 沒有符合private定義前面加了receiver
kitty.gossip #出錯
# 雖然符合了沒有receiver的規定
# private只存在類別裡面,這樣呼叫程式無法判斷你在呼叫什麼。
gossip #出錯
```
- **其實 private 也不是那麼 private**
```ruby=
class Cat
private
def gossip
puts "我跟你說,你不能跟別人說"
end
end
kitty = Cat.new
#在kitty(物件)上面,呼叫send(公開方法),然後傳了(:gossip(符號/參數)進去)
kitty.send(:gossip) # 成功!
```
- **protected(保護)** =不限定有沒有明確的訊息接收者
- 基本上你不太需要 **protected**,比起protected,**private更像是**protected。
```ruby=
class Cat
def say_hello
puts "公開"
#protected前面有沒有加receiver(接收者)都可以
self.gossip
end
# 改為protected
protected
def gossip
puts "私人"
end
end
kitty= Cat.new
kitty.say_hello # 成功 印出 公開 私人
```
- **模組**
如何定義: **模組的命名規定 = 必須是常數**
```ruby=
# 模組定義方法
# module + 常數命名(大寫開頭)
module Flyable
def fly
puts "i can fly!"
end
end
class Cat
#使用 include把Flyable模組引入進來
include Flyable
end
kitty = Cat.new
# 不用繼承方法,也可以使用.fly功能。
kitty.fly # 印出 i can fly!
```
- **使用模組跟類別的差別?**

```ruby=
# 練習題 : 請建立一個繼承自動物 Animal 類別的小鳥 Bird 類別,並引入include 一個有實作飛行方法 fly 的飛行模組 Flyable。
module Flyable
def fly
puts "i can fly!!"
end
end
class Animal
end
class Bird < Animal
include Flyable
end
chicken = Bird.new
chicken.fly
```
- **模組的 include(增加實體方法) 與 extend(擴充類別方法)**
include(增加實體方法):

extend(擴充類別方法):

- 如果遇到同名類別...可以用模組方法(namespace)來解決這問題。
```ruby=
# 兩個同名類別不會覆蓋會融合在一起,但還是有一定風險。
# 包在A裡面
module A
class Cat
def say_hello
puts "A cat"
end
end
end
# 包在B裡面
module B
class Cat
def say_hello
puts "B cat"
end
end
end
#modlue 宣告名::來呼叫
kitty = A::Cat.new
kitty.say_hello # 印出 A cat
nancy = B::Cat.new
nancy.say_hello # 印出 B cat
```
- 總整理 **冒號**的意思?

### 11. Web 篇之 Rack
[**課程連結**](https://campus.5xruby.tw/courses/coding/lectures/10135063)
什麼是Rack? : Ruby實做的網站伺服器介面

- **1. HTTP狀態:**
- 可根據網站回應的「數字」,得知目前網站的狀態:
- 「一切都正常」的 200
- 「這個頁面目前不在這裡,請你去這裡看看」的轉址 301
- 「不知道為什麼,東西不在這裡」的找不到頁面 404
- 「伺服器錯誤」的 500
Rack流程(使用proc.new方法):
- 1.開啟ubuntu移動至自訂目錄並輸入rackup(執行前先用vscode該目錄建立config.ru)
```ruby=
zhong@ZHONGJUN-AN-HP:~$ cd /mnt/d/projects
zhong@ZHONGJUN-AN-HP:/mnt/d/projects$ cd rake-demo
zhong@ZHONGJUN-AN-HP:/mnt/d/projects/rake-demo$ rackup
```
- 2.vscode裡建立config.ru並寫入
```ruby=
# 物件化的 Block使用Proc方法
run Proc.new {
[
200, # 狀態
{ "Content-Type" => "text/html" }, # header
["Hello, Rack"] # 內容物
]
}
```
在瀏覽器輸入:http://127.0.0.1:9292/

- 中介軟體 Middleware(class方法)

- 問題Q & A:
**Q:想請問** run Proc.new 的 run 的功能是什麼?
介紹 Proc 的課程中是把 Proc 指定給一個變數,再使用。
```
A:run 是 rack 提供的一個方法,就是整個 rack 應用程式的進入點。
後面接一個「可以回應 call 方法並且會回應適當內容的物件」,而 Proc 就剛好是一種可以回應 call 方法的物件。
a = Proc.new
run a
跟
run Proc.new
這兩種寫法的差別就是少了 a 變數的產生而已
```
### 12. Web 篇之 Sinatra
[**課程連結**](https://campus.5xruby.tw/courses/489534/lectures/10135082)
- Sinatra 其實也是一款 Rack 應用程式
- Sinatra 與 Ruby on Rails有什麼差別?
- 好比摩托車 與 汽車一樣,sinatra比較輕便、但相較於ROR功能性沒這麼齊全
**1.安裝** : 在Ubuntu介面下輸入
```ruby=
$ gem install sinatra
```
**2.安裝重新整理套件** : 在Ubuntu介面下輸入
```ruby=
$ gem install sinatra-contrib
```
**3.效能更好的伺服器 puma** : 在Ubuntu介面下輸入
```ruby=
$ gem install puma
```
**4.打招呼**
```ruby=
# ! 檔案:hello.rb / vscode
require 'sinatra'
# 重整套建,因影響效率,if 開發者才打開
require 'sinatra/reloader' if development?
# ↓根目錄
get '/' do
"你好,世界,現在是 #{Time.now}"
end
# 1.在ubuntu cd到該目錄之後打: ruby 根目錄名.rb
# 2.在瀏覽器輸入 http://localhost:4567/
```
**5.帶參數**
```ruby=
# ! 檔案:hello.rb / vs code
require 'sinatra'
get '/cats' do
"黑貓白貓,會呼嚕的貓就是好貓!"
end
# 當在網上打localhost:xxxx/cats/11
get '/cats/:id' do
# ↓會捕捉上面id並做成hash,用key(:id)拿。
"你好,你是第 #{params[:id]} 號的貓"
# 印出 你好,你是第 11 號的貓
end
```
**6.erb = Embedded Ruby**
ruby內建功能,可以在 HTML 裡面寫 Ruby 程式碼
```ruby=
# vscode <首頁.rb>
require 'sinatra'
# 重整套建,因影響效率,if 開發者才打開
require 'sinatra/reloader' if development?
# ↓根目錄
get '/:name' do
# 這裡宣告變數,,給erb :index 使用
@name = "小明Q"
# ↓透過erb :自訂名 在projuct的目錄/views/自訂名.erb讀檔案
# 這樣不用在hello.rb寫html語法會更整潔
erb :index,
# ↓透過layout: :自訂名稱在projuct的目錄/views/自訂名稱.erb讀檔案並套用版型
layout: :my_qqq
end
```
```ruby=
#根目錄/views/index(自訂名).erb
# 在這裡寫上html 語法即可
我的名子是<%= @name # 從首頁抓取宣告變數的內容
```
```htmlembedded=
# 根目錄/views/my_qqq(自訂名稱).erb
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>my_qqq</title>
</head>
<body>
<!-- 在讀取my_qqq樣板時候,會從index.erb抓取該檔案內容並放在這裡 -->
<%= yield %> # 這裡抓取index.erb內容並顯示 我的名子是小明Q
</body>
</html>
```
- 1.使用表單


- 2.處理表單


## 1 - 7 Ruby on rails
### 1. 簡介
- Ruby on Rails 不是一種<font color="red">**程式語言!**</font>,他是一種網站開發框架
- Q.什麼是網站開發框架?
- A. 制定一套規範或者規則(思想),大家在該規範或者規則下工作。或可說使用別人搭好的舞台來做編劇及表演。<font color="red">**專門為網站開發所設計的工具包**</font>
### 2. 環境安裝
```ruby=
# ubuntu裡執行
# 安裝⽬前最新發⾏版本
$ gem install rails
# 安裝嚐鮮版本
$ gem install rails --pre
# 安裝特定版本
$ gem install rails -v 5.2.2
# 確認版本
$ rails -v
Rails 5.2.3
```
- <font color="blue">冷知識:**什麼是root權限( 根/最高 權限)?**</font>
- <font color="blue">冷知識:**什麼是IDE(整合開方環境)?**</font>
IDE的英文全名是Integrated Development Environment,中文譯為:整合開發環境。[**解釋連結**](https://makerpro.cc/2015/08/what-is-ide/)
#### 2.1 終端機(Ubuntu)
```ruby=
# 建立名為「hello-rails」的專案
$ rails new hello-rails
# 切換⽬錄
$ cd hello-rails
# 啟動 Rails 本機伺服器
$ rails server
# 1.出錯時,先安裝node.js
# ↓先提高至root權限才能安裝
$ sudo apt-get install nodejs
# 2.安裝yarn
[安裝教學](https://https://phoenixnap.com/kb/how-to-install-yarn-ubuntu)
# 3.安裝rails webpacker:install
$ rails webpacker:install
# 4.再啟動
$ rails server
```
### 3. 常用終端機指令介紹
[詳細教學](https://railsbook.tw/chapters/03-command-line-tools.html)

```ruby=
# 回上一層路徑
$ cd ..
# 回上一步所在的路徑位置
$ cd -
# 顯示詳細該資料夾詳細檔案資訊
$ ls -l
# 顯示詳細該資料夾詳細檔案資訊(包含隱藏檔)
$ ls -la
# 快速刪除字元
ctrl+W
# 快速刪除整行字元
ctrl+U
# 快速搜尋歷史指令
ctrl+R
```
### 4. 第一個 Rails 應用程式
[詳細教學](https://railsbook.tw/chapters/04-your-first-rails-application.html)
```ruby=
# • 建立全新專案:
$ rails new hello-project
# • 安裝套件
$ bundle install
# • 啟動伺服器
$ rails server
# • 打開瀏覽器,輸入網址 http://localhost:3000
# • 應該可以看到開心歡呼的畫面
```
#### 4.1 建立資料表(Scaffold方法)
[詳細教學](https://railsbook.tw/chapters/04-your-first-rails-application.html)
[資料刪除方法](https://www.sejuku.net/blog/63480)
```ruby=
# ubuntu介面
# • 第一步:建立使用者相關檔案及設定:資料名(自訂) /名子/電話/信箱
$ bin/rails generate scaffold User name:string email:string
tel:string
# 少打一點:
# 在使用Scaffold方法時,會自動生成id欄位可省略
# generate = g
# string 型態可省略不打
# bin/rails 可以打 rails 就行了
$ rails g scaffold User name email tel
# • 第二步:把 scaffold 的資料表設定具像化:
$ rails db:migrate
# • 第三步:啟動伺服器
$ rails server
# 或是簡寫成:
$ rails s
# • 如果剛剛的伺服器沒有中斷的話,第三步可省略
# • 最後,打開瀏覽器,連到網址 http://localhost:3000/users
```
### 5. MVC 是什麼
Route ( 路徑 )+ MVC 架構 = 模型(Model)、視圖(View)和控制器(Controller)
[解釋](https://medium.com/%E5%AE%B8-%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98/%E6%B7%BA%E8%AB%87-rails-%E7%9A%84-mvc-%E6%9E%B6%E6%A7%8B-129458f0892f)
[Model、View、Controller 三分天下](https://railsbook.tw/chapters/10-mvc.html)
[Routes](https://railsbook.tw/chapters/11-routes.html)
[Controller](https://railsbook.tw/chapters/12-controllers.html)

- <font color="blue">冷知識:**在 MVC 的 V(View)就是畫面嗎??**</font>
View = 「**會回傳 HTML 的方法(method)**」
### 6. Migration 是做什麼的?
[什麼是 Migration](https://railsbook.tw/chapters/17-model-migration.html#what-is-migration)
[教學](https://ihower.tw/rails/migrations.html)
Migration是一個描述資料表長什麼樣子,本身不是一個資料表,透過指令rail db:migration具現化資料表。
Migration作用在於每次建立修改資料表,都會產生紀錄類似於版本控制,便於管理與操作。
#### 6.1 Model 跟資料表(Table)的關係是?
`Model 不是資料庫(Database)也不是資料表(Table),Model 可能只是疊在資料表上面的一個抽象類別,負責跟實體的資料表溝通。
打個比方,Model 的角色有點類似卡通「哆啦 A 夢」裡有種叫做「翻譯蒟蒻」的道具,我們跟 Model 說人話,Model 會幫我們翻譯成資料庫看得懂的話(SQL),幫我們跟資料庫要資料`

#### 6.2.新增第二個 Model(文章資料表)
[Model 關連性](https://railsbook.tw/chapters/18-model-relationship.html)
資料表關聯 :

```ruby=
# ubuntu介面
#
# • 第一步:建立使用者相關檔案及設定:
$ bin/rails g scaffold Post title content:text is_available:boolean user:references
# 除了 string 型態之外欄位不可省略
# user_id:integer 可改用 user:references
# • 第二步:把 scaffold 的資料表設定具像化:
$ rails db:migrate
# • 最後,打開瀏覽器,連到網址 http://localhost:3000/posts
```
建立關連 :
```ruby=
# 檔案:app/models/user.rb
# ⼀位使⽤者(User)可以寫很多篇⽂章(Post)
class User < ApplicationRecord
has_many :posts
end
# 檔案 app/models/post.rb
# 每篇⽂章(Post)都屬於某位使⽤者(User)
class Post < ApplicationRecord
belongs_to :user
end
```
#### 6.3 控制台模式
```ruby=
# unubtu介面
$ bin/rails console # 進入到控制台模式
> me = User.first # 取得第⼀個會員
> me.posts # 查詢該會員所有的⽂章
> the_post = Post.first # 取得第⼀篇⽂章
> the_post.user # 查詢該⽂章的作者是誰
```
### 7. 資料驗證
[資料驗證(Validation)](https://railsbook.tw/chapters/19-model-validation-and-callback.html#validation)
[官網驗證教學](https://guides.rubyonrails.org/active_record_validations.html)
資料驗證機制該寫在哪裡比較好呢?
常見的選項有:
也許妳可以不做前端驗證,但一定要做<font color="red">**後端驗證!!**</font>
- 前端驗證:在 HTML 頁面使用 JavaScript 在使用者填寫資料的時候檢查。
- 後端驗證:資料上傳到伺服器後,在寫入資料庫之前由網站應用程式進行檢查。
```ruby=
# 檔案:app/models/post.rb
# 驗證文章輸入的內容長度
class Post < ApplicationRecord
# content內容長度不能超過20字元
validates :content, length: { maximum: 20 }
end
```
### 7. 純手工打造(投票系統)
[簡易票選系統實作(上)](https://railsbook.tw/chapters/13-crud-part-1.html)
[簡易票選系統實作(下)](https://railsbook.tw/chapters/14-crud-part-2.html)
### 8. ActiveRecord - 資料表關連
**Active Record 是什麼?**
是一種 ORM 框架,把從資料表的一筆資料包裝成一個物件,並可在物件上增加額外的邏輯操作,讓資料的存取更便利。

**Model 是什麼?**
Model 不等於資料庫,是依照 Active Record 模式設計的產物。

**ORM = Object Relational Mapping (物件關聯對映)**
ORM 在網站開發結構中,是在『資料庫』和『 Model 資料容器』兩者之間,簡單來說,它是一個幫助使用者更簡便、安全的去從資料庫讀取資料。
因為 ORM 的一個特性為: 透過程式語言(Ruby, Java),去操作資料庫語言( SQL )。而這也是實作了物件導向的概念,產生的一種工具模式。
```
• 試說明以下幾個名詞各代表什麼意思,並有什麼關連性
• Active Record
• ORM
• Model
• Database
• Table
```
**C**onvention **o**ver **C**onfiguration(CoC) 慣例優於設定
所謂的「慣例」就像是不成文的規定,當遇到某種情況的時候我們會用特定的方式來解決問題,或是該把某個功能的程式碼放在什麼地方,不過即使不照著慣例寫,也有別的方法可以達到一樣的目的。
在 Rails 裡有相當多這樣的慣例,例如像是專案的目錄結構、資料表的關連及命名等,順著 Rails 的慣例,程式碼可以變得更簡潔、優雅。甚至可以說在學習 Rails 的過程,除了學習 Ruby/Rails 的語法之外,也是在學習 Rails 的慣例。
不要做重複的事(Don’t Repeat Yourself, DRY)
如果有些程式碼或結構一直重複的出現,就應該把重複的部份抽離出來,整理成為一個方法、類別或模組。這樣不僅可以重複使用,也會因此變得比較好維護,有發生問題也比較容易被發現。
**Ruby & rails 常見慣例**
- Model:大寫、單數
- Table:小寫、複數

- 每個表格預設有一個叫做 id 的流水編號欄位
- 在 migration 裡預設會有個 timestamps:
- timestamps 會轉換成 created_at 跟 updated_at 這兩個時間欄位
- 在資料新增或更新的時候自動寫入當下時間
<font color="red">猜猜看,這個 Migration 檔會做什麼事? 會建立幾個欄位?</font>

<font color="blue">冷知識:Rails 怎麼知道英文字的單、複數?不規則變化??</font>
```ruby=
# ubuntu裡進入rails console模式可以看英單字變化
"mouse".pluralize # 印出複數名詞
=> "mice"
"mice".singularize # 轉換回去
=> "mouse"
# (自訂命名規則)到rails專案目錄>config>initializers檔案裡編輯
# 設定 aaa 複數名 = bbb
inflect.irregular 'aaa', 'bbb'
# 進入rails console模式
"aaa".pluralize
=> "bbb"
"bbb".singularize
=> "aaa"
```
<font color="blue">冷知識:Rails 怎麼知道檔名跟類別之間的對照?</font>
```ruby=
# ubuntu裡進入rails console模式
"VoteLog".underscore
=> "vote_log"
"vote_log".camelize
=> "VoteLog"
```
### 8.1 關連性
書店系統

書店系統 - 店長

書店系統 - 商店

書店系統 - 商品

關連性

1. BUBNTU裡建立好專案(rails new),並建立(rails g model / rails db:migrate)店長;商店;商品資料表。
2. BUBNTU的rails console模式建立店長,商店資料。
3.
<font color="red">**has_one(一對一)**</font>
```ruby=
# code .打開專案路徑: app > models > owner.rb
# 並寫上has_one :store
class Owner < ApplicationRecord
# 依照上圖關聯性.一位店長只有一家店鋪。
has_one :store
end
#
• has_one 不是設定,它是一個類別方法。
• 執行 has_one :store 後,會動態產生幾個好用的方法:
•store
•store=
•build_store
•create_store
#
```
<font color="red">**belongs_to**</font>
```ruby=
# code .打開專案路徑: app > models > store.rb
# 並寫上belongs_to :owner
class Store < ApplicationRecord
belongs_to :owner
end
# 可以指定這家商店屬於哪家owner(店長)or查詢owner(店長)是誰
• 跟 has_one 一樣,belongs_to 也是類別方法。
• 執行 belongs_to :owner 後,會動態得到幾個好用的方法:
•owner
•owner=
#
# 各別建立 Owner 跟 Store 實體
o1 = Owner.create(name: 'Sherly')
s1 = Store.new(title: 'Ruby Shop')
# 然後把 Store 指定給 Owner
o1.store = s1 # 這個 store= ⽅法就是 has_one 產⽣的
p o1.store # store ⽅法也是 has_one 產⽣的
# 或是,直接從 Owner 的⾓度直接 create 或 build 商店
o1 = Owner.create(name: 'Sherly')
# ↓create_(依照has_one :(名子))來生成
o1.create_store(title: 'Ruby Shop')
# ↓.build_(依照has_one :(名子))來生成
o1.build_store(title: 'Ruby Shop')
•build 跟 create 有什麼差別?
• build類似於new,不會直接寫進資料庫,要再自行'.save'做寫入。
• create的話,會直接寫入資料庫。
```
<font color="red">** has_many :(自訂名)(一對多)**</font>
```ruby=
# code .打開專案路徑: app > models > store.rb
# 並寫上belongs_to :owner, has_many :products
class Store < ApplicationRecord
belongs_to :owner
# 一間店鋪可以有很多商品(一對多)。
has_many :products
end
class Store < ApplicationRecord
end
#
• has_many 不是設定,是類別方法。
• 執行 has_many :products 後,會動態產生幾個方法:
•products
•products=
•build
•create
#
# 各別建立物件
# ↓抓一號商店出來
s1 = Store.first
# 用new方法先建立兩筆物件(商品)還沒寫進去
p1 = Product.new(name: 'Ruby', price: 200)
p2 = Product.new(name: 'Rails', price: 150)
# 把 2 個 Product 物件丟給 Store 物件
s1.products = [p1, p2] # 這個 products= 就是動態產⽣的
# 或是⼀次丟⼀個也可以
s1.products << p1
s1.products << p2
# 或是,從 Store 的⾓度來建立 Product
s1 = Store.first
s1.products.build(name: 'Ruby', price: 200)
s1.products.create(name: 'Ruby', price: 200)
```
```ruby=
【練習題】
• Q.has_one 跟 belongs_to 需要同時設定嗎?沒設定會怎樣?
A. 設定has_one(4種) 跟 belongs_to(2種)方法,是方便查詢互相設定的資料,不設定的話就是不能使用上面(4種)&(2種)方法。
• Q.如果只設定 has_one 但沒有設定 belongs_to 的話會怎樣?
A. 其實設定has_one 跟 belongs_to的目的不是建立資料表的關聯,是在資料庫外一層(MODEL),用模擬的方式建立關聯性,不是真的在資料表拉關連線,是透過xxx_id的欄位在做互相查詢。
• Q.xxx_id 一定要叫這個名字嗎?
A. (慣例),但可自行設定。
# 初始id為owner_id ↓指定外部鍵為'u_id'
belongs_to :owner, foreign_key: 'u_id'
```
<font color="red">**has_many :through(多對多)**</font>

```
• 雖然有點複雜,但用起來的手感跟 has_many 差不多
• 需要第三個資料表來儲存兩邊的資訊
• 第三個資料表通常僅存放兩邊的 id,並且 belongs_to 兩邊的 Model
```
```ruby=
# ununtu建立第三方資料表
$ rails g model WareHouse sotre:references product:references
$ rails db:migrate
```
```ruby=
# app > models > ware_house.rb
class WareHouse < ApplicationRecord
# 倉庫屬於商店也屬於商品
belongs_to :store
belongs_to :product
end
# app > models > store.rb
class Store < ApplicationRecord
#一間商店只屬於一位店長
belongs_to :owner
# 先建立與倉庫的關聯
has_many :ware_houses
# 商店想要知道自己有哪些商品的話,但沒有直接關連,相關資訊要透過ware_houses(倉庫)
has_many :products, through: :ware_houses
end
# app > models > product.rb
class Product < ApplicationRecord
# 先建立與倉庫的關聯
has_many :ware_houses
# 商品可以在那些商店賣or找的到,但沒有直接關連,相關資訊要透過ware_houses(倉庫)
has_many :stores, through: :ware_houses
end
```
```ruby=
# ubuntu裡的rails c建立三筆商品資料
$ p1 = Product.new(name: 'p1')
$ p2 = Product.new(name: 'p2')
$ p3 = Product.new(name: 'p3')
# store1號店有p1, p2, p3商品
$ s1.products = [p1, p2, p3]
# store2號店有p1, p2商品
$ s2.products = [p1, p2]
# 查詢store1號店有賣幾種商品
$ s1.products.count
=> 3
# 查詢store2號店有賣幾種商品
$ s2.products.
=> 2
```
### 8.2 ORM 基本操作
[**教學連結 - Model 基本操作之 CRUD**](https://railsbook.tw/chapters/16-model-basic.html)
CRUD = **C**reate, **R**ead, **U**pdate, **D**elete
- <font color="red">ORM 基本操作之 **C**reate</font>
```
• new 方法會建立一筆資料,但還不會存到資料庫裡。
• create 或 create! 會建立一筆資料,並直接寫入資料庫裡。
• Q. create 跟 create! 這兩個方法有什麼不同?
A. 它跟 create 方法一樣會把資料直接寫入資料表,當資料寫入發生錯誤時,create 方法會默默的回傳 nil(事實上是一個 Model 物件),而 create! 方法則會產生例外(Exception)訊息。
```
```ruby=
book = Product.new(name: 'Ruby book', price: 350)
book.save # 到這⼀步才存到資料庫裡
# 建立並同時存到 products 表格裡
Product.create(name: 'Ruby book', price:350)
```
- <font color="red">ORM 基本操作之 **R**ead</font>
```
• first 與 last
• find_by(id: 1) 與 find(1)
• find_by_sql
• find_each
```
```ruby=
Product.first # 找到第⼀筆資料
Product.last # 找到最後⼀筆資料
Product.find(1) # 找到 id = 1 的資料, 找不到時回傳nil
Product.find_by(id: 1) # 找到 id = 1 的資料, 找不到時回傳錯誤訊息
# 直接使⽤ SQL 查詢
Product.find_by_sql("select * from products where id = 1")
# batch find(批量抓取資料一次一千筆)
Product.find_each do | product |
# ...
end
```
```
• all
• select
• where
• order
• limit
```
```ruby=
Product.all # 找出所有產品
Product.select('name') # 同上,但只選取 name 欄位
Product.where(name: 'Ruby') # 找出所有 name 欄位是 Ruby 的資料
Product.order('id DESC') # 依照 id ⼤⼩反向排序
Product.order(id: :desc) # 同上
Product.limit(5) # 只取出 5 筆資料
```
```
• count
• average
• sum
• maximum 與 minimum
• 不要傻傻的用 ORM 抓出來轉迴圈再來計算!
```
```ruby=
Candidate.cound # 算出幾位候選人
Candidate.average(:age)# 算出候選人平均年齡
Candidate.sum(:age)# 算出候選人平均年齡
Candidate.maximum(:age)# 找出候選人最大年齡
Candidate.minimum(:age)# 找出候選人最小年齡
```
- <font color="red">ORM 基本操作之 **U**pdate</font>
```
• save
• update
• update_attributes
• update_all
```
```ruby=
p1 = Product.find_by(id: 1)
p1.name = 'Ruby book' # 1.改名
p1.save # 2.儲存
# 更新↓下面兩種方法功能都一樣。第二種語法較長不常使用
p1.update(name: 'Ruby book')
p1.update_attributes(name: 'Ruby book')
# 該資料表全部更新成(輸入的值)(請⼩⼼使⽤!)
Product.update_all(name: 'Ruby book', price: 250)
````
```
• increment 與 decrement
• toggle
```
```ruby=
my_order = Order.first
my_order.increment(:quantity) # quantity 欄位的值 + 1
my_order.toggle(:is_online) # 把原本的 true/false 值對調
# 記得要Save!!
my_order.save
```
- <font color="red">ORM 基本操作之 **D**elete</font>
```
• delete
• destroy
• destroy_all(condition = nil)
```
```ruby=
p1 = Product.find_by(id: 1)
# 刪除
p1.destroy
p1.delete
# 刪除編號 1 號的商品
Product.delete(1)
# 刪除所有低於 10 元的商品
Product.destroy_all("price < 10")
```
- <font color="blue">冷知識:**destroy 是真的把資料刪除嗎?**</font>
是真的會從資料表刪除該筆資料,使用上要多注意!!
### 8.3 Scope(範圍)方法
* • 把一群條件整理成一個 Scope
* • 簡化使用時的邏輯
* • 減少在 Controller 裡寫一堆 Where 組合
* • 用起來跟類別方法一樣
* • 其實也是一種類別方法
```ruby=
# Model
class Product < ApplicationRecord
# 定義 ↓自訂名 ↓給他一個Lambda(方法)
scope :available, -> { where(is_available: true)}
# ↓可以帶參數
scope :price_over, ->(p) { where(["price > ?", p])}
end
# Controller
class ProductsController < ApplicationController
def index
# ↓呼叫方法
@products = Product.available
end
end
```
```
• default_scope 可幫所有的查詢預設套用 scope
• default_scope 的副作用?所有的查詢條件都會帶入此方法。
```
```ruby=
class Product < ApplicationRecord
default_scope { order('id DESC') }
scope :available, -> { where(is_available: true) }
end
```
```
# 【練習題】
• scope 跟類別方法有什麼不一樣?
其實兩種都一樣,可以互相替換
• 什麼時候該用什麼寫法?
因人而異!!通常假如此方法要寫多行,就用類別方法,比較簡單就使用scope方法
```
### 8.4 資料驗證
[Scope 與類別方法](https://railsbook.tw/chapters/16-model-basic.html#scope-and-class-method)
```
•presence
•format
•uniqueness
•numericality
•length
•condition
```
```ruby=
class Product < ApplicationRecord
# validates :驗證欄位, 驗證方法(欄位必須要存在)
validates :name, presence: true
end
# 舊式寫法
class Product < ApplicationRecord
validates_presence_of :name, :title, :price
end
# 驗證過程
>> p1 = Product.new # 忘了寫產品名稱,但還沒寫入資料庫
>> p1.errors.any? # 看看有沒錯誤...
=> false # 這時候還沒發⽣錯誤
>> p1.validate # 開始進⾏驗證(還不需要存檔喔)
=> false # 得到 false 表⽰沒有驗證成功
>> p1.errors.any? # 看看有沒有錯誤...
=> true # 這時候就有錯誤內容了
>> p1.errors.full_messages # 使⽤ full_messages 可把資料印出來
=> ["Title can't be blank"]
```
```
# 【練習題】
• 硬是要繞過驗證可以嗎?
可以只要寫進入的時候加上參數(validate: false),就可繞過驗證
• 只要有驗證就可以保證資料正確嗎?
假如還有跟別的系統共用的情況下就不一定,因不確定別的系統也有做相同驗證
```
### 8.5 Callback
```
• 當某物件.save時資料存檔的流程會經過以下流程:
• save > valid > before_validation > validate > after_validate > before_save >
before_create > create > after_create > after_save > after_commit
• 可在這些流程執行的時候對資料做一些事情
```
```ruby=
# 在資料存檔前對 Email 進⾏加密
class User < ApplicationRecord
# ↓要在哪個流程 ↓自訂義
before_create :encrypt_email
private
def encrypt_email
require 'digest'
self.email = Digest::MD5.hexdigest(email)
end
end
```
### 9 寄發信件
[**教學連結**](https://railsbook.tw/chapters/20-send-email.html)
[**Mailgun寄信設定,GCP第三方寄信服務手把手教學!**](https://digital-transformation.media/google-cloud-platform/mailgun-smtp/)
[**背景工作及工作排程**](https://railsbook.tw/chapters/21-background-job.html)
## 1 - 8 資料庫(Mysql)
### 1. 建立資料庫
- <font color="blue">冷知識:**SQL語法大小寫有差嗎?**</font>
SQL大小寫其實沒有分別、慣例上語法使用大寫,一般使用小寫。
- <font color="blue">**INT 跟 INTEGER 這兩種型態有什麼差別?**</font>
其實都一樣,INT可以讓你少打字。
- <font color="blue">**CHAR(10) 跟 VARCHAR(10) 這兩種型態有什麼差別?**</font>
• CHAR(10)->容納(255)字元
放 'abc' 3 個字的話,剩下的 7 個字元會填空白
放 'abcdefg' 7 個字的話,剩下的 3 個字會填空白
• VARCHAR(10)->容納(65535)字元
放 'abc' 3個字的話,它就是存 3 個字,再額外用 1 個 byte 存長度
放 'abcdefg' 7 個字的話,它就是存 7 個字,再額外用 1 個 byte 存長度
```sql=
-- 建立資料庫
create database hello-world;
-- 使用資料庫
use `hello-world`;
-- 刪除資料庫;
drop database `hello-world`;
```
- 資料型態
VARCHAR = **VAR**iable **CHAR**acter
- 建立資料表

MYSQL Workbench介面新增
```sql=
PK:primary key 主键
NN:not null 不能為空值
UQ:unique 唯一索引
BIN:binary 二進制數據(比text更大的二進制數據)
UN:unsigned 無符號 整數(非負數)
ZF:zero fill 填充0 例如字段內容是1 int(4), 則內容顯示為0001
AI:auto increment 自動遞增
G:generated column 生成列
```

```sql=
-- 建立資料表
-- ↓資料庫名 ↓資料表名
CREATE TABLE `hello-world`.`heroes` (
-- ↓欄位名稱
`id` INT NOT NULL AUTO_INCREMENT,
-- ↓型態
`name` VARCHAR(100) NOT NULL,
`gender` CHAR(1) NULL,
`age` INT NULL,
`hero_level` CHAR(1) NOT NULL,
`description` TEXT NULL,
PRIMARY KEY (`id`));
-- 追加欄位
ALTER TABLE heroes
ADD COLUMN super_power VARCHAR(100);
-- 刪除欄位
ALTER TABLE heroes
DROP COLUMN super_power;
-- 刪除資料表
DROP TABLE heroes;
```
- **DDL / DML / DQL**
- **D**ata **D**efinition **L**anguage 資料定義語言
- insert (建立)
- drop (刪除)
- alter
- **D**ata **M**anipulation **L**anguage 資料操作語言
- insert
- delete
- update
- **D**ata **Q**uery **L**anguage 資料查詢語言
- select
### 2. CRUD 之 C 新增資料
- 文字資料需要使用引號包起來
- 數字型態(整數、小數)則不需要使用引號

```sql=
-- 1.新增資料
INSERT INTO heroes
(name, gender, age, hero_level, hero_rank, description)
VALUES
('埼玉', 'M', 25, 'C', 388,'無論多強的敵人幾乎都是一拳擊敗');
-- 2.新增資料(省略欄位)
-- 注意這方法要填全部欄位否則出錯。
INSERT INTO heroes
VALUES
('埼玉', 'M', 25, 'C', 388,'無論多強的敵人幾乎都是一拳擊敗');
-- 3.新增資料(部份欄位)
INSERT INTO heroes
(name, hero_level, description)
VALUES
('埼玉', 'C','無論多強的敵人幾乎都是一拳擊敗');
-- 4.新增資料(多餘的單引號)
INSERT INTO heroes
(name, gender, age, hero_level, description)
VALUES
-- ↓如要保留單引號,前面加反協線即可。
('Speed-o\'-Sound Sonic', 'M', 25, 'S', '自稱「最強的忍者」');
```
- <font color="blue">冷知識:**寫入的欄位順序一定要照表格的順序嗎?**</font>
不用照順序也可以,不過要注意value要對上你所排的順序。
- <font color="blue">冷知識:**VARCHAR(10) 欄位如果放超過 10 個字的話會怎樣?**</font>
超過所設定上限,會出錯!!、往後設計資料表欄位時候,要事先想好。
### 3. CRUD 之 R 查詢資料

```sql=
-- 1.查詢所有女性 S 級英雄資料
-- ↓全部欄位
SELECT *
FROM heroes
--↓ 過濾 ↓追加
WHERE hero_level = 'S' AND gender = 'F';
-- 2.只顯示部分欄位
-- ↓ 部分欄位 ↓
SELECT name, gender
FROM heroes
WHERE hero_level = 'S';
-- 3.查詢沒填寫年齡的資料(空值)
SELECT *
FROM heroes
WHERE age IS NULL;
-- 4.名字裡有背心關鍵字的英雄
SELECT *
FROM heroes
WHERE name LIKE '%背心%';
-- 5.年齡在10 ~ 25 歲之間
SELECT *
FROM heroes
-- 寫法1.
WHERE age >= 10 AND age <= 25;
-- 寫法2. ↓區間
WHERE age BETWEEN 10 AND 25;
-- 6.列出所有 S 級 跟 A 級 的英雄
SELECT *
FROM heroes
-- 寫法1.
WHERE hero_level = 'S' OR hero_level = 'A';
-- 寫法2.
WHERE hero_level IN ('S', 'A');
-- 7.列出不是 S 級的英雄
SELECT *
FROM heroes
-- ↓不等於
WHERE hero_level <> 'S';
-- 8.列出不是 S 級也不是 A 級的英雄
SELECT *
FROM heroes
-- 寫法1.
WHERE hero_level <> 'S' AND hero_level <> 'A';
-- 寫法2.
WHERE hero_level NOT IN ('S', 'A')
```
### 4. CRUD 之 U 更新資料
```sql=
-- 1.更新資料
↓表格名稱
UPDATE heroes
↓設定
SET age = 10
↓條件
WHERE id = 25
-- 2.更新資料(多個欄位)
UPDATE heroes
SET age = 10, hero_level = 'A', hero_rank = 5
WHERE id = 25
-- 3.更新資料(全資料表)
-- sql預設禁止一次修改多次資料,如要修改打sql_safe_updates=0即可。
set sql_safe_updates=0;
UPDATE heroes
SET age = age + 1
```
- <font color="blue">冷知識:**使用 UPDATE 指令的時候如果忘了加 WHERE 條件會發生什麼事?**</font>
**A:**這樣會一次改多筆資料,mysql有安全模式下
會出錯,如要一次多筆修改加上set sql_safe_updates=0;即可。
- <font color="blue">冷知識:**為什麼在 MySQL 這樣寫不會更新資料?**</font>
<font color="red">UPDATE heroes
SET age = 0
WHERE age IS NULL;</font>
**A:**雖然給了where條件,但還是一次會改多筆資料的原因,安全模式下會不能修改、
要先把安全模式關閉才能修改。
### 5. CRUD 之 D 刪除資料
```sql=
-- 把 C 級英雄全部刪除
DELETE FROM heroes
WHERE hero_level = 'C';
```
### 6. 資料庫正規化
什麼是正規化?
正規化 = 設計資料表的優化
正規化 = 讓每個資料表存放應該存的資料就好
- 降低資料重複性

把 students 表格拆解成 students 與 departments

- 第一正規化**First Normal Form(1NF)**
- 每個欄位應該只有一筆資料
- 刪除資料中的重複群組
- 無論如何至少要做第一正規化


- 第二正規化(2NF)
- 需要滿足 1NF
- 去除「部份相依性(Partial Dependency)」
- 跟rails的多對多類似


- 第三正規化(3NF)
- 需要滿足 2NF
- 移除「遞移相依」(Transitive Dependency)


### 7. 更進階的查詢
```sql=
-- 1.「算出 A 級英雄總共有多少人」
-- ↓計數
SELECT COUNT(*)
FROM heroes
WHERE hero_level = 'A'
-- 2.「算出 A 級英雄的年齡總和」
-- ↓加總
SELECT SUM(age)
FROM heroes
WHERE hero_level = 'A' AND age IS NOT NULL;
-- 3.「算出 A 級英雄的年齡平均」
-- ↓平均
SELECT AVG(age)
FROM heroes
WHERE hero_level = 'A' AND age IS NOT NULL;
-- 4.「所有英雄裡最老的是幾歲」
-- ↓最大 ↓最小
SELECT MAX(age) --MIN(age)
FROM heroes;
-- 5.「算出每個等級英雄的年齡總和」
SELECT hero_level, SUM(age)
FROM heroes
-- ↓分組
GROUP BY hero_level;
-- 6.「列出總共有哪些災難等級」
-- ↓不同
SELECT DISTINCT danger_level
FROM monsters;
-- 7.「列出所有 S 級英雄並且依照位階排序」
SELECT *
FROM heroes
WHERE hero_level = 'S'
-- ↓排序
ORDER BY hero_rank;
-- 8.「列出所有 S 級英雄並且依照年齡由大到小排序」
SELECT *
FROM heroes
WHERE hero_level = 'S'
-- ↓反向 ↓正向
ORDER BY age DESC; --ASC
-- 9.「列出 S 級英雄的前 3 名」
SELECT *
FROM heroes
WHERE hero_level = 'S'
ORDER BY hero_rank
-- ↓限量
LIMIT 3;
```
### 8. 更多表格的查詢
[**教學連結**](https://campus.5xruby.tw/courses/489534/lectures/10959748)
- ER 圖(Entity-Relationship Diagram)
- Entity(代表一個個表單)
- Relationship(關聯、代表表單跟表單之間的連線)
ER圖操作流程:

- 主鍵 **Primary Key / PK**
- 舉例:每一個表單的ID就是PK
- 其實是某個資料表的一個欄位
- 獨一無二的值
- 不可以是 NULL
- 外部鍵 **Foreign Key / FK**
- 其實是某個資料表的一個欄位
- 參照到另一個資料表的主伴
- 用來確認該資料表的記錄跟被對照到的表格的資料是對得起來的
- 跟 PK 不同,FK 不需要獨一無二
- 而且也可以是 NULL
外部鍵ER圖:

流程1:建立外部鍵

流程2:選擇連接後表格更新或被刪除時候的選項。
**SET NULL = 空值、CASCADE = 一起更新、RESTRICT = 不給更新**

```sql=
-- 1.「列出被崎玉打倒的反派」
SELECT *
FROM monsters
WHERE kill_by =
(
-- ↓這裡是用PK跟FK連結的欄位
SELECT id
FROM heroes
WHERE name = '埼玉'
);
-- 2.「列出被崎玉跟傑諾斯打倒的反派」
SELECT name, danger_level
FROM monsters
WHERE kill_by IN
(
SELECT id
FROM heroes
WHERE name in ('埼玉','傑諾斯')
);
```
- 交集**inner / left / right**
```sql=
-- 1.反派是被誰打倒的
SELECT m.name, m.danger_level, h.name
-- ↓簡寫
FROM monsters m
LEFT JOIN heroes h
ON m.kill_by = h.id
WHERE m.kill_by IS NOT NULL;
```