打造Rails App(四) — To do list

Nathan Lee
Change or Die!
Published in
21 min readOct 25, 2017

透過遵循Alpha camp的課程內容,一步一步的把第一個Rails App-相簿做出來後,針對整個Ruby on Rails的App開發有了初步的架構跟觀念了,當然這樣是不夠的,所以第一個打造Rails App的初階專案來囉,就是打造一個Rails app — To do list,為了加深印象跟重新再複習,所以特在Medium上將步驟拆解並記錄下來。

User Stories:

  • 使用者可以建立 to do 項目,各個 to do 項目要能儲存以下資訊:名稱 (name)、必須完成時間 (due date)、其他說明 (note)
  • 使用者可以看到一張 to do 清單,清單上呈現了 to do 的資訊
  • 使用者能在 to do清單上很方便地選擇要刪除、修改、或去看詳細的內容
  • 如果 due date 已經過期,就不能再刪除 to do 了
  • 最後,請使用 Bootstrap 美化這個 To do list

下圖是第一版完成的樣式,

To do list

接著就開始拆解完成上圖的各個步驟囉!

1. 建立Rails App專案:to_do_list

1–1. 打開terminal輸入建立Rails App專案to_do_list的指令,如下

$ rails new to_do_list
執行$ rails new to_do_list建立Rails app to_do_list專案

1–2. 輸入指令進到to_do_list目錄下,進行後續動作

$ cd to_do_list
to_do_list專案目錄

而關於目錄下各個資料夾所負責的任務可以參考:打造Rails App(二) — Route + MVC目錄結構

2. 建立Model

專案目錄產生後,接著要開始建立model然後遷移資料庫,這裡我們先來建立model吧!

2–1. 進到to_do_list目錄下後,在terminal輸入指令,建立一個叫做task的 model,

$ rails generate model task
輸入$ rails generate model task 建立task model

2–2. task model建立完成後,在db/migrate目錄下產生/新增 task model 的資料庫遷移(migrate)記錄20171018014822_create_tasks.rb檔案

新增 task model 的資料庫遷移(migrate)記錄20171018014822_create_tasks.rb檔案

2–3. 在app/models目錄下也產生/新增了task.rb這個Task model檔案

新增了task.rb這個task model檔案

至於資料庫遷移紀錄檔及task model檔案內有什麼內容這邊就不多加說明,可以參照:打造Rails App(三) — Ruby on Rails Active Record

3. 建立資料表(Table)在資料中

要完整建立Task model的話,Task model 的一舉一動還需連結著資料庫裡一張叫 tasks 的資料表 (table),並透過 Active Record 提供的許多方法,同時操作 Task model、也同時操作 tasks table。接著讓我們來建立tasks table吧,

3–1. 在db/migrate目錄下的資料庫遷移(migrate)記錄20171018014822_create_tasks.rb檔案中,編輯資料結構

在20171018014822_create_tasks.rb檔案中,編輯資料結構(一)
在20171018014822_create_tasks.rb檔案中,編輯資料結構(二)

3–2. 在terminal中輸入資料遷移指令產程資料表(Table),

$ rails db:migrate
輸入$ rails db:migrate產生資料表

然後tasks table就建立完成了!

註:Rails慣例-命名

1. Model命名為單數且開頭為大寫;所以在這專案model名稱為Task

2. Table預設為負數且以小寫及底線分隔命名;所以在這專案table名稱為tasks

4. Route設定

Rails中的Route會根據接受到的HTTP Request Method及URI比對Route中對照表的資料,是整個Rails App的第一關!

4–1. 打開在config/routes.rb,使用resources方法自動產生8個路徑及7個action。resources方法程式碼如下,

resources :task
routes.rb未編輯前
在routes.rb中輸入resources :task使用resources方法

4–2. 在terminal中查看Rails routes,執行下列指令進行查看設定,

$ rails routes
使用$ rails routes查看routes設定

會發現在routes.rb未使用resources方法前,查詢routes的結果會是未定義任何routes;在routes.rb中輸入resources :task使用resources方法後,在查詢一次routes的結果會是顯示完整的 CRUD 路徑(8個路徑及7個action),然後routes就設定好了!

5. 建立控制器(controller)

Rails中的Route會根據接受到的HTTP Request Method及URI比對Route中對照表的資料後,就決定由controller中哪個Action繼續執行後續動作。route在上一個步驟設定好了,有了路徑也知道往哪去,這個步驟就開始建立要去的地方也就是controller!

5–1. 建立tasks controller,在terminal中執行下列指令,

$ rails generate controller tasks
執行指令$ rails generate controller tasks 建立controller

除了新增 app/controllers/目錄下的tasks_controller.rb 及 app/views/tasks 目錄之外,也產生了相關的測試檔、JavaScript(CoffeeScript)、CSS(SCSS)檔案。

新增 app/controllers/目錄下的tasks_controller.rb
app/controllers/tasks_controller.rb的初始內容

想了解app/controllers/tasks_controller.rb的初始內容有什麼含義可以參照:打造Rails App(三) — Ruby on Rails Active Record

6. 在controller中建立第一個Action

