<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 表單幫助方法 表單是網頁程序的基本組成部分,用于接收用戶的輸入。然而,由于表單中控件的名稱和各種屬性,使用標記語言難以編寫和維護。Rails 提供了很多視圖幫助方法簡化表單的創建過程。因為各幫助方法的用途不一樣,所以開發者在使用之前必須要知道相似幫助方法的差異。 讀完本文,你將學到: * 如何創建搜索表單等不需要操作模型的普通表單; * 如何使用針對模型的表單創建和編輯數據庫中的記錄; * 如何使用各種類型的數據生成選擇列表; * 如何使用 Rails 提供用于處理日期和時間的幫助方法; * 上傳文件的表單有什么特殊之處; * 創建操作外部資源的案例; * 如何編寫復雜的表單; ### Chapters 1. [編寫簡單的表單](#%E7%BC%96%E5%86%99%E7%AE%80%E5%8D%95%E7%9A%84%E8%A1%A8%E5%8D%95) * [普通的搜索表單](#%E6%99%AE%E9%80%9A%E7%9A%84%E6%90%9C%E7%B4%A2%E8%A1%A8%E5%8D%95) * [調用 `form_tag` 時使用多個 Hash 參數](#%E8%B0%83%E7%94%A8-form_tag-%E6%97%B6%E4%BD%BF%E7%94%A8%E5%A4%9A%E4%B8%AA-hash-%E5%8F%82%E6%95%B0) * [生成表單中控件的幫助方法](#%E7%94%9F%E6%88%90%E8%A1%A8%E5%8D%95%E4%B8%AD%E6%8E%A7%E4%BB%B6%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) * [其他幫助方法](#%E5%85%B6%E4%BB%96%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) 2. [處理模型對象](#%E5%A4%84%E7%90%86%E6%A8%A1%E5%9E%8B%E5%AF%B9%E8%B1%A1) * [模型對象幫助方法](#%E6%A8%A1%E5%9E%8B%E5%AF%B9%E8%B1%A1%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) * [把表單綁定到對象上](#%E6%8A%8A%E8%A1%A8%E5%8D%95%E7%BB%91%E5%AE%9A%E5%88%B0%E5%AF%B9%E8%B1%A1%E4%B8%8A) * [記錄辨別技術](#%E8%AE%B0%E5%BD%95%E8%BE%A8%E5%88%AB%E6%8A%80%E6%9C%AF) * [表單如何處理 PATCH,PUT 或 DELETE 請求?](#%E8%A1%A8%E5%8D%95%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86-patch%EF%BC%8Cput-%E6%88%96-delete-%E8%AF%B7%E6%B1%82%EF%BC%9F) 3. [快速創建選擇列表](#%E5%BF%AB%E9%80%9F%E5%88%9B%E5%BB%BA%E9%80%89%E6%8B%A9%E5%88%97%E8%A1%A8) * [`select` 和 `option` 標簽](#select-%E5%92%8C-option-%E6%A0%87%E7%AD%BE) * [處理模型的選擇列表](#%E5%A4%84%E7%90%86%E6%A8%A1%E5%9E%8B%E7%9A%84%E9%80%89%E6%8B%A9%E5%88%97%E8%A1%A8) * [根據任意對象組成的集合創建 `option` 標簽](#%E6%A0%B9%E6%8D%AE%E4%BB%BB%E6%84%8F%E5%AF%B9%E8%B1%A1%E7%BB%84%E6%88%90%E7%9A%84%E9%9B%86%E5%90%88%E5%88%9B%E5%BB%BA-option-%E6%A0%87%E7%AD%BE) * [時區和國家選擇列表](#%E6%97%B6%E5%8C%BA%E5%92%8C%E5%9B%BD%E5%AE%B6%E9%80%89%E6%8B%A9%E5%88%97%E8%A1%A8) 4. [使用日期和時間表單幫助方法](#%E4%BD%BF%E7%94%A8%E6%97%A5%E6%9C%9F%E5%92%8C%E6%97%B6%E9%97%B4%E8%A1%A8%E5%8D%95%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) * [獨立的幫助方法](#%E7%8B%AC%E7%AB%8B%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) * [處理模型對象的幫助方法](#%E5%A4%84%E7%90%86%E6%A8%A1%E5%9E%8B%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) * [通用選項](#%E9%80%9A%E7%94%A8%E9%80%89%E9%A1%B9) * [單個時間單位選擇列表](#%E5%8D%95%E4%B8%AA%E6%97%B6%E9%97%B4%E5%8D%95%E4%BD%8D%E9%80%89%E6%8B%A9%E5%88%97%E8%A1%A8) 5. [上傳文件](#%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6) * [上傳了什么](#%E4%B8%8A%E4%BC%A0%E4%BA%86%E4%BB%80%E4%B9%88) * [使用 Ajax 上傳文件](#%E4%BD%BF%E7%94%A8-ajax-%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6) 6. [定制表單構造器](#%E5%AE%9A%E5%88%B6%E8%A1%A8%E5%8D%95%E6%9E%84%E9%80%A0%E5%99%A8) 7. [理解參數命名約定](#%E7%90%86%E8%A7%A3%E5%8F%82%E6%95%B0%E5%91%BD%E5%90%8D%E7%BA%A6%E5%AE%9A) * [基本結構](#%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84) * [結合在一起使用](#%E7%BB%93%E5%90%88%E5%9C%A8%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8) * [使用表單幫助方法](#%E4%BD%BF%E7%94%A8%E8%A1%A8%E5%8D%95%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95) 8. [處理外部資源的表單](#%E5%A4%84%E7%90%86%E5%A4%96%E9%83%A8%E8%B5%84%E6%BA%90%E7%9A%84%E8%A1%A8%E5%8D%95) 9. [編寫復雜的表單](#%E7%BC%96%E5%86%99%E5%A4%8D%E6%9D%82%E7%9A%84%E8%A1%A8%E5%8D%95) * [設置模型](#%E8%AE%BE%E7%BD%AE%E6%A8%A1%E5%9E%8B) * [嵌套表單](#%E5%B5%8C%E5%A5%97%E8%A1%A8%E5%8D%95) * [控制器端](#%E6%8E%A7%E5%88%B6%E5%99%A8%E7%AB%AF) * [刪除對象](#%E5%88%A0%E9%99%A4%E5%AF%B9%E8%B1%A1) * [避免創建空記錄](#%E9%81%BF%E5%85%8D%E5%88%9B%E5%BB%BA%E7%A9%BA%E8%AE%B0%E5%BD%95) * [按需添加字段](#%E6%8C%89%E9%9C%80%E6%B7%BB%E5%8A%A0%E5%AD%97%E6%AE%B5) 本文的目的不是全面解說每個表單方法和其參數,完整的說明請閱讀 [Rails API 文檔](http://api.rubyonrails.org/)。 ### 1 編寫簡單的表單 最基本的表單幫助方法是 `form_tag`。 ``` <%= form_tag do %> Form contents <% end %> ``` 像上面這樣不傳入參數時,`form_tag` 會創建一個 `&lt;form&gt;` 標簽,提交表單后,向當前頁面發起 POST 請求。假設當前頁面是 `/home/index`,生成的 HTML 如下(為了提升可讀性,添加了一些換行): ``` <form accept-charset="UTF-8" action="/home/index" method="post"> <div style="margin:0;padding:0"> <input name="utf8" type="hidden" value="&#x2713;" /> <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> </div> Form contents </form> ``` 你會發現 HTML 中多了一個 `div` 元素,其中有兩個隱藏的 `input` 元素。這個 `div` 元素很重要,沒有就無法提交表單。第一個 `input` 元素的 `name` 屬性值為 `utf8`,其作用是強制瀏覽器使用指定的編碼處理表單,不管是 GET 還是 POST。第二個 `input` 元素的 `name` 屬性值為 `authenticity_token`,這是 Rails 的一項安全措施,稱為“跨站請求偽造保護”。`form_tag` 幫助方法會為每個非 GET 表單生成這個元素(表明啟用了這項安全保護措施)。詳情參閱“[Rails 安全指南](security.html#cross-site-request-forgery-csrf)”。 為了行文簡潔,后續代碼沒有包含這個 `div` 元素。 #### 1.1 普通的搜索表單 在網上見到最多的表單是搜索表單,搜索表單包含以下元素: * `form` 元素,`action` 屬性值為 `GET`; * 輸入框的 `label` 元素; * 文本輸入框 ; * 提交按鈕; 創建這樣一個表單要分別使用幫助方法 `form_tag`、`label_tag`、`text_field_tag` 和 `submit_tag`,如下所示: ``` <%= form_tag("/search", method: "get") do %> <%= label_tag(:q, "Search for:") %> <%= text_field_tag(:q) %> <%= submit_tag("Search") %> <% end %> ``` 生成的 HTML 如下: ``` <form accept-charset="UTF-8" action="/search" method="get"> <div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div> <label for="q">Search for:</label> <input id="q" name="q" type="text" /> <input name="commit" type="submit" value="Search" /> </form> ``` 表單中的每個 `input` 元素都有 ID 屬性,其值和 `name` 屬性的值一樣(上例中是 `q`)。ID 可用于 CSS 樣式或使用 JavaScript 處理表單控件。 除了 `text_field_tag` 和 `submit_tag` 之外,每個 HTML 表單控件都有對應的幫助方法。 搜索表單的請求類型一定要用 GET,這樣用戶才能把某個搜索結果頁面加入收藏夾,以便后續訪問。一般來說,Rails 建議使用合適的請求方法處理表單。 #### 1.2 調用 `form_tag` 時使用多個 Hash 參數 `form_tag` 方法可接受兩個參數:表單提交地址和一個 Hash 選項。Hash 選項指定提交表單使用的請求方法和 HTML 選項,例如 `form` 元素的 `class` 屬性。 和 `link_to` 方法一樣,提交地址不一定非得使用字符串,也可使用一個由 URL 參數組成的 Hash,這個 Hash 經 Rails 路由轉換成 URL 地址。這種情況下,`form_tag` 方法的兩個參數都是 Hash,同時指定兩個參數時很容易產生問題。假設寫成下面這樣: ``` form_tag(controller: "people", action: "search", method: "get", class: "nifty_form") # => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">' ``` 在這段代碼中,`method` 和 `class` 會作為生成 URL 的請求參數,雖然你想傳入兩個 Hash,但實際上只傳入了一個。所以,你要把第一個 Hash(或兩個 Hash)放在一對花括號中,告訴 Ruby 哪個是哪個,寫成這樣: ``` form_tag({controller: "people", action: "search"}, method: "get", class: "nifty_form") # => '<form accept-charset="UTF-8" action="/people/search" method="get" class="nifty_form">' ``` #### 1.3 生成表單中控件的幫助方法 Rails 提供了很多用來生成表單中控件的幫助方法,例如復選框,文本輸入框和單選框。這些基本的幫助方法都以 `_tag` 結尾,例如 `text_field_tag` 和 `check_box_tag`,生成單個 `input` 元素。這些幫助方法的第一個參數都是 `input` 元素的 `name` 屬性值。提交表單后,`name` 屬性的值會隨表單中的數據一起傳入控制器,在控制器中可通過 `params` 這個 Hash 獲取各輸入框中的值。例如,如果表單中包含 `&lt;%= text_field_tag(:query) %&gt;`,就可以在控制器中使用 `params[:query]` 獲取這個輸入框中的值。 Rails 使用特定的規則生成 `input` 的 `name` 屬性值,便于提交非標量值,例如數組和 Hash,這些值也可通過 `params` 獲取。 各幫助方法的詳細用法請查閱 [API 文檔](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html)。 ##### 1.3.1 復選框 復選框是一種表單控件,給用戶一些選項,可用于啟用或禁用某項功能。 ``` <%= check_box_tag(:pet_dog) %> <%= label_tag(:pet_dog, "I own a dog") %> <%= check_box_tag(:pet_cat) %> <%= label_tag(:pet_cat, "I own a cat") %> ``` 生成的 HTML 如下: ``` <input id="pet_dog" name="pet_dog" type="checkbox" value="1" /> <label for="pet_dog">I own a dog</label> <input id="pet_cat" name="pet_cat" type="checkbox" value="1" /> <label for="pet_cat">I own a cat</label> ``` `check_box_tag` 方法的第一個參數是 `name` 屬性的值。第二個參數是 `value` 屬性的值。選中復選框后,`value` 屬性的值會包含在提交的表單數據中,因此可以通過 `params` 獲取。 ##### 1.3.2 單選框 單選框有點類似復選框,但是各單選框之間是互斥的,只能選擇一組中的一個: ``` <%= radio_button_tag(:age, "child") %> <%= label_tag(:age_child, "I am younger than 21") %> <%= radio_button_tag(:age, "adult") %> <%= label_tag(:age_adult, "I'm over 21") %> ``` 生成的 HTML 如下: ``` <input id="age_child" name="age" type="radio" value="child" /> <label for="age_child">I am younger than 21</label> <input id="age_adult" name="age" type="radio" value="adult" /> <label for="age_adult">I'm over 21</label> ``` 和 `check_box_tag` 方法一樣,`radio_button_tag` 方法的第二個參數也是 `value` 屬性的值。因為兩個單選框的 `name` 屬性值一樣(都是 `age`),所以用戶只能選擇其中一個單選框,`params[:age]` 的值不是 `"child"` 就是 `"adult"`。 復選框和單選框一定要指定 `label` 標簽。`label` 標簽可以為指定的選項框附加文字說明,還能增加選項框的點選范圍,讓用戶更容易選中。 #### 1.4 其他幫助方法 其他值得說明的表單控件包括:多行文本輸入框,密碼輸入框,隱藏輸入框,搜索關鍵字輸入框,電話號碼輸入框,日期輸入框,時間輸入框,顏色輸入框,日期時間輸入框,本地日期時間輸入框,月份輸入框,星期輸入框,URL 地址輸入框,Email 地址輸入框,數字輸入框和范圍輸入框: ``` <%= text_area_tag(:message, "Hi, nice site", size: "24x6") %> <%= password_field_tag(:password) %> <%= hidden_field_tag(:parent_id, "5") %> <%= search_field(:user, :name) %> <%= telephone_field(:user, :phone) %> <%= date_field(:user, :born_on) %> <%= datetime_field(:user, :meeting_time) %> <%= datetime_local_field(:user, :graduation_day) %> <%= month_field(:user, :birthday_month) %> <%= week_field(:user, :birthday_week) %> <%= url_field(:user, :homepage) %> <%= email_field(:user, :address) %> <%= color_field(:user, :favorite_color) %> <%= time_field(:task, :started_at) %> <%= number_field(:product, :price, in: 1.0..20.0, step: 0.5) %> <%= range_field(:product, :discount, in: 1..100) %> ``` 生成的 HTML 如下: ``` <textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea> <input id="password" name="password" type="password" /> <input id="parent_id" name="parent_id" type="hidden" value="5" /> <input id="user_name" name="user[name]" type="search" /> <input id="user_phone" name="user[phone]" type="tel" /> <input id="user_born_on" name="user[born_on]" type="date" /> <input id="user_meeting_time" name="user[meeting_time]" type="datetime" /> <input id="user_graduation_day" name="user[graduation_day]" type="datetime-local" /> <input id="user_birthday_month" name="user[birthday_month]" type="month" /> <input id="user_birthday_week" name="user[birthday_week]" type="week" /> <input id="user_homepage" name="user[homepage]" type="url" /> <input id="user_address" name="user[address]" type="email" /> <input id="user_favorite_color" name="user[favorite_color]" type="color" value="#000000" /> <input id="task_started_at" name="task[started_at]" type="time" /> <input id="product_price" max="20.0" min="1.0" name="product[price]" step="0.5" type="number" /> <input id="product_discount" max="100" min="1" name="product[discount]" type="range" /> ``` 用戶看不到隱藏輸入框,但卻和其他文本類輸入框一樣,能保存數據。隱藏輸入框中的值可以通過 JavaScript 修改。 搜索關鍵字輸入框,電話號碼輸入框,日期輸入框,時間輸入框,顏色輸入框,日期時間輸入框,本地日期時間輸入框,月份輸入框,星期輸入框,URL 地址輸入框,Email 地址輸入框,數字輸入框和范圍輸入框是 HTML5 提供的控件。如果想在舊版本的瀏覽器中保持體驗一致,需要使用 HTML5 polyfill(使用 CSS 或 JavaScript 編寫)。polyfill 雖[無不足之處](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills),但現今比較流行的工具是 [Modernizr](http://www.modernizr.com/) 和 [yepnope](http://yepnopejs.com/),根據檢測到的 HTML5 特性添加相應的功能。 如果使用密碼輸入框,或許還不想把其中的值寫入日志。具體做法參見“[Rails 安全指南](security.html#logging)”。 ### 2 處理模型對象 #### 2.1 模型對象幫助方法 表單的一個特別常見的用途是編輯或創建模型對象。這時可以使用 `*_tag` 幫助方法,但是太麻煩了,每個元素都要設置正確的參數名稱和默認值。Rails 提供了很多幫助方法可以簡化這一過程,這些幫助方法沒有 `_tag` 后綴,例如 `text_field` 和 `text_area`。 這些幫助方法的第一個參數是實例變量的名字,第二個參數是在對象上調用的方法名(一般都是模型的屬性)。Rails 會把在對象上調用方法得到的值設為控件的 `value` 屬性值,并且設置相應的 `name` 屬性值。如果在控制器中定義了 `@person` 實例變量,其名字為“Henry”,在表單中有以下代碼: ``` <%= text_field(:person, :name) %> ``` 生成的結果如下: ``` <input id="person_name" name="person[name]" type="text" value="Henry"/> ``` 提交表單后,用戶輸入的值存儲在 `params[:person][:name]` 中。`params[:person]` 這個 Hash 可以傳遞給 `Person.new` 方法;如果 `@person` 是 `Person` 的實例,還可傳遞給 `@person.update`。一般來說,這些幫助方法的第二個參數是對象屬性的名字,但 Rails 并不對此做強制要求,只要對象能響應 `name` 和 `name=` 方法即可。 傳入的參數必須是實例變量的名字,例如 `:person` 或 `"person"`,而不是模型對象的實例本身。 Rails 還提供了用于顯示模型對象數據驗證錯誤的幫助方法,詳情參閱“[Active Record 數據驗證](active_record_validations.html#displaying-validation-errors-in-views)”一文。 #### 2.2 把表單綁定到對象上 雖然上述用法很方便,但卻不是最好的使用方式。如果 `Person` 有很多要編輯的屬性,我們就得不斷重復編寫要編輯對象的名字。我們想要的是能把表單綁定到對象上的方法,`form_for` 幫助方法就是為此而生。 假設有個用來處理文章的控制器 `app/controllers/articles_controller.rb`: ``` def new @article = Article.new end ``` 在 `new` 動作對應的視圖 `app/views/articles/new.html.erb` 中可以像下面這樣使用 `form_for` 方法: ``` <%= form_for @article, url: {action: "create"}, html: {class: "nifty_form"} do |f| %> <%= f.text_field :title %> <%= f.text_area :body, size: "60x12" %> <%= f.submit "Create" %> <% end %> ``` 有幾點要注意: * `@article` 是要編輯的對象; * `form_for` 方法的參數中只有一個 Hash。路由選項傳入嵌套 Hash `:url` 中,HTML 選項傳入嵌套 Hash `:html` 中。還可指定 `:namespace` 選項為 `form` 元素生成一個唯一的 ID 屬性值。`:namespace` 選項的值會作為自動生成的 ID 的前綴。 * `form_for` 方法會拽入一個**表單構造器**對象(`f` 變量); * 生成表單控件的幫助方法在表單構造器對象 `f` 上調用; 上述代碼生成的 HTML 如下: ``` <form accept-charset="UTF-8" action="/articles/create" method="post" class="nifty_form"> <input id="article_title" name="article[title]" type="text" /> <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea> <input name="commit" type="submit" value="Create" /> </form> ``` `form_for` 方法的第一個參數指明通過 `params` 的哪個鍵獲取表單中的數據。在上面的例子中,第一個參數名為 `article`,因此所有控件的 `name` 屬性都是 `article[attribute_name]` 這種形式。所以,在 `create` 動作中,`params[:article]` 這個 Hash 有兩個鍵:`:title` 和 `:body`。`name` 屬性的重要性參閱“[理解參數命名約定](#understanding-parameter-naming-conventions)”一節。 在表單構造器對象上調用幫助方法和在模型對象上調用的效果一樣,唯有一點區別,無法指定編輯哪個模型對象,因為這由表單構造器負責。 使用 `fields_for` 幫助方法也可創建類似的綁定,但不會生成 `&lt;form&gt;` 標簽。在同一表單中編輯多個模型對象時經常使用 `fields_for` 方法。例如,有個 `Person` 模型,和 `ContactDetail` 模型關聯,編寫如下的表單可以同時創建兩個模型的對象: ``` <%= form_for @person, url: {action: "create"} do |person_form| %> <%= person_form.text_field :name %> <%= fields_for @person.contact_detail do |contact_details_form| %> <%= contact_details_form.text_field :phone_number %> <% end %> <% end %> ``` 生成的 HTML 如下: ``` <form accept-charset="UTF-8" action="/people/create" class="new_person" id="new_person" method="post"> <input id="person_name" name="person[name]" type="text" /> <input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" /> </form> ``` `fields_for` 方法拽入的對象和 `form_for` 方法一樣,都是表單構造器(其實在代碼內部 `form_for` 會調用 `fields_for` 方法)。 #### 2.3 記錄辨別技術 用戶可以直接處理程序中的 `Article` 模型,根據開發 Rails 的最佳實踐,應該將其視為一個資源: ``` resources :articles ``` 聲明資源有很多附屬作用。資源的創建與使用請閱讀“[Rails 路由全解](routing.html#resource-routing-the-rails-default)”一文。 處理 REST 資源時,使用“記錄辨別”技術可以簡化 `form_for` 方法的調用。簡單來說,你可以只把模型實例傳給 `form_for`,讓 Rails 查找模型名等其他信息: ``` ## Creating a new article # long-style: form_for(@article, url: articles_path) # same thing, short-style (record identification gets used): form_for(@article) ## Editing an existing article # long-style: form_for(@article, url: article_path(@article), html: {method: "patch"}) # short-style: form_for(@article) ``` 注意,不管記錄是否存在,使用簡短形式的 `form_for` 調用都很方便。記錄辨別技術很智能,會調用 `record.new_record?` 方法檢查是否為新記錄;而且還能自動選擇正確的提交地址,根據對象所屬的類生成 `name` 屬性的值。 Rails 還會自動設置 `class` 和 `id` 屬性。在新建文章的表單中,`id` 和 `class` 屬性的值都是 `new_article`。如果編輯 ID 為 23 的文章,表單的 `class` 為 `edit_article`,`id` 為 `edit_article_23`。為了行文簡潔,后文會省略這些屬性。 如果在模型中使用單表繼承(single-table inheritance,簡稱 STI),且只有父類聲明為資源,子類就不能依賴記錄辨別技術,必須指定模型名,`:url` 和 `:method` 選項。 ##### 2.3.1 處理命名空間 如果在路由中使用了命名空間,`form_for` 方法也有相應的簡寫形式。如果程序中有個 `admin` 命名空間,表單可以寫成: ``` form_for [:admin, @article] ``` 這個表單會提交到命名空間 `admin` 中的 `ArticlesController`(更新文章時提交到 `admin_article_path(@article)`)。如果命名空間有很多層,句法類似: ``` form_for [:admin, :management, @article] ``` 關于 Rails 路由的詳細信息以及相關的約定,請閱讀“[Rails 路由全解](routing.html)”一文。 #### 2.4 表單如何處理 PATCH,PUT 或 DELETE 請求? Rails 框架建議使用 REST 架構設計程序,因此除了 GET 和 POST 請求之外,還要處理 PATCH 和 DELETE 請求。但是大多數瀏覽器不支持從表單中提交 GET 和 POST 之外的請求。 為了解決這個問題,Rails 使用 POST 請求進行模擬,并在表單中加入一個名為 `_method` 的隱藏字段,其值表示真正希望使用的請求方法: ``` form_tag(search_path, method: "patch") ``` 生成的 HTML 為: ``` <form accept-charset="UTF-8" action="/search" method="post"> <div style="margin:0;padding:0"> <input name="_method" type="hidden" value="patch" /> <input name="utf8" type="hidden" value="&#x2713;" /> <input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" /> </div> ... ``` 處理提交的數據時,Rails 以 `_method` 的值為準,發起相應類型的請求(在這個例子中是 PATCH 請求)。 ### 3 快速創建選擇列表 HTML 中的選擇列表往往需要編寫很多標記語言(每個選項都要創建一個 `option` 元素),因此最適合自動生成。 選擇列表的標記語言如下所示: ``` <select name="city_id" id="city_id"> <option value="1">Lisbon</option> <option value="2">Madrid</option> ... <option value="12">Berlin</option> </select> ``` 這個列表列出了一組城市名。在程序內部只需要處理各選項的 ID,因此把各選項的 `value` 屬性設為 ID。下面來看一下 Rails 為我們提供了哪些幫助方法。 #### 3.1 `select` 和 `option` 標簽 最常見的幫助方法是 `select_tag`,如其名所示,其作用是生成 `select` 標簽,其中可以包含一個由選項組成的字符串: ``` <%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %> ``` 這只是個開始,還無法動態生成 `option` 標簽。`option` 標簽可以使用幫助方法 `options_for_select` 生成: ``` <%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...]) %> ``` 生成的 HTML 為: ``` <option value="1">Lisbon</option> <option value="2">Madrid</option> ... ``` `options_for_select` 方法的第一個參數是一個嵌套數組,每個元素都有兩個子元素:選項的文本(城市名)和選項的 `value` 屬性值(城市 ID)。選項的 `value` 屬性值會提交到控制器中。ID 的值經常表示數據庫對象,但這個例子除外。 知道上述用法后,就可以結合 `select_tag` 和 `options_for_select` 兩個方法生成所需的完整 HTML 標記: ``` <%= select_tag(:city_id, options_for_select(...)) %> ``` `options_for_select` 方法還可預先選中一個選項,通過第二個參數指定: ``` <%= options_for_select([['Lisbon', 1], ['Madrid', 2], ...], 2) %> ``` 生成的 HTML 如下: ``` <option value="1">Lisbon</option> <option value="2" selected="selected">Madrid</option> ... ``` 當 Rails 發現生成的選項 `value` 屬性值和指定的值一樣時,就會在這個選項中加上 `selected` 屬性。 `options_for_select` 方法的第二個參數必須完全和需要選中的選項 `value` 屬性值相等。如果 `value` 的值是整數 2,就不能傳入字符串 `"2"`,必須傳入數字 `2`。注意,從 `params` 中獲取的值都是字符串。 使用 Hash 可以為選項指定任意屬性: ``` <%= options_for_select([['Lisbon', 1, {'data-size' => '2.8 million'}], ['Madrid', 2, {'data-size' => '3.2 million'}]], 2) %> ``` 生成的 HTML 如下: ``` <option value="1" data-size="2.8 million">Lisbon</option> <option value="2" selected="selected" data-size="3.2 million">Madrid</option> ... ``` #### 3.2 處理模型的選擇列表 大多數情況下,表單的控件用于處理指定的數據庫模型,正如你所期望的,Rails 為此提供了很多用于生成選擇列表的幫助方法。和其他表單幫助方法一樣,處理模型時要去掉 `select_tag` 中的 `_tag`: ``` # controller: @person = Person.new(city_id: 2) ``` ``` # view: <%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> ``` 注意,第三個參數,選項數組,和傳入 `options_for_select` 方法的參數一樣。這種幫助方法的一個好處是,無需關心如何預先選中正確的城市,只要用戶設置了所在城市,Rails 就會讀取 `@person.city_id` 的值,為你代勞。 和其他幫助方法一樣,如果要在綁定到 `@person` 對象上的表單構造器上使用 `select` 方法,相應的句法為: ``` # select on a form builder <%= f.select(:city_id, ...) %> ``` `select` 幫助方法還可接受一個代碼塊: ``` <%= f.select(:city_id) do %> <% [['Lisbon', 1], ['Madrid', 2]].each do |c| -%> <%= content_tag(:option, c.first, value: c.last) %> <% end %> <% end %> ``` 如果使用 `select` 方法(或類似的幫助方法,例如 `collection_select` 和 `select_tag`)處理 `belongs_to` 關聯,必須傳入外鍵名(在上例中是 `city_id`),而不是關聯名。如果傳入的是 `city` 而不是 `city_id`,把 `params` 傳給 `Person.new` 或 `update` 方法時,會拋出異常:`ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750)`。這個要求還可以這么理解,表單幫助方法只能編輯模型的屬性。此外還要知道,允許用戶直接編輯外鍵具有潛在地安全隱患。 #### 3.3 根據任意對象組成的集合創建 `option` 標簽 使用 `options_for_select` 方法生成 `option` 標簽必須使用數組指定各選項的文本和值。如果有個 `City` 模型,想根據模型實例組成的集合生成 `option` 標簽應該怎么做呢?一種方法是遍歷集合,創建一個嵌套數組: ``` <% cities_array = City.all.map { |city| [city.name, city.id] } %> <%= options_for_select(cities_array) %> ``` 這種方法完全可行,但 Rails 提供了一個更簡潔的幫助方法:`options_from_collection_for_select`。這個方法接受一個由任意對象組成的集合,以及另外兩個參數:獲取選項文本和值使用的方法。 ``` <%= options_from_collection_for_select(City.all, :id, :name) %> ``` 從這個幫助方法的名字中可以看出,它只生成 `option` 標簽。如果想生成可使用的選擇列表,和 `options_for_select` 方法一樣要結合 `select_tag` 方法一起使用。`select` 方法集成了 `select_tag` 和 `options_for_select` 兩個方法,類似地,處理集合時,可以使用 `collection_select` 方法,它集成了 `select_tag` 和 `options_from_collection_for_select` 兩個方法。 ``` <%= collection_select(:person, :city_id, City.all, :id, :name) %> ``` `options_from_collection_for_select` 對 `collection_select` 來說,就像 `options_for_select` 與 `select` 的關系一樣。 傳入 `options_for_select` 方法的子數組第一個元素是選項文本,第二個元素是選項的值,但傳入 `options_from_collection_for_select` 方法的第一個參數是獲取選項值的方法,第二個才是獲取選項文本的方法。 #### 3.4 時區和國家選擇列表 要想在 Rails 程序中實現時區相關的功能,就得詢問用戶其所在的時區。設定時區時可以使用 `collection_select` 方法根據預先定義的時區對象生成一個選擇列表,也可以直接使用 `time_zone_select` 幫助方法: ``` <%= time_zone_select(:person, :time_zone) %> ``` 如果想定制時區列表,可使用 `time_zone_options_for_select` 幫助方法。這兩個方法可接受的參數請查閱 API 文檔。 以前 Rails 還內置了 `country_select` 幫助方法,用于創建國家選擇列表,但現在已經被提取出來做成了 [country_select](https://github.com/stefanpenner/country_select) gem。使用這個 gem 時要注意,是否包含某個國家還存在爭議(正因為此,Rails 才不想內置)。 ### 4 使用日期和時間表單幫助方法 你可以選擇不使用生成 HTML5 日期和時間輸入框的幫助方法,而使用生成日期和時間選擇列表的幫助方法。生成日期和時間選擇列表的幫助方法和其他表單幫助方法有兩個重要的不同點: * 日期和時間不在單個 `input` 元素中輸入,而是每個時間單位都有各自的元素,因此在 `params` 中就沒有單個值能表示完整的日期和時間; * 其他幫助方法通過 `_tag` 后綴區分是獨立的幫助方法還是操作模型對象的幫助方法。對日期和時間幫助方法來說,`select_date`、`select_time` 和 `select_datetime` 是獨立的幫助方法,`date_select`、`time_select` 和 `datetime_select` 是相應的操作模型對象的幫助方法。 這兩類幫助方法都會為每個時間單位(年,月,日等)生成各自的選擇列表。 #### 4.1 獨立的幫助方法 `select_*` 這類幫助方法的第一個參數是 `Date`、`Time` 或 `DateTime` 類的實例,并選中指定的日期時間。如果不指定,就使用當前日期時間。例如: ``` <%= select_date Date.today, prefix: :start_date %> ``` 生成的 HTML 如下(為了行為簡便,省略了各選項): ``` <select id="start_date_year" name="start_date[year]"> ... </select> <select id="start_date_month" name="start_date[month]"> ... </select> <select id="start_date_day" name="start_date[day]"> ... </select> ``` 上面各控件會組成 `params[:start_date]`,其中包含名為 `:year`、`:month` 和 `:day` 的鍵。如果想獲取 `Time` 或 `Date` 對象,要讀取各時間單位的值,然后傳入適當的構造方法中,例如: ``` Date.civil(params[:start_date][:year].to_i, params[:start_date][:month].to_i, params[:start_date][:day].to_i) ``` `:prefix` 選項的作用是指定從 `params` 中獲取各時間組成部分的鍵名。在上例中,`:prefix` 選項的值是 `start_date`。如果不指定這個選項,就是用默認值 `date`。 #### 4.2 處理模型對象的幫助方法 `select_date` 方法在更新或創建 Active Record 對象的表單中有點力不從心,因為 Active Record 期望 `params` 中的每個元素都對應一個屬性。用于處理模型對象的日期和時間幫助方法會提交一個名字特殊的參數,Active Record 看到這個參數時就知道必須和其他參數結合起來傳遞給字段類型對應的構造方法。例如: ``` <%= date_select :person, :birth_date %> ``` 生成的 HTML 如下(為了行為簡介,省略了各選項): ``` <select id="person_birth_date_1i" name="person[birth_date(1i)]"> ... </select> <select id="person_birth_date_2i" name="person[birth_date(2i)]"> ... </select> <select id="person_birth_date_3i" name="person[birth_date(3i)]"> ... </select> ``` 創建的 `params` Hash 如下: ``` {'person' => {'birth_date(1i)' => '2008', 'birth_date(2i)' => '11', 'birth_date(3i)' => '22'}} ``` 傳遞給 `Person.new`(或 `update`)方法時,Active Record 知道這些參數應該結合在一起組成 `birth_date` 屬性,使用括號中的信息決定傳給 `Date.civil` 等方法的順序。 #### 4.3 通用選項 這兩種幫助方法都使用同一組核心函數生成各選擇列表,因此使用的選項基本一樣。默認情況下,Rails 生成的年份列表包含本年前后五年。如果這個范圍不能滿足需求,可以使用 `:start_year` 和 `:end_year` 選項指定。更詳細的可用選項列表請參閱 [API 文檔](http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html)。 基本原則是,使用 `date_select` 方法處理模型對象,其他情況都使用 `select_date` 方法,例如在搜索表單中根據日期過濾搜索結果。 很多時候內置的日期選擇列表不太智能,不能協助用戶處理日期和星期幾之間的對應關系。 #### 4.4 單個時間單位選擇列表 有時只需顯示日期中的一部分,例如年份或月份。為此,Rails 提供了一系列幫助方法,分別用于創建各時間單位的選擇列表:`select_year`,`select_month`,`select_day`,`select_hour`,`select_minute`,`select_second`。各幫助方法的作用一目了然。默認情況下,這些幫助方法創建的選擇列表 `name` 屬性都跟時間單位的名稱一樣,例如,`select_year` 方法創建的 `select` 元素 `name` 屬性值為 `year`,`select_month` 方法創建的 `select` 元素 `name` 屬性值為 `month`,不過也可使用 `:field_name` 選項指定其他值。`:prefix` 選項的作用與在 `select_date` 和 `select_time` 方法中一樣,且默認值也一樣。 這些幫助方法的第一個參數指定選中哪個值,可以是 `Date`、`Time` 或 `DateTime` 類的實例(會從實例中獲取對應的值),也可以是數字。例如: ``` <%= select_year(2009) %> <%= select_year(Time.now) %> ``` 如果今年是 2009 年,那么上述兩種用法生成的 HTML 是一樣的。用戶選擇的值可以通過 `params[:date][:year]` 獲取。 ### 5 上傳文件 程序中一個常見的任務是上傳某種文件,可以是用戶的照片,或者 CSV 文件包含要處理的數據。處理文件上傳功能時有一點要特別注意,表單的編碼必須設為 `"multipart/form-data"`。如果使用 `form_for` 生成上傳文件的表單,Rails 會自動加入這個編碼。如果使用 `form_tag` 就得自己設置,如下例所示。 下面這兩個表單都能用于上傳文件: ``` <%= form_tag({action: :upload}, multipart: true) do %> <%= file_field_tag 'picture' %> <% end %> <%= form_for @person do |f| %> <%= f.file_field :picture %> <% end %> ``` 像往常一樣,Rails 提供了兩種幫助方法:獨立的 `file_field_tag` 方法和處理模型的 `file_field` 方法。這兩個方法和其他幫助方法唯一的區別是不能為文件選擇框指定默認值,因為這樣做沒有意義。正如你所期望的,`file_field_tag` 方法上傳的文件在 `params[:picture]` 中,`file_field` 方法上傳的文件在 `params[:person][:picture]` 中。 #### 5.1 上傳了什么 存在 `params` Hash 中的對象其實是 `IO` 的子類,根據文件大小,可能是 `StringIO` 或者是存儲在臨時文件中的 `File` 實例。不管是哪個類,這個對象都有 `original_filename` 屬性,其值為文件在用戶電腦中的文件名;還有個 `content_type` 屬性,其值為上傳文件的 MIME 類型。下面這段代碼把上傳的文件保存在 `#{Rails.root}/public/uploads` 文件夾中,文件名和原始文件名一樣(假設使用前面的表單上傳)。 ``` def upload uploaded_io = params[:person][:picture] File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file| file.write(uploaded_io.read) end end ``` 文件上傳完畢后可以做很多操作,例如把文件存儲在某個地方(服務器的硬盤,Amazon S3 等);把文件和模型關聯起來;縮放圖片,生成縮略圖。這些復雜的操作已經超出了本文范疇。有很多代碼庫可以協助完成這些操作,其中兩個廣為人知的是 [CarrierWave](https://github.com/jnicklas/carrierwave) 和 [Paperclip](http://www.thoughtbot.com/projects/paperclip)。 如果用戶沒有選擇文件,相應的參數為空字符串。 #### 5.2 使用 Ajax 上傳文件 異步上傳文件和其他類型的表單不一樣,僅在 `form_for` 方法中加入 `remote: true` 選項是不夠的。在 Ajax 表單中,使用瀏覽器中的 JavaScript 進行序列化,但是 JavaScript 無法讀取硬盤中的文件,因此文件無法上傳。常見的解決方法是使用一個隱藏的 `iframe` 作為表單提交的目標。 ### 6 定制表單構造器 前面說過,`form_for` 和 `fields_for` 方法拽入的對象是 `FormBuilder` 或其子類的實例。表單構造器中封裝了用于顯示單個對象表單元素的信息。你可以使用常規的方式使用各幫助方法,也可以繼承 `FormBuilder` 類,添加其他的幫助方法。例如: ``` <%= form_for @person do |f| %> <%= text_field_with_label f, :first_name %> <% end %> ``` 可以寫成: ``` <%= form_for @person, builder: LabellingFormBuilder do |f| %> <%= f.text_field :first_name %> <% end %> ``` 在此之前需要定義 `LabellingFormBuilder` 類,如下所示: ``` class LabellingFormBuilder < ActionView::Helpers::FormBuilder def text_field(attribute, options={}) label(attribute) + super end end ``` 如果經常這么使用,可以定義 `labeled_form_for` 幫助方法,自動啟用 `builder: LabellingFormBuilder` 選項。 所用的表單構造器還會決定執行下面這個渲染操作時會發生什么: ``` <%= render partial: f %> ``` 如果 `f` 是 `FormBuilder` 類的實例,上述代碼會渲染局部視圖 `form`,并把傳入局部視圖的對象設為表單構造器。如果表單構造器是 `LabellingFormBuilder` 類的實例,則會渲染局部視圖 `labelling_form`。 ### 7 理解參數命名約定 從前幾節可以看出,表單提交的數據可以直接保存在 `params` Hash 中,或者嵌套在子 Hash 中。例如,在 `Person` 模型對應控制器的 `create` 動作中,`params[:person]` 一般是一個 Hash,保存創建 `Person` 實例的所有屬性。`params` Hash 中也可以保存數組,或由 Hash 組成的數組,等等。 HTML 表單基本上不能處理任何結構化數據,提交的只是由普通的字符串組成的鍵值對。在程序中使用的數組參數和 Hash 參數是通過 Rails 的參數命名約定生成的。 如果想快速試驗本節中的示例,可以在控制臺中直接調用 Rack 的參數解析器。例如: T&gt; `ruby TIP: Rack::Utils.parse_query "name=fred&phone=0123456789" TIP: # =&gt; {"name"=&gt;"fred", "phone"=&gt;"0123456789"} TIP:` #### 7.1 基本結構 數組和 Hash 是兩種基本結構。獲取 Hash 中值的方法和 `params` 一樣。如果表單中包含以下控件: ``` <input id="person_name" name="person[name]" type="text" value="Henry"/> ``` 得到的 `params` 值為: ``` {'person' => {'name' => 'Henry'}} ``` 在控制器中可以使用 `params[:person][:name]` 獲取提交的值。 Hash 可以隨意嵌套,不限制層級,例如: ``` <input id="person_address_city" name="person[address][city]" type="text" value="New York"/> ``` 得到的 `params` 值為: ``` {'person' => {'address' => {'city' => 'New York'}}} ``` 一般情況下 Rails 會忽略重復的參數名。如果參數名中包含空的方括號(`[]`),Rails 會將其組建成一個數組。如果想讓用戶輸入多個電話號碼,在表單中可以這么做: ``` <input name="person[phone_number][]" type="text"/> <input name="person[phone_number][]" type="text"/> <input name="person[phone_number][]" type="text"/> ``` 得到的 `params[:person][:phone_number]` 就是一個數組。 #### 7.2 結合在一起使用 上述命名約定可以結合起來使用,讓 `params` 的某個元素值為數組(如前例),或者由 Hash 組成的數組。例如,使用下面的表單控件可以填寫多個地址: ``` <input name="addresses[][line1]" type="text"/> <input name="addresses[][line2]" type="text"/> <input name="addresses[][city]" type="text"/> ``` 得到的 `params[:addresses]` 值是一個由 Hash 組成的數組,Hash 中的鍵包括 `line1`、`line2` 和 `city`。如果 Rails 發現輸入框的 `name` 屬性值已經存在于當前 Hash 中,就會新建一個 Hash。 不過有個限制,雖然 Hash 可以嵌套任意層級,但數組只能嵌套一層。如果需要嵌套多層數組,可以使用 Hash 實現。例如,如果想創建一個包含模型對象的數組,可以創建一個 Hash,以模型對象的 ID、數組索引或其他參數為鍵。 數組類型參數不能很好的在 `check_box` 幫助方法中使用。根據 HTML 規范,未選中的復選框不應該提交值。但是不管是否選中都提交值往往更便于處理。為此 `check_box` 方法額外創建了一個同名的隱藏 `input` 元素。如果沒有選中復選框,只會提交隱藏 `input` 元素的值,如果選中則同時提交兩個值,但復選框的值優先級更高。處理數組參數時重復提交相同的參數會讓 Rails 迷惑,因為對 Rails 來說,見到重復的 `input` 值,就會創建一個新數組元素。所以更推薦使用 `check_box_tag` 方法,或者用 Hash 代替數組。 #### 7.3 使用表單幫助方法 前面幾節并沒有使用 Rails 提供的表單幫助方法。你可以自己創建 `input` 元素的 `name` 屬性,然后直接將其傳遞給 `text_field_tag` 等幫助方法。但是 Rails 提供了更高級的支持。本節介紹 `form_for` 和 `fields_for` 方法的 `name` 參數以及 `:index` 選項。 你可能會想編寫一個表單,其中有很多字段,用于編輯某人的所有地址。例如: ``` <%= form_for @person do |person_form| %> <%= person_form.text_field :name %> <% @person.addresses.each do |address| %> <%= person_form.fields_for address, index: address.id do |address_form|%> <%= address_form.text_field :city %> <% end %> <% end %> <% end %> ``` 假設這個人有兩個地址,ID 分別為 23 和 45。那么上述代碼生成的 HTML 如下: ``` <form accept-charset="UTF-8" action="/people/1" class="edit_person" id="edit_person_1" method="post"> <input id="person_name" name="person[name]" type="text" /> <input id="person_address_23_city" name="person[address][23][city]" type="text" /> <input id="person_address_45_city" name="person[address][45][city]" type="text" /> </form> ``` 得到的 `params` Hash 如下: ``` {'person' => {'name' => 'Bob', 'address' => {'23' => {'city' => 'Paris'}, '45' => {'city' => 'London'}}}} ``` Rails 之所以知道這些輸入框中的值是 `person` Hash 的一部分,是因為我們在第一個表單構造器上調用了 `fields_for` 方法。指定 `:index` 選項的目的是告訴 Rails,其中的輸入框 `name` 屬性值不是 `person[address][city]`,而要在 `address` 和 `city` 索引之間插入 `:index` 選項對應的值(放入方括號中)。這么做很有用,因為便于分辨要修改的 `Address` 記錄是哪個。`:index` 選項的值可以是具有其他意義的數字、字符串,甚至是 `nil`(此時會新建一個數組參數)。 如果想創建更復雜的嵌套,可以指定 `name` 屬性的第一部分(前例中的 `person[address]`): ``` <%= fields_for 'person[address][primary]', address, index: address do |address_form| %> <%= address_form.text_field :city %> <% end %> ``` 生成的 HTML 如下: ``` <input id="person_address_primary_1_city" name="person[address][primary][1][city]" type="text" value="bologna" /> ``` 一般來說,最終得到的 `name` 屬性值是 `fields_for` 或 `form_for` 方法的第一個參數加 `:index` 選項的值再加屬性名。`:index` 選項也可直接傳給 `text_field` 等幫助方法,但在表單構造器中指定可以避免代碼重復。 為了簡化句法,還可以不使用 `:index` 選項,直接在第一個參數后面加上 `[]`。這么做和指定 `index: address` 選項的作用一樣,因此下面這段代碼 ``` <%= fields_for 'person[address][primary][]', address do |address_form| %> <%= address_form.text_field :city %> <% end %> ``` 生成的 HTML 和前面一樣。 ### 8 處理外部資源的表單 如果想把數據提交到外部資源,還是可以使用 Rails 提供的表單幫助方法。但有時需要為這些資源創建 `authenticity_token`。做法是把 `authenticity_token: 'your_external_token'` 作為選項傳遞給 `form_tag` 方法: ``` <%= form_tag 'http://farfar.away/form', authenticity_token: 'external_token') do %> Form contents <% end %> ``` 提交到外部資源的表單,其中可包含的字段有時受 API 的限制,例如支付網關。所有可能不用生成隱藏的 `authenticity_token` 字段,此時把 `:authenticity_token` 選項設為 `false` 即可: ``` <%= form_tag 'http://farfar.away/form', authenticity_token: false) do %> Form contents <% end %> ``` 以上技術也可用在 `form_for` 方法中: ``` <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %> Form contents <% end %> ``` 如果不想生成 `authenticity_token` 字段,可以這么做: ``` <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %> Form contents <% end %> ``` ### 9 編寫復雜的表單 很多程序已經復雜到在一個表單中編輯一個對象已經無法滿足需求了。例如,創建 `Person` 對象時還想讓用戶在同一個表單中創建多個地址(家庭地址,工作地址,等等)。以后編輯這個 `Person` 時,還想讓用戶根據需要添加、刪除或修改地址。 #### 9.1 設置模型 Active Record 為此種需求在模型中提供了支持,通過 `accepts_nested_attributes_for` 方法實現: ``` class Person < ActiveRecord::Base has_many :addresses accepts_nested_attributes_for :addresses end class Address < ActiveRecord::Base belongs_to :person end ``` 這段代碼會在 `Person` 對象上創建 `addresses_attributes=` 方法,用于創建、更新和刪除地址(可選操作)。 #### 9.2 嵌套表單 使用下面的表單可以創建 `Person` 對象及其地址: ``` <%= form_for @person do |f| %> Addresses: <ul> <%= f.fields_for :addresses do |addresses_form| %> <li> <%= addresses_form.label :kind %> <%= addresses_form.text_field :kind %> <%= addresses_form.label :street %> <%= addresses_form.text_field :street %> ... </li> <% end %> </ul> <% end %> ``` 如果關聯支持嵌套屬性,`fields_for` 方法會為關聯中的每個元素執行一遍代碼塊。如果沒有地址,就不執行代碼塊。一般的作法是在控制器中構建一個或多個空的子屬性,這樣至少會有一組字段顯示出來。下面的例子會在新建 `Person` 對象的表單中顯示兩組地址字段。 ``` def new @person = Person.new 2.times { @person.addresses.build} end ``` `fields_for` 方法拽入一個表單構造器,參數的名字就是 `accepts_nested_attributes_for` 方法期望的。例如,如果用戶填寫了兩個地址,提交的參數如下: ``` { 'person' => { 'name' => 'John Doe', 'addresses_attributes' => { '0' => { 'kind' => 'Home', 'street' => '221b Baker Street' }, '1' => { 'kind' => 'Office', 'street' => '31 Spooner Street' } } } } ``` `:addresses_attributes` Hash 的鍵是什么不重要,但至少不能相同。 如果關聯的對象已經存在于數據庫中,`fields_for` 方法會自動生成一個隱藏字段,`value` 屬性的值為記錄的 `id`。把 `include_id: false` 選項傳遞給 `fields_for` 方法可以禁止生成這個隱藏字段。如果自動生成的字段位置不對,導致 HTML 無法通過驗證,或者在 ORM 關系中子對象不存在 `id` 字段,就可以禁止自動生成這個隱藏字段。 #### 9.3 控制器端 像往常一樣,參數傳遞給模型之前,在控制器中要[過濾參數](action_controller_overview.html#strong-parameters): ``` def create @person = Person.new(person_params) # ... end private def person_params params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street]) end ``` #### 9.4 刪除對象 如果允許用戶刪除關聯的對象,可以把 `allow_destroy: true` 選項傳遞給 `accepts_nested_attributes_for` 方法: ``` class Person < ActiveRecord::Base has_many :addresses accepts_nested_attributes_for :addresses, allow_destroy: true end ``` 如果屬性組成的 Hash 中包含 `_destroy` 鍵,且其值為 `1` 或 `true`,就會刪除對象。下面這個表單允許用戶刪除地址: ``` <%= form_for @person do |f| %> Addresses: <ul> <%= f.fields_for :addresses do |addresses_form| %> <li> <%= addresses_form.check_box :_destroy%> <%= addresses_form.label :kind %> <%= addresses_form.text_field :kind %> ... </li> <% end %> </ul> <% end %> ``` 別忘了修改控制器中的參數白名單,允許使用 `_destroy`: ``` def person_params params.require(:person). permit(:name, addresses_attributes: [:id, :kind, :street, :_destroy]) end ``` #### 9.5 避免創建空記錄 如果用戶沒有填寫某些字段,最好將其忽略。此功能可以通過 `accepts_nested_attributes_for` 方法的 `:reject_if` 選項實現,其值為 Proc 對象。這個 Proc 對象會在通過表單提交的每一個屬性 Hash 上調用。如果返回值為 `false`,Active Record 就不會為這個 Hash 構建關聯對象。下面的示例代碼只有當 `kind` 屬性存在時才嘗試構建地址對象: ``` class Person < ActiveRecord::Base has_many :addresses accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| attributes['kind'].blank?} end ``` 為了方便,可以把 `reject_if` 選項的值設為 `:all_blank`,此時創建的 Proc 會拒絕為 `_destroy` 之外其他屬性都為空的 Hash 構建對象。 #### 9.6 按需添加字段 我們往往不想事先顯示多組字段,而是當用戶點擊“添加新地址”按鈕后再顯示。Rails 并沒有內建這種功能。生成新的字段時要確保關聯數組的鍵是唯一的,一般可在 JavaScript 中使用當前時間。 ### 反饋 歡迎幫忙改善指南質量。 如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。 翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。 文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。 最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看