---
# System prepended metadata

title: Ruby on Rails

---

## 建立專案
```bash
# 不指定版本
$ rails new project_name

# 指定版本
$ rails _6.1.4.6_ new project_name
```
### 啟動
```bash
$ rails s
```
### 安裝套件
```bash
# 安裝特定套件
$ gem install rubocop

# 安裝 Gemfile 內列出內容
$ bundle install
```

## 2025/11/10 rails install
```bash
# 安裝 rbenv & ruby-build
brew install rbenv ruby-build

# 把 rbenv 加到 zsh 啟動腳本
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
source ~/.zshrc
rbenv -v

# 用 rbenv 安裝 & 切換 Ruby 版本，gem 安裝 Rails
rbenv install 3.3.0
rbenv global 3.3.0
gem install rails
rails -v

# 如果 gem install rails 顯示 ou don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory
# 需要檢查當前路徑在哪裡，讓 rails 指向自己安裝的不是使用 mac 本身的 ruby
exec $SHELL -l
rbenv rehash

# 用已安裝好的 rails build project
rails new myapp
cd myapp
rails s

# 如果要指定版本
# 先安裝指定版本的 Rails gem
gem install rails -v 6.1.4.6

# 用該版本去產生專案
rails _6.1.4.6_ new myapp
cd myapp_6_1
bundle exec rails -v
```
## 指令
- rollback 會有風險，如果已經有資料存在，但必需得回滾的話，建議在新增一個 migration 修正。
- 新增欄位會產生一個空白的檔案，需要自行加上`add_column :articles, :photo, :string`，第一參數：資料表名稱、第二參數：欄位名稱、第三參數：資料型態。
```rb
# 建立 controller
$ rails g controller user

# 建立 model
$ rails g model User name:string age:integer

# 資料遷移
$ rails db:migrate

# 資料回滾
$ rails db:rollback

# 查看哪些 migration 已執行
# up:已執行、down:未執行
$ rails db:migrate:status

# 新增、修改、刪除資料表欄位
$ rails g migration add_phone_cache_to_user
$ rails g migration RenameDescription
```
## 數字 number
```rb
puts 10 / 3 # 3
puts 1 + 2  # 3
```
問題：要怎麼知道某個數字是不是奇數？
```rb
number = 10
if number % 2 == 0
  puts "偶數"
else
  puts "奇數"
end
```
或使用內建判斷做法
```rb
puts 10.odd?  # false
puts 10.even? # true
```
問題：要怎麼做「四捨五入到小數點第二位」？
```rb
# 四捨五入到小數點第二位
puts 3.3333333.round(2) # 3.33
# 四捨五入
puts 3.834.round        # 4
```
## 字串 string
字串安插
```rb
name = "Jeter"
age = 18

puts "你好，我是#{name}，我今年#{age}。"
# 你好，我是Jeter，我今年18。
```
字串長度計算
```rb
puts "Jeter".size # 5
```
計算有多少個指定字元
```rb
# 先使用 split 方法拆解成陣列，在使用 count 計算
words = "Ut in aliquam mauris. Donec dolor quam, sagittis id efficitur vel, convallis vitae tortor"
puts words.split.count # 14
```
字串的大小寫轉換
```rb
puts "Jeter".upcase      # 全大寫 JETER
puts "Jeter".downcase    # 全小寫 jeter
puts "Jeter".swapcase    # 自首小寫，其餘大寫 jETER
puts "jeter".capitalize  # 自首大寫，其餘小寫 Jeter
```
想要知道某個字母在字串中共出現幾次
```rb
words = "Lorem Ipsum Dolor Sit Amet, Consectetur Adipiscing Elit."
puts words.count("i") # 5

# for loop
words = "Lorem Ipsum Dolor Sit Amet, Consectetur Adipiscing Elit."
count = 0
words.each_char do |word|
    if word == "i"
        count += 1
    end
end

puts "字母 i 出現 #{count} 次。" # 字母 i 出現 5 次。
```
想知道字串是不是特定字元開題或結尾？或是包含指定字串或字元？
```rb
puts "Jeter".start_with?("J") # true
puts "Jeter".start_with?("j") # false

puts "Jeter".include?("J") # true
puts "Jeter".include?("j") # false
```
想把字串裡的某些字換成其它字
```rb
# sub 方法只會替換第一個遇到的字串。
puts "PHP is good, and I love PHP".sub(/PHP/, "Ruby")
# Ruby is good, and I love PHP

# gsub 方法會替換掉所有符合的字串。
puts "PHP is good, and I love PHP".gsub(/PHP/, "Ruby")
# Ruby is good, and I love Ruby
```
### empty?
檢查字串是否為空字串
```rb
puts "Jeter".empty? # false
puts " ".empty?     # false，空白字元不算空字串
puts "".empty?      # true
```
## 陣列 array
建立陣列的方法
```rb
p Array.new             # []
p Array.new(5)          # [nil, nil, nil, nil, nil]
p Array.new(5, "Ruby")  # ["Ruby", "Ruby", "Ruby", "Ruby", "Ruby"]
```
通常會使用`[]`方式
```rb
friends = ["Jeter", "Jason", "Jay"]
```
陣列取用
```rb
friends = ["Jeter", "Jason", "Jay"]
puts friends[0]  # Jeter
```
取得陣列第一或最後元素
```rb
friends = ["Jeter", "Jason", "Jay"]
puts friends.first  # Jeter
puts friends.last   # Jay
```
1. 把陣列`[1, 2, 3, 4, 5]`變成`[1, 3, 5, 7, 9]`
```rb
number = [1, 2, 3, 4, 5]
result = []
number.each do |num|
    result << num * 2 - 1
end

p result  # [1, 3, 5, 7, 9]
```
或是可以使用`map`
`map`方法會對陣列中的元素做某件事後再收集成新的陣列。
```rb
number = [1, 2, 3, 4, 5]
p number.map { |num| num * 2 - 1} # [1, 3, 5, 7, 9]
```
2. 計算從 1 加到 100 的總和
```rb
total = 0
(1..100).to_a.each do |i|
    total = total + i
end

puts total  # 5050
```
或是使用`*`展開
```rb
total = 0
[*1..100].each do |i|
    total += i
end

puts total  # 5050 
```
`reduce`方法會對每個元素不斷的`互動(例子為相加)`，再把最後結果收集起來。
```rb
puts [*1..100].reduce(0) { |total, num| total + num }  #. 5050
```
3. 請印出 1 ~ 100 數字中所有的單數
```rb
result = []
[*1..100].each do |num|
    if num % 2 != 0
        result << num
    end
end

p result
# [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99]
```
`select`方法可篩選出符合條件的元素並收集成陣列。
```rb
p [*1..100].select { |num| num % 2 != 0 }
# [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99]
```
要計算奇數、偶數可利用數字物件內建方法
```rb
p [*1..100].select { |i| i.odd? }
```
4. 如果想要從 1 到 100 中隨便取 5 個不重複的亂數
```rb
puts [*1..100].sample(5)
```
## 範圍 range
.. 和 ... 差異
```rb
(1..5).to_a  # [1, 2, 3, 4, 5]
(1...5).to_a # [1, 2, 3, 4]
```
範圍也可使用`each` or `map`
```rb
(1..5).each { |x| puts x } # 1 2 3 4 5
(1..5).map { |x| x * 2 }   # [2, 4, 6, 8, 10]
```
取陣列內容也可使用範圍
```rb
languages = ["ruby", "python", "php", "javascript", "rails"]
languages[1..3] # ["python", "php", "javascript"]
```
## 雜湊 hash
Ruby 1.9 版本前後差異如下，但意思上是相同的。
```rb
old_hash = {:title => "Ruby", :price => 350}
new_hash = {title: "Ruby", price: 350}
puts old_hash == new_hash # true
```
Ruby 中要取得`hash`內容是需要使用`symbol`也就是`:`冒號，`:`和`" "`是不同東西，例如：`profile[:name]` and `profile["name"]`。
```rb
profile = {name: "Jeter", age: 10}
puts profile[:name]
```
1. 要怎麼把 Hash 裡的東西一個一個印出來？
```rb
profile = {name: "Jeter", age: 10}
p profile.keys   # [:name, :age]
profile = {name: "Jeter", age: 10}
p profile.values # ["Jeter", 10]
```
如果要一個一個打印出來
```rb
profile = {name: "Jeter", age: 10}
profile.each do |elements|
    p elements
end
# [:name, "Jeter"]
# [:age, 10]
```
如果要個別取得`key`and`value`
```rb
profile = {name: "Jeter", age: 10}
profile.each do |key, value|
    puts key
    puts value
end
# name
# Jeter
# age
# 10
```
## 符號 symbol
```rb
class Order
  attr_reader :status

  def initialize(items, status = :pending)
    @items = items
    @status = status
  end

  def complete
    @status = :complete
  end
end

order = Order.new(["item A", "item B", "item C"])

if order.status == :pending
  puts "order is pending"
elsif order.status == :complete
  puts "order is complete"
else
  puts "not started"
end
# order is pending
# 如果呼叫 complete 會改變輸出內容
order.complete
if order.status == :pending
  puts "order is pending"
elsif order.status == :complete
  puts "order is complete"
else
  puts "not started"
end
# order is complete
```
`symbol` and `string` 差異
前者無法改變內容，後者可以，所以可把`symbol`看成`不可變`的字串。
```rb
"hello"[0] = "k"
# k
:hello[0] = "k"
# `<main>': undefined method `[]=' for :hello:Symbol (NoMethodError)
```
`symbol` and `string` 是可以互相轉換的

## method and block
`method`定義和`python`是一樣的，都是使用`def`作為開頭，差別在`ruby`在結尾會加上`end`。
```rb
def hello(name)
  puts "hello, #{name}"