Controller建立好了,但是裡面沒有宣告任何action要使用的方法(method),讓我們著手建立第一個action!

6–1. 在app/controllers/tasks_controller.rb中宣告index action要使用的方法(Method)

宣告index action要使用的方法(Method)

在route設定中,若要使用當時產生的7個action,則均須在tasks_controller.rb中宣告各個action欲使用的方法(method)。

7. 建立view替action controller的資料樣板化

Action Controller將Model回傳的資料處理完畢後交給View樣板化。

7–1. 在app/views/tasks目錄下,新建立index.html.erb檔案

在app/views/tasks目錄下新建index.html.erb檔案

7–2. 打開index.html.erb並著手編輯index頁面

在index.html.erb中編輯index頁面

7–3. 此時在terminal輸入指令連線至server並在瀏覽器開啟專案頁面(http://localhost:3000/)會看到下圖,

$ rails server
連線至http://localhost:3000/ 瀏覽專案頁面

會發現怎麼沒有顯示出先前在index.html.erb中所編輯的頁面!別急,因為我們沒有幫Rails App設定根目錄!

7–4. 在routes.rb中輸入下方程式碼設定根目錄,

root "tasks#index"
在routes.rb中輸入root “tasks#index” 設定根目錄

7–5 執行rails console,並建立一筆資料在資料表中,

進入rails console並用指令Task.create建立一筆資料

7–6. 專案有了根目錄,也有了一筆資料,讓我們再次連線到專案頁面(http://localhost:3000/)會看到下圖!

專案頁面(http://localhost:3000/)連至index action作為根目錄並顯示了剛剛在console中建立的第一筆資料

這個步驟滿足了user story:

使用者可以看到一張 to do 清單,清單上呈現了 to do 的資訊

8. 建立CRUD -Create的action method 跟 view頁面

CRUD 是 Create(新增)、Read(讀取)、Update(更新) 跟 Delete(刪除),開發 CRUD 應用程式是Rails的強項。上一步驟已經完成index action跟view頁面了,而下面是此次專案所有action跟view,

在這個步驟我們將建立CRUD- Create及view頁面。

8–1. 在app/controllers/tasks_controller.rb中宣告new action要使用的方法(Method)

在tasks_controller.rb中宣告new action要使用的方法(Method)

8–2. 在app/views/tasks目錄下,新建立new.html.erb檔案並編輯new頁面

新建立new.html.erb檔案並編輯new頁面

8–3. 連線到new頁面(http://localhost:3000/tasks/new)所呈現出來的view頁面,如下

new頁面(http://localhost:3000/tasks/new

8–4. 在app/controllers/tasks_controller.rb中宣告create action要使用的方法(Method)及在primary下宣告params這個method,以取得new頁面表單傳過來的欄位資料

tasks_controller.rb中宣告create action要使用的方法(Method)

8–5. 連線至新增頁面(http://localhost:3000/tasks/new),將欲新增的task內容填入task表單中,並按下“create”建立task資料

將欲新增的task內容填入task表單中

然後就會回到index頁面且該筆資料也會出現在index頁面中,如下,

index頁面

這個步驟滿足了user story需求:

使用者可以建立 to do 項目,各個 to do 項目要能儲存以下資訊:名稱 (name)、必須完成時間 (due date)、其他說明 (note)

9. 建立CRUD -Read的action method 跟 view頁面

9–1. 在app/controllers/tasks_controller.rb中宣告show action要使用的方法(Method)

tasks_controller.rb中宣告show action要使用的方法(Method)

9–2. 在app/views/tasks目錄下,新建立show.html.erb檔案並編輯show頁面

建立show.html.erb檔案並編輯show頁面

9–3. 連線至第一個task(http://localhost:3000/tasks/1),show頁面如下

第一個task(http://localhost:3000/tasks/1)show頁面

這個步驟滿足user story:

使用者能在 to do清單上很方便地選擇看詳細的內容

10. 建立CRUD -Update的action method 跟view頁面

10–1. 在app/controllers/tasks_controller.rb中宣告edit action & update action要使用的方法(Method)

tasks_controller.rb中宣告edit action & update action要使用的方法(Method)

10–2. 在app/views/tasks目錄下,新建立edit.html.erb檔案並編輯edit頁面

新建立edit.html.erb檔案並編輯edit頁面

10–3. 連線至第一筆task的edit頁面(http://localhost:3000/tasks/1/edit),將欲更新的task內容填入task表單中,並按下“update”更新task資料,如下圖,

將欲更新的task內容填入task表單中,並按下“update”更新task資料

更新完後,頁面會指向show頁面顯示更新後的task內容,如下圖,

更新完後頁面會指向show頁面顯示更新後的task內容

這個步驟滿足user story:

使用者能在 to do清單上很方便地選擇要修改的內容

11. 建立CRUD -Destroy的action method

11–1. 在app/controllers/tasks_controller.rb中宣告destroy action要使用的方法(Method)

tasks_controller.rb中宣告destroy action要使用的方法(Method)

11–2. 在index.html.erb中編輯index頁面時,宣告了使用delete methode 前執行confirm的動作

使用delete methode 前執行confirm的動作
按下Destroy後顯示的confirm視窗

至此,我們已經完成CRUD的建立了!

這個步驟滿足了user story:

使用者能在 to do清單上很方便地選擇要刪除

12. 執行Destroy時,判定時間是否overdue

12–1. 在tasks_controller.rb的destroy method中使用if…else進行時間是否overdue的判定。如果現在時間在task due date之前,則成功刪除且頁面指向index頁面;反之,如果現在時間在task due date之後,因為overdue則無法刪除且頁面指向index頁面。

在destroy method中使用if…else進行時間是否overdue的判定

12–2. 提示訊息(Flash Message):在application.html.erb中編輯顯示Rails的flash hash,讓在tasks_controller.rb的destroy method中宣告的notice訊息得以顯示在頁面中。

<p><%= flash[:notice] %></p>
在application.html.erb中編輯顯示Rails的flash hash

通常會用 Flash 來顯示錯誤、提示訊息等,通常會在應用程式的版型檔案 app/views/layout/application.html.erb,加入提示訊息所需的 View

12–3. 接著就來測試destroy 判別overdue的測試(測試時間:2017–10–19)

overdue判斷,刪除測試
執行destroy
頁面上方出現flash message:成功刪除!
試著刪除2017–10–18的資料
頁面上方出現flash message:OVERDUE!無法刪除

這個步驟滿足了user story:

如果 due date 已經過期,就不能再刪除 to do 了

13. 重構程式碼

在這個步驟之前,我們在建構Rails app的過程中重複寫了許多相同的程式碼,但是當重複的程式碼越多在維護上相對就更不容易,也會影響應用程式的品質。所以我們的目標是若之後應用程式需要任何的變動,我們只需要在同一個地方維護程式碼即可,且不會遺漏任何部分,以確保應用程式的品質。所以我們以Don’t Repeat Yourself(DRY) in Ruby on Rails的理念進行程式碼的重構。

13–1. 在primary method下宣告set_task這個method取代在app/controllers/tasks_controller.rb中show、edit、update 和 destroy method中均重複宣告的實例變數,如下

def set_task
@task = Task.find(params[:id]) #重複宣告的實例變數
end
在primary method下宣告set_task這個method取代重複宣告的實例變數

13–2. 宣告set_task method後,使用過濾器(filter) before action(也是回呼方法 (Callback Method) 的一種),要求Rails在執行任何或指定action前先執行set_task方法。在這邊過濾器在Rails執行show、edit、update 和 destroy method前會先執行set_task method。

使用過濾器(filter) before action,就可將各method中重複的@task = Task.find(params[:id])刪除

有過濾器(filter) before action後,就可將各method中重複的@task = Task.find(params[:id])刪除。

13–3. 接著我們著手進行view的程式碼重構,首先在app/views/tasks目錄下新增局部頁面(Partial Template)檔案 ”_form.html.erb”

新增局部頁面檔案:_form.html.erb

13–4. 將”new.html.erb” 及 “edit.html.erb” 檔案中重複的程式碼移至“_form.html.erb”局部頁面檔案中便於之後程式碼維護。

將”new.html.erb” 及 “edit.html.erb” 檔案中重複的程式碼移至“_form.html.erb”局部頁面檔案中

13–5. 在”new.html.erb” 及 “edit.html.erb” 檔案中輸入下列程式碼,告訴 view頁面來輸出名稱叫做 ”form” 的局部頁面。

<%= render :partial => "form" %>
在”new.html.erb” 中告訴 view頁面來輸出名稱叫做 ”form” 的局部頁面
在”edit.html.erb” 中告訴 view頁面來輸出名稱叫做 ”form” 的局部頁面

以上5個步驟就完成了tasks_controller.rb 及 view頁面的程式碼重構,達到DRY的目標。

14. 表單輸入資料驗證機制的建立

我們都會有失誤,如果今天在建立新task或更新task內容的時候不小心按到create或update的按鈕,可能會產生一筆不完整的資料,甚至是一筆完全沒有資料的task!為了避免失誤發生,我們新增ActiveRecord 的 Validation 驗證功能,透過 Rails 提供的方法,讓表單中的所有區塊都成為必填項目。

14–1. 在app/models/task.rb中使用下列指令使表單中所有區塊都成為必填項目。

validates_presence_of :name, :date, :note
新增ActiveRecord 的 Validation 驗證功能

14–2. 在局部頁面編輯顯示驗證錯誤訊息在頁面給user看

<% if @task.errors.present? %>
<ol>
<% @task.errors.each do |error, message| %>
<li>WARNING: <%= "#{error.capitalize} #{message}" %></li>
<% end %>
</ol>
<% end %>
驗證錯誤訊息在頁面給user看
顯示驗證錯誤訊息在頁面上方

這樣就完成了簡單的料驗證!

15. 使用 Bootstrap 美化這個 To do list

先呈現出用Bootstrap簡單美化後的頁面樣貌

index頁面:套用table, button, glyphicons, alerts進行優化
new頁面:套用form, button, glyphicons進行優化
show頁面:套用table, button, glyphicons進行優化
與new頁面相同優化元素,因為套用局部頁面

--

--