---
# System prepended metadata

title: '[08/09＆10_b] Rails實作 新專案Note'
tags: [Rails]

---

# [08/09＆10_b] Rails實作 新專案Note
https://www.xmind.net/m/jMUqLi/

依照順序 **Route => Conroller => Model => DB =>** M/C **=> View**

views呈現畫面就好，要東西用controllers抓
![](https://i.imgur.com/wAq53f9.png)

## START
1. 在目標目錄 rails new 專案名稱 => 開新專案。
2. 在目錄中新目錄 rails s => 開伺服器。
3. 在目錄中新目錄 rails c => 沙盒。
    *在 Rails c 裡面打 .all，只會噴 11 筆資料*
5. 可得到一個local 3000網址。

## (1) Route：創造路徑
在Rails，每條連結都是資源（resources）
在routes.rb用resources 創造8條路徑+7個action
```ruby=
resources :notes
```
例：路徑可改名articles
```ruby=
resources :notes, path: 'articles'
```
要背，會由上往下找，對了就執行那個路徑。

Resources資源，習慣用複數命名 (RESTful API概念)。路徑對照表可看：
* 運作rails s之後，任一不存在的網址
* 終端機指令 $rails route
    * Controller#Action**其中的new（經由post）會送到create
    * 凡有 :id 帶入後皆會傳入Controller，形成參數雜湊 **params[:id]**

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


Verb的功用：
**get 取得頁面；post 寫入資料庫；put 更新；delete 移除**

* 列表　GET /notes　
* 寫入　POST /notes
* 看3號　GET /notes/3 
* 新增　GET /notes/3/new
* 編輯3號　GET /notes/3/edit
* 更新3號　PUT /notes/3
* 刪除3號　DELETE /notes/3

### 路徑開關（很少用到）
只開部份路徑
```ruby=
resources:notes, only: [:index, :show]
```
除了此路徑全開
```ruby=
resources:notes, except: [:index]
```

### 追加功能&路徑 member/collection
例：取消訂單
除了8個路徑之外，想自製路徑取消訂單。

member會針對路徑中的id。
等於這個寫法＝delete :cancel, {on: :member} 
```ruby=
resources:notes, only: [:index, :show]
    member do
        delete :cancel
     #比方:針對ID2號訂單 DELETE /order/2/cancel
    end
```

collection會及於全部。
```ruby=
resources:notes, only: [:index, :show]
    collection do
        delete :cancel
     #刪除所有訂單 DELETE /order/cancel
    end
```

## (2) Con：建立方法
在app/views下建立相應檔案。詳筆記RB-13。建議終端機建立法：

```ruby=
   $rails g controller notes
```
    
Rails自動規範：**class名稱蛇式<==>檔案名稱駝峰式**。
Controller內定義方法，可以**放@實體變數 給View用**。

本例手動順序：
* 在routes設定get "/notes", to: "notes#index"之後
* 在app/controllers下新增「NotesController.rb」並繼承ApplicationController
* 承上，**def index**
* 手動新增app/views/notes目錄及index.html

## (3) Model：建立預期傳入的資料
產生Model的過程，要建立一個資料表。
因為Model是抽象概念，資料表/資料庫是實體存在的。所以只建立Model不夠，還要**建立migrate（描述資料表內容）**。

* 預期結構「欄位」「資料型態」
>    * **title**:string //到資料庫會變成 varchar(1~?)
>    * **content**:text //到資料庫會變成 text(…~4G) 可放較多資料
>    * **delete**:boolean (default: false) //判斷是否移除
>    * **deleted_at**:datetime //判斷移除時間
>    * **add_index** //建立索引，讓讀取速度更快（但寫入速度變慢）
>     因為隨著資料量遞增，撈資料時間呈線性成長。
>     其他資料型態還有：數字（integer）、文字（text）等等可用

### 開始建立
Model名稱用**首字大寫單數，對應migrate表格/Views目錄下的同名複數**。
（只有string屬性可以不寫）（欄位生成migration）
```ruby=
$rails g model Note title content:text
```

### 自動產生檔案
* 產生"app/model"下的新rb檔（不用編輯）
* 產生 **"db/migrate"下的新rb檔**
    * 可建立真正的資料表
    * 會得到**複數model名的migration！**
    * 會自動加時間戳記timestamps

### 建立新表格（資料庫具現化）
會在建立在"db/development.sqlite3"
```ruby=
$rails db:migrate
```

詳細設定在"config/databade.yml"
**清理內容 rails db:migrate:reset**

## (04) View/Con：建立各功能網頁
利用resources 創造8條路徑中的：
> new(.:format)

1. 在views下的相應網頁檔寫link（"/對應複數名/new"）
1. 到**views新增**同名網頁檔（new.html.erb）
2. 尚不存在create或new，所以到controller定義一個，再到views新增同名網頁檔（同上2步）
若寫這個，可拿到表單p的內容：
```htmlmixed=
render html: params["p"]
或
render html: params[:p]
```

## (05) View/Con：新增一筆資料
新增候選人資料，牽涉到View（設表單）、Controller（設create方法）、Model（被動存入新資料）

### View：建立表單送資料 ＝ 新增一筆資料庫
本動作要建立一筆資料庫
設置表單 其中之：
>
> 
> **超連結寫法**
> 從"views/notes/index.html.erb"
> 連到內建的「new」action建議使用new_note_path方法Helper：
> 
> ```ruby=
> <%= link_to "新增筆記", new_note_path %> 
> ```
> 
> 各種連結寫法：
> ![](https://i.imgur.com/BnRnqYM.png)

* **Note.new**　//新增一筆文章，只會在記憶體裡，要用 **.save** 才能存進去資料庫。
* **Note.create**　//新增一筆文章，不用寫 save 可以直接存進去資料庫。

### 觀念：params
就像雜湊
是一個**打包form送出的所有參數**的Hash（Key與Value）
*在 controller 塞 debugger 可以用 params 印出*
![](https://i.imgur.com/Vq5koRS.png)

* 用Key叫值（Value）
> h= {name: "kk"}
> 印出kk＝**h[:name]**


### View：解決invalidAuthenticityToken
rails網站的post行為必須帶token，否則無效（防止灌水）。

**方法1：加一行hidden（不建議）**
* action 是form送出的地方。
* method 是form送出方式、如get或push。
* value 帶 <%= form_authenticity_token %>。
* **各欄input對應Model的各欄名稱，資料庫抓name。**
* submit後form會把資料**包成param**，想單獨render其中的content的值的話：
> render html: **params["content"]**
> 或
> render html: **params[:content]**
```ruby=
<form action="/candidates" method="authenticity_token" value="<%= form_authenticity_token %>">

    <input type="text" name="title">
    <input type="textarea" name="content">
    <input type="submit" value="go!">
</form>
```
**方法2：form_for（內建小幫手form helper）**
form_for後面要接一個Model，而此時「Notes」已經是Model的class，所以：

* 在MVC架構下，建立行為應移到controller內較佳：將**Note.new先定義在Controller**的new方法裡，再給出「@note」實體變數，外面（view）才拿得到。 （\<%= form_for(@note) do %>）
    * def new方法中寫（本例title與content是要抓param的變數名稱）
> title = params[:title]
> content = params[:content]
> **@note=Note.new(title: title, content: content)**
* form_for會猜@note的路徑，若猜錯，就自己加一個url的雜湊（**@note, url: '/正確路徑'**）
    * 由於Object-Relational Mapping(ORM) 物件關係對應，應該會自己對應SQL與正確的物件。

**在View＝初期表單**
* 如此，action會指向/notes，方法是post，且自帶token。
* 各欄input對應Model的各欄名稱，資料庫抓name。
    * 本來的name多加一層外包 **note[  ]** ，送出的params會多包一層key（後面透過form_for達成）
    * ![](https://i.imgur.com/sEsEzEU.png)
    * 這樣的話，原本 title = params[:title] 與 content = params[:content] 可以縮寫成：
        * @note = Note.new(**params[:note]**)
        * 這樣會遇到ForbiddenAttributesError，必須做資料清洗。因此params[:note]要獨立成一個「資料清洗 def」，見後面。

```ruby=
<%= form_for(@note) do %>
  標題：<input type="text" name="note[title]">
  內文：<textarea type="text" name="note[content]"></textarea>
    <input type="submit" value="送出">
<% end %>
```
**在View＝進化表單**
* do後面可**帶變數（如 |f|）** 以承接yield出的數值，批次製造表單。如：
* 本例f是一種FormBuilder物件，可透過 **text_field、text_area 或 submit 方法**做出對應的 <input> 標籤* 
* form_for會將每一欄包起來（如candidate[name]、candidate[age]等），被params包成一個雜湊： **{candidate: {name: 'aaa', age: '20'}}**
```ruby=
<%= form_for(Note.new) do |f|%>
<div>
  <%= f.label :title, "標題" %>
  <%= f.text_field :title %>
</div>

<div>
  <%= f.label :content, "內文" %>
  <%= f.text_area :content %>
</div>

  <%= f.submit %>
<% end %>
```
#### form_for的做事
承上，form_for會自動加給一個key，等於舊寫法的：
> **<input type="text" name="note[content]" value="<%= @note.title%>">**


#### form_for的行動
form_for 偷偷用了.persisted 這個方法來判斷內容物有沒有料，會回傳布林值，
如果沒料就判斷你是想新增東西，所以會走到 new，
如果有料就判斷你是想修改東西，所以會走到 edit。

## (6) Con/Model：儲存一筆資料庫 if else
在def new完成之後，
繼續在Controller的create方法中，設定new帶入create之後的動作。
* 第8行的經由form_for打包起來的Note欄位雜湊(title: title, content: content)，**塞到new裡面給Model**。
    * 因此，最初可以寫成 render html: **params[:note]**
* 寫實體變數@note 方便View拿到。而前一步網頁中form_for的()變數，也可改為同一實體變數。
* *在controller的方法（如create）中可加一行debugger，以便在終端機除錯（印 params[:note]。按contiune結束。）*

```ruby=
def new
  @note = Note.new #寫進資料庫
end
  
def create
    # title = params[:title] #寫進該當參數
    # content = params[:content] #寫進該當參數
    @note = Note.new(title: title, content: content) #寫進資料庫 接著再改寫進clean_params並獨立為private方法
end

    if @note.save
        redirect_to "/notes" #若寫入成功 轉進notes清單
    else #若不成功
        flash[:notice] = "新增失敗"
        render :new #借views下的已經form_for的new重新渲染
    end
end
```
若不成功
* 在**Model**驗證必填某欄位（本例name） **validates :name, presence: true**
* 在controller的else給條件 **render :new**

可在if條件下一行，加入快閃訊息 flash[:notice] = "Candidate created!" ，提示成功。
寫入成功後，可用SQLite打開資料庫，在Browse Data的對應table（本例note）看寫入紀錄。
        
### Con：解決ForbiddenAttributesError資料清洗
原因：資料未清洗
解決法：
在create開頭用 **強參數strong parameter**（建立白名單，名單內才給過）
    抓（require）出前面hash（只要params裡面的:note）的key之後，只允許（permit）部分欄位過來
```ruby=
clean_params = params.require(:note).permit(:title, :content)
@note = Note.new(clean_params)
```
最後一行可能已在def new替換為實體變數的模樣 @note
參數習慣寫成clean_params

#### 包成一個新方法
為方便重複使用，獨立def成新的方法。
```ruby=
private
def clean_params
    params.require(:note).permit(:title, :content)
end
```
原本create的實體變數new所帶參數，跟著改變
```ruby=
note = Note.new(clean_params)
```

### 終端：檢證儲存成功
* rails c 或 rails console　//irb
* Note.all　//取得所有的 Note 資料像是陣列（**all為類別方法**）
    * Note.first　//抓出第一筆資料
    * rails routes　//路徑對照表 

#### IRB
- .any?:有沒有東西
- .errors.any?:有沒有錯誤
- .errors.full_message:錯誤訊息

## (07) Con/View：展示清單
在Controller透過類別方法all，建議實體變數「複數」命名
常用實體變數：all、where（篩選條件）、order（排序）、limit（限制筆數）
```ruby=
def index
    @notes = Note.all  
end
```
### 第一階段＝印出每一條
在View的index檔案中，
@notes本質上會印出陣列，所以可利用**each迴圈**印出每一條
```ruby=
<table>
  <tr>
    <td>Title</td>

  <% @notes.each do |note| %>
  <tr>
    <td>
      <%= link_to note.title, note_path(note) %>
    </td>
  </tr>
  <% end %>
</table>
```

### 第二階段＝每一條內頁
承上，增加個別筆數(note)資料連結，也就是show
* 依據內建Helper用note_path
    * **帶id**才知道要看哪一條，相當於/note/#{note.id}
    * (note.id)的**id可以省略**，會自己抽id
    * note_path也可以省略，變成 <%= link_to note.title, note %>
> <%= link_to 顯示字樣, 連結位址 %>
> 
```ruby=
<% @notes.each do |note| %>
    <tr>
      <td><%= link_to note.title, note_path(note.id) %></td>
      <td><%= note.content %></td>
    </tr>
  <% end %>
```

### 第三階段＝做出「show」
在Controller新增方法「show」，利用參數params抓取id，結合SQL的find_by方法。加上實體變數給View使用。
* .find_by //找不到資料 會給nil
* .find //找不到資料 會給ActiveRecord::RecordNotFound
```ruby=
  def show
    @note = Note.find_by(id: params[:id])
  end
```
在views目錄下新增show網頁檔：
```ruby=
<ul>
  <h1><%= @note.title %></h1>
  <li>Title: <%= @note.title %></li>
  <li>Content: <%= @note.content %></li>
</ul>
```
### 解決NoMethodError找不到資料
預防undefined method，在網頁開頭加入：
（專屬括號有等號= 印出，此處不加）
```ruby=
<% if @note %>
    主內容
<% else %>
    <h1>找不到資料</h1>
<% end %>
```

### 標出異常
#### 迴圈文字
可在new網頁列表出列出錯誤訊息。
1. 利用專屬if判斷有無錯誤，note.errors，
1. 在錯誤訊息本身，前一行放each迴圈印出full_message到變數（|message|）。
1. 將message丟到列表 \<li> 中印出來。
```ruby=
<% if @note.errors.any? %>
  <ul>
    <% @note.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>
```

#### css上色效果+排版
1. 出現錯誤時，原始碼中的div內會出現新的div **class="field_with_error"**。
2. 在app/assets/stylesheets/ 新增css檔。裡面的application.css會將所有css打包生效（以下兩行的功用，勿刪）
>  *= require_tree //載入所有scss，若關掉會按英文字母順序載
 *= require_self

3. 目錄中個別檔案檔名無所謂
4. 錯誤時，文字欄（type="text"）邊框與標籤名變紅。

```css=
.field_with_error{
    display: inline-block;
    
    input[type="text"]{
        border-color: red;
        border-width:1px;
    }

    label{
      color: red;
    }
}
```

## (08) Con/View：編輯/更新清單
建立新網頁edit，直接複製new內容。
1. 根據route路徑表，會去Controller找update方法。
    * patch:只換掉其中一個資料
    * put:整行換掉
1. 因此在View的網頁index，連結選擇路徑表Helper中的edit_xxx_path（會生成隱藏欄位name="_method" value="patch"）
```ruby=
<%= link_to "編輯", edit_note_path(note.id) %>
```

在Controller，定義edit繼承show的大標題寫法（抓資料名）
定義update也繼承create的結構，導入清洗資料，區分成（回列表index）敗（重新編輯edit）。
```ruby=
def edit
   @note = Note.find_by(id: params[:id])
end

def update
    @note = Note.find_by(id: params[:id])
	if @note.update(clean_params)
        flash[:notice] = "更新了"
        redirect_to "/notes"
    else
        render :edit
end

```

## (09) Con：刪除清單
在View的網頁，連結根據路徑表的Helper，方法要寫，不然就是get。
1. 路徑後面接method。
2. 再接data-confirm防呆。
```ruby=
<%= link_to "刪除", note_path(note),
		    method: 'delete',
		    "data-confirm": "確定嗎？" %>
```
或用雜湊
```ruby=
<%= link_to "刪除", note_path(note),
                    data: {method: 'delete', confirm: '確定嗎？' } %>
```    

根據route路徑表，會去Controller找destory方法。
```ruby=
def destroy
    @note = Note.find_by(id: params[:id]) #找到他
    @note.destroy #刪除他
    flash[:notice] = "刪除了"
    redirect_to "/notes" #離開他
end
```

## (10) Con/View：清理及簡化＊8/12影片
### Controller中
重複使用的，獨立成方法def.....

### View中
**Partial Render局部渲染**
* 不要在 partial reder裡面用實體變數
* form_for獨立出去成開頭「_」之網頁檔，取消實體變數標記。
* 原網頁呼叫實體變數就好
<%= render "form", note: @note %>
https://guides.rubyonrails.org/layouts_and_rendering.html#passing-local-variables

**Randered collection**
底限單數網頁，在同名views目錄下。
（複雜 再確認）

原網頁


## (11) Con：找不到網頁&其他
捕捉錯誤訊息
移到controller的上一層，**在Model**裡的application_record.rb
```ruby=
begin
    原內容
rescue ActiveRecord::RecordNotFound
    render file:"public/404.html", status: 404
end
```
**在Model**的application_controller.rb
* 讓所有controller 都可以自動用rescue_from這個類別方法
* 如果出現ActiveRecord::RecordNotFound就用record_not_found方法


```ruby=
class ApplicationController < ActionController::Base

  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private
  # 渲染rails提供的404頁面，狀態也同步改成404
  def record_not_found
    render file: "public/404.html", status: 404
  end
end
```
**在Controller**
在notes_controller.rb
* before_action = before_filter，做指定 action 前先撈資料
* before_action 是一個類別方法，是 singleton method
```ruby=
  before_action :find_note, only: [:show, :edit, :update, :destroy]  
```
**在View**
在index.html.erb
* method 預設為 get 要改成delete。
    * 也可以用 data 包起來
* <%# rel="nofollow" %>叫爬蟲不要跟過來 
* 按超連結 link_to 會形成一個表單 ，JS 會把 method 跟 token 塞進來
* id: note.id 可搭配JS使用，取得特定資料。
* destroy 刪除內容，要多加delete方法，不然會走錯路，走到show頁面。
```ruby=
 <%# data-method="delete"%>
    <%= link_to "刪除", note_path(note), method: "delete", 
    data: {confirm: "確定嗎？",id: note.id } %>
```
_form.html.erb
* 從@note 改成 note 讓_form.html不要自己亂抓東西，怕會抓到重複的
* 只要根據餵進來的東西在這邊呈現就好

```ruby=
<% if note.errors.any? %>
  <ul>
    <% note.errors.full_messages.each do |message| %>
    <li><%= message %></li>
    <% end %>
  </ul>
<% end %>
```
edit.html.erb
* view 提供的 render（局部渲染 partial render）不能把字串 form 改成符號，跟controller 的 render 不一樣
* 把 edit 的 @note 丟過來再丟到_form.html.erb，最後渲染回來

```ruby=
<%= render "form", note: @note %>
```
new.html.erb
* 把 new 的 @note 丟過來再丟到_form.html.erb，最後渲染回來
```ruby=
<%= render "form", note: @note %>
```

###### tags: `Rails`