end
hello("Jeter") # hello, Jeter
```
`ruby`可將方法使用`symbol`表示，依照下列程式碼來看可以把陣列中的`符號symbol`使用`send`方法直接呼叫，意思是會讓`:hello`這個符號去尋找對應的名為`hello`的方法然後執行。
```rb
def hello
  puts "hello."
end

def goodbye
  puts "goodbye."
end

methods = [:hello, :goodbye]
methods.each do |method|
  send(method)
end
# hello.
# goodbye.
```
參數預設值
如果有帶參數給方法那就會使用參數內容，否則就會使用預設值 "hi"。
```rb
def hello(message = "hi")
  "message: #{message}"
end
p hello("goodgood")  # "message: goodgood"
p hello()            # "message: hi"
```
在 Ruby 中`return`可被省略
```rb
def bmi(height, weight)
  weight / height ** 2
end
puts bmi(1.60, 60)  # 23.43
```
Ruby 定義方法時可以在命名後方加上`?`或是`!`，記住！只能加在命名後方。
`?` => 布林值
`!` => 改變原始資料結構
```rb
def hello?(age)
    age >= 20
end
puts hello?(18)  # false
```
```rb
# 未使用 !
number_list = [1, 2, 3, 4, 5]
reversed_list = number_list.reverse
p reversed_list  # [5, 4, 3, 2, 1]
p number_list    # [1, 2, 3, 4, 5]

