建立 Controller 與定義 Action
在先前介紹路徑時,以「文章列表」的路徑為例子:
1
| get '/articles', to: 'articles#index'
|
可以發現,在建立路徑的同時,to: 後方就是 controller 與 action!
Controller 的命名會根據 Route 是使用複數的 resources 還是單數 resource 方法而定。
這種命名幫助我們更好地組織與管理 controller 與 action,程式碼也更易於理解和維護。
同時也符合 Rails 的慣例優於設定(Convention over Configuration)的原則。
在這裡我們使用的是 resources,所以我們可以建立一個 ArticlesController!
rails g controller Articles
透過 rails generate controller Articles or rails g controller Articles 指令,
生成叫做 Articles 的 controller!
Remark:
controller 建立, rails generate controller 自定義名字 -> rails g controller 自定義名字
controller 移除, rails destroy controller 自定義名字 -> rails d controller 自定義名字
剛剛的指令可以幫我們生成一些所需要的檔案,而 Action 就會被定義在 controller
透過 controller.rb 定義 action
app/controllers/articles_controller.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index @articles = Article.order(id: :desc) end
def show end
def new @article = Article.new end
def create @article = Article.new(article_params)
if @article.save redirect_to "/articles", notice: "文章新增成功" else render :new end end
def edit end
def update
if @article.update(article_params) redirect_to articles_path, notice: "文章更新成功" else render :edit end end
def destroy @article.destroy redirect_to articles_path, notice: "文章刪除成功" end
private def article_params params.require(:article).permit(:title, :content, :sub_title) end
def set_article @article = Article.find(params[:id]) end end
|
註解拿掉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index @articles = Article.includes(:user).order(id: :desc) end
def show @comment = Comment.new @comments = @article.comments.order(id: :desc) end
def new @article = Article.new end
def create @article = current_user.articles.new(article_params)
if @article.save redirect_to "/articles", notice: "文章新增成功" else render :new end end
def edit end
def update if @article.update(article_params) redirect_to articles_path, notice: "文章更新成功" else render :edit end end
def destroy @article.destroy redirect_to articles_path, notice: "文章刪除成功" end
private def article_params params.require(:article).permit(:title, :content, :sub_title, :password) end
def set_article @article = Article.find(params[:id]) end
|
建立 View
當 Route, Controller 與 Action 都建立好後,就需要建立 View 呈現畫面!
需要手動新增檔案:action 名稱.html.erb,建立 view
(以 index 為例) index.html.erb
app/views/articles/index.html.erb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <h1>Articles 文章列表</h1>
<button><%= link_to "Add a new article!", new_article_path %></button> <ul> <% @articles.each do |article| %> <li class="article_list"> <%= link_to article.title, article %> </li> <span><%= link_to 'Updated', edit_article_path(article.id) %></span> <span><%= link_to 'Deleted', article_path(article.id), data: { turbo_method: 'delete', turbo_confirm: 'Are you sure?' }%></span> <% end %> </ul>
|
我建立了 POST 路徑,為了新增一篇文章用的,
也確定 Controller, Action & View 都有對應到,
但是顯示這個錯誤訊息
ActionController::InvalidAuthenticityToken in ArticlesController#create
發生什麼事了?!
這裡要額外說明一個知識,就是 CSRF!
CSRF
What is CSRF?
CSRF 是指跨站的請求偽造,這種攻擊方法會讓使用者去瀏覽一個曾經認證過的網站並執行惡意的操作,因為已經驗證過該使用者,所以網站就會認為操作來自該使用者,因而接受。
CSRF 之所以成立,是因為使用者的身份已經先被驗證過。
白話一點就像是別人拿你的會員卡去買東西,但剛好因為店家認卡不認人,所以當看到有人拿著你的卡,就相信是你本人,並接受他人使用你的會員卡進行消費。
在 Rails 中,POST 行為具有保護機制。
如果在設定好的 action 中重新整理頁面,並且在沒有建立 view 的情況下,
你會注意到與之前不同的錯誤訊息:
ActionController::InvalidAuthenticityToken in ArticlesController#create
這時候,你需要設定 CSRF token 保護機制,以便在提交表單後能夠通過!
Rails 的 form_with 方法,會自動為每個表單和非 GET 請求生成唯一的 CSRF Token,
並在提交請求時驗證該 Token,以防止 CSRF 攻擊。
Remark:
有種情況下不需要設定 CSRF token:
當你需要跟第三方金流做連線時,需要把這個保護機制關閉,這樣才能互相聯繫。
非同步交易時,當完成之後,會透過 Notify-URL 去通知對方。
實作 Go! Go!
app/views/articles/new.html.erb
特別以 new.html.erb 來說明 form_with 可以生成所需要的表單並包含 CSRF token,
保護你的應用免受 CSRF 攻擊。
這裡先看一次不藉由 form_with 時,自己加上 CSRF Token 的 input tag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <h1>Add a new article!</h1> <form action="/articles" method="post" data-turbo="false"> <div> <h2>Title</h2> <input type="text" name="title" > </div> <div> <h2>Content</h2> <textarea name="content" id="" cols="30" rows="10" placeholder="Enter your story..."></textarea> </div> <input name="authenticity_token" value="<%= form_authenticity_token %>" type="hidden"> <button>Submit!</button> </form>
|
接著是實際做一次使用 form_with :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <h1>Add a new article!</h1> <%= form_with(model: article, data: { turbo: false }) do |f| %> <div> <h2>Title</h2> <%= f.text_field :title %> </div>
<div> <h2>Subtitle</h2> <%= f.text_field :sub_title %> </div>
<div> <span>password</span> <%= f.password_field :password %> </div>
<div> <h2>Content</h2> <%= f.text_area :content %> </div>
<%= f.button :submit, class: 'submit-btn' %> <% end %>
|
當使用 form_with 時,可以去檢視網頁的原始碼,
就會發現我們不用自己加上 CSRF Token,HTML 也會生成以下這段:
1
| <input type="hidden" name="authenticity_token" value="BTeOTjllGc-FxHfBtsCidE_i1BAotM0RvZHVnr1LmA16OgTE04My-zwySMSVBav6tlOt62iEDUyDCgNANEVCkA" autocomplete="off" />
|
完整的 HTML 原始碼,CSRF Token 於 line 4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <h1 class="main-title">Add a new article!</h1>
<form data-turbo="false" action="/articles" accept-charset="UTF-8" method="post"> <input type="hidden" name="authenticity_token" value="BTeOTjllGc-FxHfBtsCidE_i1BAotM0RvZHVnr1LmA16OgTE04My-zwySMSVBav6tlOt62iEDUyDCgNANEVCkA" autocomplete="off" /> <div> <h2>Title</h2> <input type="text" name="article[title]" id="article_title" /> </div>
<div> <h2>Subtitle</h2> <input type="text" name="article[sub_title]" id="article_sub_title" /> </div>
<div> <span>password</span> <input type="password" name="article[password]" id="article_password" /> </div>
<div> <h2>Content</h2> <textarea name="article[content]" id="article_content"> </textarea> </div>
<button name="article[submit]" type="submit" id="article_submit" class="submit-btn">Create Article</button> </form>
|
Summary
自己在實作 CRUD 方式是從 Route -> Controller -> Action -> View 一個一個打造起來,
這篇主要敘述 Controller, Action & View,這三者之間的關聯與遇到 CSRF 錯誤訊息的狀況!
參考資料:
➫ 為你自己學 Ruby on Rails - Controller
➫ wikipedia - 跨站請求偽造
➫ ExplainThis