Ruby on Rails 網站程式設計基礎班

第六課

表單與資料驗證

回憶一下上結課...

Rails MVC 架構的運作流程

ActiveRecord 的概念

關聯式資料庫的關聯 (一對一, 多對多)

ActiveRecord 的關聯 (has_many, belongs_to)

 

 

常用的 ActiveRecord 查詢法

# find 是透過資料的 id 搜尋

user = User.find(1) #尋找 users 資料表裡 id 為 1 的 record

# where 是透過給訂的條件搜尋

result = User.where(name: "bob") # 會回傳一個有一或多筆 user 的陣列

# ActiveRecord 會有內建的搜尋方法,find_by_

bob = User.find_by_name("bob")

# find_by_ 後面會接上相對應資料表裡的所有的欄位名稱,是由 ActiveRecord 動態產生的方法
# 問題是,若符合條件的資料不只一筆,find_by_ 只會回傳符合條件的第一筆資料,請小心使用

Validation (驗證)

確保輸入資料庫的資料的正確性 (空字串、格式 etc.)

需要讓資料被存入資料庫前檢查正確性

問題是應該在架構中的哪一層做驗證?

Validation (驗證)

Rails 是在 Model 的層面做資料的驗證

 

1. 好處是不需要考慮資料庫的種類 (把驗證寫在資料庫)

2. 無法在用戶端(瀏覽器)跳過驗證 (把驗證寫在前端)

3. 更容易測試與維護 (不需查看噁心的 SQL 或 javascript)

Validation (驗證)

# 我們要確保資料輸入的正確性
# 像是以 User 資料表為例,要是沒有 name 和 email,就不該被建立

class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true
end

Validation (驗證)

# 我們要確保輸入的名字有一定的長度

class User < ApplicationRecord
  validates :name, presence: true
  validates :name, length: { minimum: 2 }
  validates :name, length: { maximum: 200 }
  #名字最少要有兩個英文字母,最多不可超過兩百個英文字母
  
  validates :email, presence: true
end

Validation (驗證)

# 我們要確保輸入的名字有一定的長度

class User < ApplicationRecord
  validates :name, presence: true
  validates :name, length: { minimum: 2 }
  validates :name, length: { maximum: 200 }
  
  validates :email, presence: true
  validates :email, uniqueness: true
  #任何 email 應該都是獨一無二的,所以應該卻確保它的獨特性
end

Validation (驗證)

表單 (Form)

今天的網頁服務或應用

要讓使用者建立或是編輯資料

一定需要某一種類型的表單

 

表單 (Form)

class OrdersController < ApplicationController
  # GET /orders/new
  def new
    @order = Order.new
  end
end

我們來回顧一下上星期的 Scaffold 程式碼...

Order.new 是在創造出一個 Order 的空物件

存入 @order 變數後,就能能被前端頁面的 /orders/new 的表單使用

表單 (Form)

<h1>New Order</h1>

<%= render 'form' %>

<%= link_to 'Back', orders_path %>

我們再看一下前端 new 頁面的程式碼...

表單的程式碼其實是被封裝到一個叫  _form.html.erb 的樣板裡,然後被

<% render 'form' %>  呼叫

表單 (Form)

<%= form_for(@order) do |f| %>
  <% if @order.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@order.errors.count, "error") %> prohibited this order from being saved:</h2>
      <ul>
      <% @order.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :phone %><br>
    <%= f.text_field :phone %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

實際表單的程式碼...

這邊注意,form_for 是 rails 一個內建的方法,需要綁定 ActiveRecord 的物件使用,而目前 @order 就是我們在 new action 裡宣告的 @order

<%= f.text_field :phone %> 是綁定 @order 物件的 phone 屬性,自動幫你轉成文字輸入匡的 html 程式碼

表單 (Form)

class OrdersController < ApplicationController
  # POST /orders
  def create
    @order = Order.new(order_params)
    
    if @order.save
      redirect_to @order
    else
      render :new
    end
  end

  private

    def order_params
      params.require(:order).permit(:name, :phone, :description)
    end
end

接下來我們來看一下 create action 的程式碼...

從前端傳回來的資料都會被放入 params 參數(是一個 hash) 裡面,讓後端取用並實例出新的 @order 物件

為了防止有人從前端輸入惡意的資料或參數,Rails 會要求 params 裡的資料必須經過一個過濾的安全步驟,這個機制叫做 strong parameters

Strong Parameters

class OrdersController < ApplicationController
  private

    def order_params
      params.require(:order).permit(:name, :phone, :description)
    end
end

requirepermit 會把 params hash 裡的:

params[:order][:name]

params[:order][:phone]

params[:order][:description]

過濾出來,只能讓這些資料被寫入 orders 資料庫

因為 orders 資料表的 schema 會改變,每次改變都意味著 orders_params 需要調整,因此我們把它寫成 private method

表單 (Form)

class OrdersController < ApplicationController
  # POST /orders
  def create
    @order = Order.new(order_params)
    
    if @order.save
      redirect_to @order
    else
      render :new
    end
  end

  private

    def order_params
      params.require(:order).permit(:name, :phone, :description)
    end
end

接下來我們來看一下 create action 的程式碼...

 .save 代表存入資料庫,若物件是新的,便會在資料庫產生新的資料,若該物件代表的資料已存在,便會更新該筆資料

若儲存成功,便會導回 show 頁面,若失敗,便會再次顯示 new 頁面 (也就是表單)

表單 (Form)

<h1>Editing Order</h1>

<%= render 'form' %>

<%= link_to 'Show', @order %> |
<%= link_to 'Back', orders_path %>

我們再看一下前端 edit 頁面的程式碼...

與 new 頁面一樣,表單的程式碼其實是被封裝到一個叫  _form.html.erb 的局部樣板(partial template) 裡,然後被

<% render 'form' %>  呼叫

View Partial (局部樣板)

把重複的前端程式碼獨立成一個單獨的檔案,來讓其他樣板共享引用,有點像是呼叫 method,因為 new 和 edit 頁面的表單程式碼相同,可以把表單的程式碼獨立出來

表單 (Form)

class OrdersController < ApplicationController
  # GET /orders/1/edit
  def edit
    @order = Order.find(params[:id])
  end

  # PATCH/PUT /orders/1
  def update
    @order = Order.find(params[:id])

    if @order.update(order_params)
      redirect_to @order
    else
      render :edit
    end
  end

  private

    def order_params
      params.require(:order).permit(:name, :phone, :description)
    end
end

另外,我們也注意到 edit 和 update 有重複的程式碼...

before_action

class OrdersController < ApplicationController
  before_action :set_order, only: [:edit, :update]

  # GET /orders/1/edit
  def edit
  end

  # PATCH/PUT /orders/1
  def update
    if @order.update(order_params)
      redirect_to @order
    else
      render :edit
    end
  end

  private
    def set_order
      @order = Order.find(params[:id])
    end

    def order_params
      params.require(:order).permit(:name, :phone, :description)
    end
end

before_action 是指會在該 action 執行前會觸發的方法,所以我們可以把尋找單筆資料的程式碼打包成一個 method: set_order,再設定 before_action 會在執行 edit 和 update 前呼叫 set_order

功課:留言板

1. 打造出留言板需要的資料表
2. 實作出 Post 的 List、New、Edit 與 Show     頁面

RoR 第六課:表單與驗證

By Eugene Chang

RoR 第六課:表單與驗證

  • 1,213