本課時(shí)講解 Rails 如何通過(guò)表單(Form)傳遞數(shù)據(jù),以及表單中的輔助方法使用,并實(shí)現(xiàn)登陸注冊(cè)功能。
如果我們的表單不產(chǎn)生某個(gè)資源的狀態(tài)改變,我們可以使用 GET 發(fā)送表單,這么說(shuō)很抽象,比如一個(gè)搜索表單,就可以是 GET 表單。
我們?cè)陧?yè)面的導(dǎo)航欄上,增加一個(gè)搜索框:
<%= form_tag(products_path, method: "get") do %>
<%= label_tag(:q) %>
<%= text_field_tag(:q) %>
<%= submit_tag("搜索") %>
<% end %>
form_tag 產(chǎn)生了一個(gè)表單,我們?cè)O(shè)定它的 method 是 get,它的 action 地址是 products_path,我們也可以設(shè)定一個(gè) hash 來(lái)制定地址,比如:
form_tag({action: "search"}, method: "get") do
這需要你在 products 里再增加一個(gè) search 方法,否則,你會(huì)得到一個(gè) No route matches {:action=>"search", :controller=>"products"} 的提示,這告訴我們,form_tag 的第一個(gè)參數(shù)需要是一個(gè)可解析的 routes 地址。當(dāng)然,你也可以給它一個(gè)字符串,這個(gè)地址即便不存在,也不會(huì)造成 no route 提示了。
form_tag("/i/dont/know", method: "get") do
這并不是我們最終的代碼,我們還需要增加一些附加的屬性,讓我們的式樣看起來(lái)正常一些。而且我用了 params[:q] 這個(gè)方法,獲得地址中的 q 參數(shù),把搜索的內(nèi)容放回到搜索框中。
http://wiki.jikexueyuan.com/project/rails-practice/images/chapter_3/4.png" alt="" />
我們可以在 controller 里,使用 ActiveRecord 的 where 方法查詢傳入的參數(shù),我們也可以使用 (ransack)[https://github.com/activerecord-hackery/ransack] 這種 gem 來(lái)實(shí)現(xiàn)搜索功能。
ransack 是一個(gè) metasearch 的 gem,實(shí)現(xiàn)它非常的方便。我們把它加入到 gemfile 里:
gem 'ransack'
我們?cè)谝晥D里,使用 ransack 提供的輔助方法,來(lái)實(shí)現(xiàn)表單:
<%= search_form_for @q, html: { class: "navbar-form navbar-left" } do |f| %>
<div class="form-group">
<%= f.search_field :name_cont, class: "form-control", placeholder: "輸入商品名稱" %>
</div>
<% end %>
提示:如果每個(gè)頁(yè)面都包含這個(gè)搜索框,但是不見(jiàn)得每個(gè)頁(yè)面都有 @q 這個(gè)實(shí)例,所以我們可以自己寫(xiě)一個(gè)表單,實(shí)現(xiàn)搜索:
<%= form_tag products_path, method: :get, class: "navbar-form navbar-left" do %>
<div class="form-group">
<%= text_field_tag "q[name_cont]", params["q"] && params["q"]["name_cont"], class: "form-control input-sm search-form", placeholder: "輸入商品名稱" %>
</div>
<% end %>
在商品的 controller 中,我們修改 index 方法:
def index
@q = Product.ransack(params[:q])
@products = @q.result(distinct: true)
end
好了,一個(gè)簡(jiǎn)單的查詢實(shí)現(xiàn)了。這里我們使用的是 name_cont 來(lái)實(shí)現(xiàn)模糊查詢,文檔 上提供了詳盡的方法,實(shí)現(xiàn)更復(fù)雜的查詢。
在我們使用 form_tag 的同時(shí),我們還需要一些輔助方法來(lái)生成表單控件。
<%= 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) %>
解析后的代碼是:
<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" />
更多的表單輔助方法,建議大家直接查看這個(gè)部分的 源代碼,我一直認(rèn)為源代碼是最好的教材。
我們還可以使用不帶有 _tag 結(jié)尾的輔助方法,來(lái)顯示一個(gè)模型(Model) 實(shí)例(Instance),比如我們的 @product,可以在它的編輯頁(yè)面中這樣來(lái)寫(xiě):
<%= text_field(:product, :name) %>
他會(huì)給我們
<input type="text" value="測(cè)試商品" name="product[name]" id="product_name">
它接受兩個(gè)參數(shù),并把它拼裝成 product[name],并且把 value 賦予這個(gè)屬性的值。我們提交表單的時(shí)候,Rails 會(huì)把它解釋成 product: {name: '測(cè)試商品', ...},這樣,Product.create(...) 可以添加這個(gè)商品信息到數(shù)據(jù)庫(kù)中了。
不過(guò)這樣做會(huì)有個(gè)問(wèn)題,這個(gè)商品會(huì)有很多屬性需要我們填寫(xiě),會(huì)讓代碼變得“啰嗦”。這時(shí),我們可以把這個(gè)實(shí)例,綁定到表單上。
注:說(shuō)模型對(duì)象,通常指 Product 這個(gè)模型,說(shuō)模型實(shí)例,指 @product。一些文檔上并不區(qū)分這種稱呼,個(gè)人覺(jué)得容易混淆。
來(lái)看看我們的商品添加界面使用的表單吧,它在這里 app/views/products/_form.html.erb
<%= form_for @product, :html => { :class => 'form-horizontal' } do |f| %>
這里我們用了 form_for 這個(gè)方法,它可以將一個(gè)資源和表單綁定,這里我們將 controller 中的 @product 和它綁定。form_for 會(huì)判斷 @product 是否為一個(gè)新的實(shí)例(你可以看看 @product.new_record?),從而將 form 的地址指向 create 還是 update 方法,這是符合我們之前提到的 REST 風(fēng)格。
當(dāng)然,大多數(shù)瀏覽器是不支持PUT,PATCH,DELETE 方法的,瀏覽器在提交表單時(shí),只會(huì)是 GET 或 POST,這時(shí),form_tag 會(huì)創(chuàng)建一個(gè)隱藏空間,來(lái)告訴 Rails 這是一個(gè)什么動(dòng)作。而 form_for 會(huì)根據(jù)實(shí)例,來(lái)自動(dòng)判斷。
<input name="_method" type="hidden" value="patch" />
在我們顯示商品屬性的時(shí)候,用到了 f.text_field :name 這個(gè)輔助方法,這樣,我們不用再為每一個(gè) text_field 去聲明這是哪個(gè)實(shí)例了。f 是一個(gè)表單構(gòu)造器(Form Builder)實(shí)例,你可以在 這里 看到更多它的介紹。
我們可以自己定義 FormBuilder,以節(jié)省更多的代碼,也可以使用 simple form,formtastic 這種 Gem。推薦 ruby-toolbox.com 這個(gè)網(wǎng)站,你可以發(fā)現(xiàn)其他的好用的 Gem。
現(xiàn)在,我們實(shí)現(xiàn)一個(gè)很重要的功能,注冊(cè)和登錄。我們不需要從頭實(shí)現(xiàn)它,因?yàn)槲覀冇?Rails 十大必備 Gem 中的第一位:Devise 可以選擇。
在 Gemfile 中增加
gem 'devise'
在 bundle install 之后,我們需要?jiǎng)?chuàng)建配置文件:用戶(User)
% rails generate devise:install User
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Some setup you must do manually if you haven't yet:
1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
In production, :host should be set to the actual host of your application.
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root to: "home#index"
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:
config.assets.initialize_on_precompile = false
On config/application.rb forcing your application to not access the DB
or load models when precompiling your assets.
5. You can copy Devise views (for customization) to your app by running:
rails g devise:views
===============================================================================
之后,我們創(chuàng)建用戶(User)模型:
% rails generate devise User
invoke active_record
create db/migrate/20150224071758_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users
之后,我們創(chuàng)建用戶(User)需要的 views:
% rails g devise:views
invoke Devise::Generators::SharedViewsGenerator
create app/views/users/shared
create app/views/users/shared/_links.html.erb
invoke form_for
create app/views/users/confirmations
create app/views/users/confirmations/new.html.erb
create app/views/users/passwords
create app/views/users/passwords/edit.html.erb
create app/views/users/passwords/new.html.erb
create app/views/users/registrations
create app/views/users/registrations/edit.html.erb
create app/views/users/registrations/new.html.erb
create app/views/users/sessions
create app/views/users/sessions/new.html.erb
create app/views/users/unlocks
create app/views/users/unlocks/new.html.erb
invoke erb
create app/views/users/mailer
create app/views/users/mailer/confirmation_instructions.html.erb
create app/views/users/mailer/reset_password_instructions.html.erb
create app/views/users/mailer/unlock_instructions.html.erb
最后,更新 db:
rake db:migrate
在使用注冊(cè)登錄功能前,我們修改一下布局頁(yè)面,增加幾個(gè)鏈接:
<% if user_signed_in? %>
<li><%= link_to current_user.email, profile_path %></li>
<li><%= link_to "退出", destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to "登錄", new_user_session_path %></li>
<li><%= link_to "注冊(cè)", new_user_registration_path %></li>
<% end %>
現(xiàn)在,我們可以使用注冊(cè)登錄功能了,是不是很簡(jiǎn)單呢?
接下來(lái),我們對(duì) Devise 創(chuàng)建的頁(yè)面做一點(diǎn)修改,同時(shí)看看 Rails 如何實(shí)現(xiàn)表單的。
我們登錄界面在 app/views/users/sessions/new.html.erb,我們把它改一下,符合我們頁(yè)面風(fēng)格,具體如何使用 html 代碼,可以參考 http://bootswatch.com/simplex/。
本章的代碼在 這里,希望可以幫助大家理解表單和其使用。