# 使用 !
number_list = [1, 2, 3, 4, 5]
reversed_list = number_list.reverse!
p reversed_list  # [5, 4, 3, 2, 1]
p number_list    # [5, 4, 3, 2, 1]
```
Block
使用 yield 方法，暫時把控制權交棒給 Block，等 Block 執行結束後再把控制權交回來
```rb
def hello
  puts "開始"
  yield
  puts "結束"
end

hello {
  puts "這裡是 block"

# 開始
# 這裡是 block
# 結束
```
Block 並不是物件，所以無法單獨存在於 Ruby 語言中，所以需要依附在方法或是物件後方。
但如果使用`Proc`類別就可以將 Block 物件化，要使用時需要再該物件使用`call`
```rb
greeting = Proc.new { puts "hello world." }
greeting.call  # hello world.
```
也可以帶參數
```rb
hello = Proc.new { |name| puts "hello, #{name}" }
hello.call("Jeter")  # hello, Jeter
```
`Proc`呼叫方法
![截圖 2025-04-22 15.36.24](https://hackmd.io/_uploads/BJEbD6Nkge.png)
## 類別 class 和 模組 module
類別必需使用常數命名，有了類別後就可以使用`new`方法產生實體(instance)。
```rb
class Cat
  def eat(food)
    puts "#{food} 好好吃"
  end
end

cat = Cat.new
cat.eat "肉泥"  # 肉泥 好好吃
```
在 Ruby 中如果要透過`new`方法傳入參數，就必需得在物件中定義一個名為`initialize`的方法，不能取名為`init`、`__init__`等。
```rb
class Cat
  def initialize(name, gender)
    @name = name
    @gender = gender
  end

  def hello
    puts "hello, my name is #{@name}."
  end
end

cat = Cat.new("Jeter", "female")
cat.hello  # hello, my name is Jeter.
```
實體方法和類別方法
```rb
class Cat
  def initialize(name, gender)
    @name = name
    @gender = gender
  end

  def hello
    puts "hello, my name is #{@name}."
  end
end

cat = Cat.new("Jeter", "female")
cat.hello  # 作用在 cat 所產生的實體，所以 hello 為實體方法

class PostsController < ApplicationController
  def index
    # 取得所有 Post 資料，這裡的 all 方法作用在 Post 類別上，所以稱作類別方法
    @posts = Post.all
  end
end
```
定義類別方法有幾種方式：
1. 在方法前方加上`self`。
```rb
class Cat
  def self.all
    
  end
end
```
這樣就可使用`Cat.all`呼叫。
2. 
```rb
class Cat
  class << self
    def all
    
    end
  end
end
```
方法的存取控制
- public => 預設公開，所有人都可以使用。
- protected => 可在同個類別內部使用，或是繼承該類別的子類別可使用。
- private => 只在類別內部使用。
```rb
class Cat
  def eat
    puts "好吃"
  end

  protected
  def sleeping
    puts "zzzz....."
  end

  private
  def gossip
    puts "hello!!!"
  end
end

cat = Cat.new
cat.eat       # 好吃
cat.sleeping  # NoMethodError
cat.gossip    # NoMethodError
```
但也不是`private`就無法被取得，利用`send`方法把要呼叫的方法當作參數傳遞就可以被取得。
```rb
cat.send(:gossip)  # hello!!!
```
繼承
```rb
class Animal
  def food(eat)
    puts "#{eat}好好吃！"
  end
end

class Cat < Animal

end

class Dog < Animal

end

food = Cat.new
food.food("罐頭")  # 罐頭好好吃！
```
模組
不一定都需要使用繼承方式，利用`module`就可讓類別`include`後使用。 
```rb
module Flyable
  def fly
    puts "我會飛！！！"
  end
end

class Cat
  include Flyable
end

cat = Cat.new
cat.fly  # 我會飛！！！
```
## Routes
使用`resources`會讓 rails 自動產生 8 條路徑，並對應到 controller 的 7 個方法
```bash
Prefix    Verb   URI Pattern               Controller#Action
users     GET    /users(.:format)             users#index
          POST   /users(.:format)             users#create
new_user  GET    /users/new(.:format)         users#new
edit_user GET    /users/:id/edit(.:format)    users#edit
user      GET    /users/:id(.:format)         users#show
          PATCH  /users/:id(.:format)         users#update
          PUT    /users/:id(.:format)         users#update
          DELETE /users/:id(.:format)         users#destroy
```
分別會是：
- index   --> 使用者列表
- create  --> 新增使用者
- new     --> 新增使用者頁面
- show    --> 檢視單一使用者
- update  --> 更新資使用者
- edit    --> 編輯使用者頁面
- destroy --> 刪除使用者

prefix 用意，在後面接上`_path`或`_url`後可以變成「產生相對應的路徑或網址」的 View Helper。如果是站內連結，通常會使用`_path`寫法來產生站內的路徑，例如：
```rb
products + path     = products_path         => /products
new_product + path  = new_product_path      => /products/new
edit_product + path = edit_product_path(2)  => /products/2/edit
```
如果是使用`_url`則會產生完整的路徑，包括主機網域名稱：
```rb
products + url     = products_url         => http://kaochenlong.com/products
new_product + url  = new_product_url      => http://kaochenlong.com/products/new
edit_product + url = edit_product_url(2)  => http://kaochenlong.com/products/2/edit
```
轉址
```rb
# 當使用者進入到 /me 這路徑會轉址到 redirect 頁面，當然路徑也是可以調整的，
# 例如：/me.php 這樣做看起來是個 php 做的網站，但其實是使用 Rails 做的，所以這部分滿特別！？
get "/me", to: redirect("https://www.highercloud.com.tw/")
```
在路徑顯示部分，如不需要一次產生 8 條路徑可以使用`only`或`except`來控制，例如：
```rb
# 只顯示 index and create routes
resources :users, only: [:index, :create]
# 這些不顯示
resources :users, except: [:new, :edit, :update, :destroy]
```
單數 resource 不會產生`:id`路徑且只會產生 7 條路徑(沒有 index)。
```rb
resource :user
# new_user   GET   /user/new(.:format)   users#new
```
#### 單複數 resources 使用情境大致可分成，設計使用者只能夠 CRUD 自己的個人資料時使用單數 resource，如果是管理員可以 CRUD 使用者的個人資料時就使用複數 resources。
#### 大腸包小腸設計
可以理解成：
```bash
/users/1 --> 檢視使用者 1 號的資料
/users/1/posts --> 檢視使用者 1 號的所有文章
/users/1/posts/2 --> 檢視使用者 1 號的 2 號文章
/users/1/posts/2/edit --> 編輯使用者 1 號的 2號文章
```
```bash
        Prefix Verb   URI Pattern                              Controller#Action
    user_posts GET    /users/:user_id/posts(.:format)          posts#index
               POST   /users/:user_id/posts(.:format)          posts#create
 new_user_post GET    /users/:user_id/posts/new(.:format)      posts#new
edit_user_post GET    /users/:user_id/posts/:id/edit(.:format) posts#edit
     user_post GET    /users/:user_id/posts/:id(.:format)      posts#show
               PATCH  /users/:user_id/posts/:id(.:format)      posts#update
               PUT    /users/:user_id/posts/:id(.:format)      posts#update
               DELETE /users/:user_id/posts/:id(.:format)      posts#destroy
         users GET    /users(.:format)                         users#index
               POST   /users(.:format)                         users#create
      new_user GET    /users/new(.:format)                     users#new
     edit_user GET    /users/:id/edit(.:format)                users#edit
          user GET    /users/:id(.:format)                     users#show
               PATCH  /users/:id(.:format)                     users#update
               PUT    /users/:id(.:format)                     users#update
               DELETE /users/:id(.:format)                     users#destroy
```
在路徑設計上因檢視特定資料、編輯、刪除、更新的操作並不一定得知道使用者是誰，所以可以不需要列出所有 routes，從下方設定來看，可看出文章部分因 index、new、create 需要知道 users_id，所以需要包在 users 內，但是 show、update、edit、destroy 並不一定需要知道 users_id 所以可以獨立在外層。
```rb
Rails.application.routes.draw do
  resources :users do
    resources :posts, only: [:index, :new, :create]
  end
  resources :posts, only: [:show, :edit, :update, :destroy]
end
```
後台網址設定，就是加一個 /admin 路徑在前方，想要做到這樣的網址設計可以直接在 resources 後面接上 path。
```rb
Rails.application.routes.draw do
  resources :products, path: "/admin/products"
end
```
```bash
      Prefix Verb   URI Pattern                        Controller#Action
    products GET    /admin/products(.:format)          products#index
             POST   /admin/products(.:format)          products#create
 new_product GET    /admin/products/new(.:format)      products#new
edit_product GET    /admin/products/:id/edit(.:format) products#edit
     product GET    /admin/products/:id(.:format)      products#show
             PATCH  /admin/products/:id(.:format)      products#update
             PUT    /admin/products/:id(.:format)      products#update
             DELETE /admin/products/:id(.:format)      products#destroy
```
但在做後台的時候，更常使用 namespace 方法來把 resources 包起來，產生後台的專屬路徑，這樣產生的專屬路徑會在前方加上 /admin。
```rb
Rails.application.routes.draw do
  namespace :admin do
    resources :products
    resources :articles
  end
end
```
```bash
            Prefix Verb   URI Pattern                        Controller#Action
    admin_products GET    /admin/products(.:format)          admin/products#index
                   POST   /admin/products(.:format)          admin/products#create
 new_admin_product GET    /admin/products/new(.:format)      admin/products#new
edit_admin_product GET    /admin/products/:id/edit(.:format) admin/products#edit
     admin_product GET    /admin/products/:id(.:format)      admin/products#show
                   PATCH  /admin/products/:id(.:format)      admin/products#update
                   PUT    /admin/products/:id(.:format)      admin/products#update
                   DELETE /admin/products/:id(.:format)      admin/products#destroy
    admin_articles GET    /admin/articles(.:format)          admin/articles#index
                   POST   /admin/articles(.:format)          admin/articles#create
 new_admin_article GET    /admin/articles/new(.:format)      admin/articles#new
edit_admin_article GET    /admin/articles/:id/edit(.:format) admin/articles#edit
     admin_article GET    /admin/articles/:id(.:format)      admin/articles#show
                   PATCH  /admin/articles/:id(.:format)      admin/articles#update
                   PUT    /admin/articles/:id(.:format)      admin/articles#update
                   DELETE /admin/articles/:id(.:format)      admin/articles#destroy
```
如果不希望後台路徑讓有心人士猜到，可以使用加入 path，這樣一來就把原本的 /admin 改變成自定義的內容，不易猜測。
```rb
Rails.application.routes.draw do
  # namespace
  namespace :admin, path: "saoidfai" do
    resources :products
  end
end
```
```bash
            Prefix Verb   URI Pattern                              Controller#Action
    admin_products GET    /saoidfai/products(.:format)          admin/products#index
                   POST   /saoidfai/products(.:format)          admin/products#create
 new_admin_product GET    /saoidfai/products/new(.:format)      admin/products#new
edit_admin_product GET    /saoidfai/products/:id/edit(.:format) admin/products#edit
     admin_product GET    /saoidfai/products/:id(.:format)      admin/products#show
                   PATCH  /saoidfai/products/:id(.:format)      admin/products#update
                   PUT    /saoidfai/products/:id(.:format)      admin/products#update
                   DELETE /saoidfai/products/:id(.:format)      admin/products#destroy
```
## RESTful 設計
根據`CRUD`有不同的操作方式，例如：
```bash
# 取得所有會員列表
/users
# 取得 user id 為 123 的資料
/users/123
# 取得 user id 為 123 的貼文
/users/123/posts
# 編輯 user id 為 123 的資料
/users/123/edit
```
## Controller
定義邏輯的地方，當使用者進入到某頁面例如：`/hello_world`，這時會去`controller`尋找對應的控制器找尋對應方法，像是到`PagesController`中找尋`hello方法`，接著`Rails`會自動判斷到`app/views`資料夾中找`controller`名字目錄裡同名的檔案，因`controller`取名為`PagesController`所以會到`app/views/pages`資料夾中找`controller`裡和方法相同的檔案。
流程會是：`app/controller/pages_controller.rb --> app/views/pages/hello.html.erb`
```rb
# config/routes.rb
get "/hello_world", to: "pages#hello"
  # resources 可自動產生 8 條不同的路徑，並對應到 controller 的 7 個方法
  resources :posts
  resources :users
  
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def hello
    
  end
end

# app/views/pages/hello.html.erb
<h1>hello world</h1>
<h2>這是個 HTML 檔案！！！！！</h2>
```
## params
```rb
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def hello
    render json: params
  end
end
```
當使用者輸入：`http://127.0.0.1:3000/hello_world?name=Jeter&age=20`。
會得到以下像是 hash 的回傳內容。
```bash
{"name":"Jeter","age":"20","controller":"pages","action":"hello"}
```
如果只想取得 name 參數可以這樣操作
```rb
class PagesController < ApplicationController
  def hello
    render plain: params["name"]
  end
end
```
不管是`get`or`post`方法傳遞的參數，都會被收集到`params`中。
## 練習：BMI 計算器
`form_tag`會被轉換成`<form> tag`、
`text_field_tag`會被轉換成`<input type="text"/>`、
`submit_tag`會被轉換成`<input type="submit"/>`
以上這些方法都統稱為`View Helper`。
```rb
# config/routes.rb
Rails.application.routes.draw do
  get "bmi", to: "bmi#index"
  post "bmi/result", to: "bmi#result"
end

# app/controllers/bmi_controller.rb
class BmiController < ApplicationController
  def index
  end

  def result
    height = params[:body_height].to_f / 100
    weight = params[:body_weight].to_f
    @bmi = (weight / (height * height)).round(2)
    
    if @bmi >= 27
      @bmi_info = "肥胖。"
    elsif @bmi >= 24 && @bmi <27
      @bmi_info = "過重。"
    elsif @bmi >= 18.5 && @bmi < 24
      @bmi_info = "健康。"
    else
      @bmi_info = "過輕。"
    end
  end
end

# app/views/bmi/index.html.erb
<h1>BMI 計算機</h1>
<%= form_tag '/bmi/result' do %>
  身高：<%= text_field_tag 'body_height' %> 公分<br />
  體重：<%= text_field_tag 'body_weight' %> 公斤<br />
  <%= submit_tag "開始計算" %>
<% end %>

# app/views/bmi/result.html.erb
<h1>您的 BMI 為：<%= @bmi %></h1>
```
## form_for 和 form_tag
通常在`form_for`後面 block 裡會放`f`變數，是一種 FormBuilder物件，可以透過這個物件上的`text_field`、`text_area`、`submit`方法做出對應的`<input> tag`。
form_for:
- 可接一個 Model 物件參數
- 根據 Model 屬性自動生成輸入欄位
- 表單自動加入 Model 實例的值

form_tag:
- 不一定需要綁定到 Model，常用於通用表單
- 需要手動指定動作 URL

在`Controller`的`new`方法設定的實體變數`@candidate`，就是要給剛剛前面`form_for`用的。
`form_for`除了可以產生`<form> tag`之外，它的`action`，也就是當你按下送出按鈕要去的那個地方，會根據傳給它的這個物件是新的還是舊的而自己判斷。
在這裡因為是剛剛才做出來的，`form_for`會認為你現在是要做「新增」這件事。
## 資料關聯
- 一對一
`optional: true`表示允許 user 為填寫，如果沒有這樣做會產生 nil。
1. has_one 跟 belongs_to 方法需要同時設定嗎？
不一定，看實際需求而定。以 User 跟 Store 的例子來看，如果不需要「從 Store 反查 User」的功能，那 belongs_to 就不需要設定了。
```rb
# app/models/user.rb
class User < ApplicationRecord
  has_one :store
end

# app/models/store.rb
class Store < ApplicationRecord
  belongs_to :user, optional: true
end
```
- 一對多
```rb
# app/models/store.rb
class Store < ApplicationRecord
  belongs_to :user
  has_many :products
end

# app/models/product.rb
class Product < ApplicationRecord
  belongs_to :store
end
```
- 多對多
沒辦法在兩邊的 model 定義`has_many`or`belongs_to`來做關聯，通常需要第三方資料表儲存兩邊的資訊。
1. `references`會自動加上索引(index)，加速查詢速度。 
2. 自動幫 model 加上 belongs_to
```bash
$ rails g model WareHouse store:references product:references
```
```rb
# app/models/ware_house.rb
class WareHouse < ApplicationRecord
  belongs_to :store
  belongs_to :product
end

# app/models/store.rb
class Store < ApplicationRecord
  belongs_to :user

  has_many :ware_houses
  has_many :products, through: :ware_houses
end

# app/models/product.rb
class Product < ApplicationRecord
  has_many :ware_houses
  has_many :stores, through: :ware_houses
end
```
多對多關聯除了透過`has_many...through`還可以透過`has_and_belongs_to_many`，這樣就不需透過第三方 model，但還是得有第三方資料表。
```rb
# app/models/product.rb
class Product < ApplicationRecord
  has_and_belongs_to_many :stores
end

# app/models/store.rb
class Store < ApplicationRecord
  has_and_belongs_to_many :products
end
```
## 備註
`increment`方法是`Rails`中`Active Record`提供的一個方法，用於增加資料庫記錄中特定欄位的值並儲存到資料庫。
- increment
只在記憶體中增加值，並不會儲存進資料庫。
- increment!
增加值並儲存進資料庫。
```rb
@candidate.increment!(:votes)
```
## before_action 方法
在 controller 中有個 before_action 方法，指的是讓某個 action 在執行之前先做某件事。
```rb
class CandidatesController < ApplicationController
  before_action :find_candidate, only: [:edit, :update, :destroy, :vote, :show]

  def vote
    if @candidatemodel 寫入到 vote_logs 資料表
      @candidate.vote_logs.create(ip_address: request.remote_ip)
    end
    redirect_to candidates_path, notice: "完成投票。"
  end

  def index
    @candidates = Candidate.order(id: :asc)
  end

  def new
    @candidate = Candidate.new
  end

  def create
    @candidate = Candidate.new(candidate_params)
    if @candidate.save
      redirect_to candidates_path, notice: "新增候選人成功。"
    else
      render :new
    end
  end

  def show
  end

  def edit
  end

  def update
    if @candidate.update(candidate_params)
      redirect_to candidate_path, notice: "資料更新成功。"
    else
      render :edit
    end
  end

  def destroy
    if @candidate
      @candidate.destroy
    end
    redirect_to candidate_path, notice: "候選人資料已刪除。"
  end

  # strong parameters，可對 params 內容資料做過濾，permit 方法表示只允許這些參數通過，其他的無視，算是開一個白名單概念。
  private
  def candidate_params
    params.require(:candidate).permit(:name, :age, :party, :politics)
  end

  def find_candidate
    # 透過 params 取得的所有資料型態都會是字串，如果要直接拿來做數學運算，需要使用 to_i 或 to_f 方法轉換型態
    @candidate = Candidate.find_by(id: params[:id])
  end
end
```
## yield
當 controller 在處理 view 時會先透過`app/views/layouts/application.html.erb`然後將`.hmtl.erb`的檔案填入`yield`中，這樣做主要是不需寫一堆重複的`HTML`。
1.`csrf_meta_tags`方法會在頁面上產生`<csrf-param>`跟`<csrf-token>`兩個`<meta>`標籤，用途主要是確保網站較不容易受到 CSRF（Cross-site request forgery）攻擊。
2. `stylesheet_link_tag`方法會轉換成 CSS 的`<link rel="stylesheet">`標籤。
3.`javascript_include_tag`方法會轉換成`JavaScript`的`<script>`標籤。
在`app/views/layouts/application.html.erb`定義好`yield`空格位置後，可在需要的模板使用`provide`填空。
```rb
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield :my_title %></title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= notice %>
    <%= yield %>
  </body>
</html>

# app/views/candidates/index.html.erb
<% provide :my_title, "你好，我是Title" %>
<h1>候選人列表</h1>

# app/views/candidates/new.html.erb
<% provide :my_title, "新增候選人" %>
<h1>新增候選人</h1>

<%= render "form", candidate: @candidate %>

<br />
<%= link_to "回候選人列表", candidates_path %>
```
## View Helper
`link_to`、`form_for`都算是一種 view helper
## attr_accessor
定義`getter`和`setter`方法，建立`可讀取`、`可設定`的屬性。
## validates
驗證機制，檢查資料是否符合規定。
## Model
Model 跟資料表通常是一對一沒錯，但並不是絕對的，有時候 Model 跟資料表一點關係都沒有。
### 慣例
Model 在命名上必需是大寫開頭的單數，例如：User。
資料表則是小寫或下底線的複數命名，例如：user_projects
## CRUD
### C
透過 model 新增一筆資料有`new`和`create`兩種方法，
使用`new`的話後續還要接上`save`才會把資料儲存進資料表，
如果使用`create`方法則不需要接上`save`。
### R
讀取資料，
`first & last` --> 取得第一筆或是最後一筆資料。
`find & find_by` --> 找到指定 id 的人。
差別是`find_by`方法如果找不到指定 id 的資料，僅會回傳 nil 物件，但`find`方法會直接產生`ActiveRecord::RecordNotFound`的例外訊息。
```rb
irb(main):001:0> Candidate.find_by(id: 9487)
  Candidate Load (1.3ms)  SELECT "candidates".* FROM "candidates" WHERE "candidates"."id" = $1 LIMIT $2  [["id", 9487], ["LIMIT", 1]]
=> nil
```
```rb
Candidate.first     # 取得第一筆資料
Candidate.first(5)  # 取得前三筆資料
Candidate.order(age: :desc).first  # 取出年紀最大的候選人
Candidate.order(age: :desc).limit(3)  # 限制取得筆數
Candidate.order(:age)  # 小排到大
Candidate.find(1)
Candidate.find_by(id: 1)
Candidate.all  # 取得所有資料
Candidate.where("age > 18")  # 條件篩選
```
### U
更新資料常用的有`save`、`update`、`update_attribute`、`update_attributes`方法。
```rb
# 先找出要更新的人
user = Candidate.find_by(id: 1)
# 使用 save
candidate.name = "Jeter"
candidate.save
# 使用 update_attribute 方法更新單一欄位的值（注意：方法名字是單數)
Candidate.update_attribute(:name, "Jeter")
# 使用 update 更新資料，可一次更新多個欄位，且不需要再呼叫 save 方法
candidate.update(name: "Jeter", age: 18)
# 使用 update_attributes 方法
candidate.update_attributes(name: "Jeter", age: 18)
```
### D
刪除可以使用`delete`或`destroy`
```rb
# 先找出要操作的資料
user = Candidate.find_by(id: 1)
# 再把資料刪除
user.destroy  # destroy 會有完整回呼(callback)
user.delete   # 執行 SQL 的 DELETE FROM
```
## Migration
檔名符合某些字樣時，例如：add...to... 或是 remove...from ...，後面再加一些欄位就可以自動幫你產生一個寫好的 Migration 檔案。
```rb
$ rails g migration add_candidate_id_to_articles candidate_id:integer:index
```
```rb
class AddCandidateIdToArticles < ActiveRecord::Migration[5.0]
  def change
    add_column :articles, :candidate_id, :integer
    add_index :articles, :candidate_id
  end
end
```
## schema
這檔案內容是再執行`rails db:migrate`時自動產生的。
## seeds
要在建立資料表時順便產生初始資料時可以在`db/seeds.rb`檔案中做。
```rb
$ rails db:seed
```
## 資料驗證
- 在 Model 加上驗證
希望每篇文章的標題都是必填的。
```rb
# app/models/article.rb
class Article < ApplicationRecord
  validates :title, presence: true
end
```
只有以下方式會觸發驗證：
- create
- create!
- save
- save!
- update
- update!
## 背景工作及排程
產生一個任務
```bash
rails g job user_confirm_email
```
## 寫測試
- 安裝測試工具
```bash
$ gem install rspec
```
- 在任意地方建立資料夾並建立檔案
在根目錄建立`TDD-test`資料夾，裡面有個`bank_account_spec.rb`檔案
```bash
TDD-test/bank_account_spec.rb
```
- 得知測試結果
```bash
$ rspec bank_account_spec.rb
```
- 測試結果
分為三種：
-- . 表示成功
-- F 表示失敗
-- * 表示待辦狀態pending