天天看點

[翻譯] 165: 批量修改記錄(Edit Multiple)

165 : 批量修改記錄(Edit Multiple)

檢視原版Railscast

譯者:darkbaby123

校對:本文現在暫無校對者,如果哪位兄弟熱心幫忙,可以聯系蝸牛同學,或者給我發消息

第52集 示範了如何編輯多條資料庫記錄的例子。那個示例程式允許你一次選中多個任務,然後每個被選中的任務(Task )都會被設定成“完成”。

[翻譯] 165: 批量修改記錄(Edit Multiple)

這一集中我們将對這種想法進行擴充。但這次,我們可以讓使用者選擇更新一批記錄的多個屬性,而不是上一集的單個屬性。

網上商店應用程式

下面顯示的就是我們要改造的應用程式。目前,如果我們要修改表格的最後兩條記錄,就要單獨編輯它們。如果要修改的記錄很少,這不成問題。但如果面對成堆的記錄這就很乏味了。下面我們将修改這個程式,讓它可以同時修改一批記錄的多個屬性。

[翻譯] 165: 批量修改記錄(Edit Multiple)

修改index視圖

我們将在每條産品之前加上一個checkbox,這樣我們可以輕易地選擇要修改的産品。但在此之前,我們先把顯示所有産品的table标簽用form包起來。目前我們在Controller層還沒有一個用來編輯多個産品的action,是以先把url位址空着,以後再來填吧。

<% form_tag ... do %>      

我們沒有為form指定送出方式,是以它預設是POST。雖然這個form将帶我們到一個顯示所有選中産品的新頁面,我們似乎應該用GET,但因為我們要傳遞多個産品的id過去,這是GET做不到的。

在table的每一行中,我們要加入一個checkbox,它的值是對應的産品的 id (還要在table的标題部分加上一個空的<th> ,保證表格是對齊的)。

<td><%= check_box_tag "product_ids[]", product.id %></td>      

注意check_box_tag的name,它是以一個方括号結尾的,這是所有傳上去的值将會被集合成一個數組。

最後我們在table下面加上送出按鈕。

<%= submit_tag "Edit Checked" %>      

現在我們的index視圖應該是這個樣子:

<h1>Products</h1>
<% form_tag ... do %>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Category</th>
      <th>Price</th>
    </tr>
  </thead>
  <tbody>
    <% for product in @products %>
      <tr>
        <td><%= check_box_tag "product_ids[]", product.id %></td>
        <td><%= product.name %></td>
        <td><%= product.category.name %></td>
        <td><%= number_to_currency product.price, :unit => "&pound;" %></td>
        <td><%= link_to "Edit", edit_product_path(product) %></td>
        <td><%= link_to "Destroy", product_path(product), :confirm => "Are you sure?", :method => :delete %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<%= submit_tag "Edit Checked" %>
<% end %>
<%= link_to "New Product", new_product_path %>      

修改products控制器

我們的products控制器包含了有用的RESTful action。現在我們要增加兩個action來編輯和更新多條記錄。

def edit_multiple
end

def update_multiple
end
           

這兩個action的用處和标準的 edit 和 update 差不多,但它們用來處理多條記錄的編輯和更新。其中 edit_multiple 方法需要對應的視圖,我們過一會兒再寫它。

在我們的程式中,Products 是一個RESTful資源,是以我們必須修改routes檔案使這兩個新action可以通路。因為我們要對products集合添加方法,是以我們用 :collection 參數添加這兩個方法。

map.resources :products, :collection => { :edit_multiple => :post, :update_multiple => :put }
           

:collection 參數需要一個hash,其中hash的鍵代表action的名字,值代表這個action的html method。如同上文提到的,我們采用POST作為 edit_multiple 的method,雖然理想情況下我們應該用GET。

現在我們的action定義好了,我們可以回到index視圖,給 form_tag 填上正确的url位址

<% form_tag edit_multiple_products_path do %>      

現在我們重新整理index頁面,就可以看到每個産品前面的checkbox,下面的“Edit Checked”按鈕會帶我們到edit_multiple頁面。

[翻譯] 165: 批量修改記錄(Edit Multiple)

新的form

如果我們選中一些産品,然後點選下面的"Edit Checked"按鈕,會看到一個missing template錯誤。這是因為我們還沒有建立視圖代碼,是以下一步工作就是完成它。我們将把視圖代碼寫在 /app/views/products/edit_multiple.html.erb 檔案中。但在此之前,我們先看看點選按鈕後,development日志中生成的資訊。

Processing ProductsController#edit_multiple (for 127.0.0.1 at 2009-06-06 19:24:37) [POST]
  Parameters: {"product_ids"=>["3", "4", "5"], "commit"=>"Edit Checked", "authenticity_token"=>"s5z3KEJpBM7zC2JooC/relZ2oZtVpfxL/IMklpcBuYU="}      

在Parameters部分,我們可以看到我們選中的産品的id以數組的形式組織着。在控制器中,我們将用這些參數找到之前選中的産品。

def edit_multiple
  @products = Product.find(params[:product_ids])
end
           

現在轉到我們的edit_multiple視圖。在我們之前的程式中,我們已經有了一個放在partial中的form,它用在建立和編輯産品的頁面。看起來似乎我們可以直接把這個form拿過來用。但因為它做一些修改來适應批量記錄,是以我們幹脆建立一個新的form。

在視圖的開頭我們先定義一個沒有關閉block的 form_for (譯者注:原文寫的form_tag,但實際代碼是form_for,是以翻譯時改了一下)。因為這個form是用來更新多個産品的,我們為它的第一個參數指定一個symbol而不是實際的Product對象。我們還要為它指定 :url 和 :method (注意是PUT,是以要單獨指定)。

<% form_for :product, :url => update_multiple_products_path, :html => { :method => :put } do |form| %>      

在form中我們需要設定所有選中的産品的 id s,否則form送出時,我們不知道哪些産品會被更新。我們可以用一系列的hidden_field_tag來存放産品的 id s,同時我們做一個清單來顯示這些待更新的産品。

<ul>
  <% for product in @products %>
    <li>
      <%= h product.name %>
      <%= hidden_field_tag "product_ids[]", product.id%>
    </li>
  <% end %>
</ul>      

下一步,我們将為form添加表單元素來表示 Product 的屬性。這些屬性包括産品名稱、分類名稱、價格、和是否停售的資訊。當我們送出這個form時,我們隻希望更新那些填寫了具體值的屬性。是以對那些有一系列選擇的下拉框,我們要加一個空選項,這樣使用者就可以選擇“空”來跳過這些屬性的修改。

<ol class="formList">
  <li>
    <%= form.label :category_id %>
    <%= form.collection_select :category_id, Category.all, :id, :name, :include_blank => true %>
  </li>
  <li>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </li>
  <li>
    <%= form.label :price %>
    <%= form.text_field :price %>
  </li>
  <li>
    <%= form.label :discontinued %>
    <%= form.select :discontinued, [["Yes", true], ["No", false]], :include_blank => true %>
  </li>
  <li>
    <%= form.submit "Submit" %>
  </li>
</ol>      

最後,我們關閉 form_for 的block,我們的form也完成了。

<% end %>      

現在如果我們重新整理頁面,我們會看到我們選中的産品的清單,還有下面用來修改産品資訊的form。注意所有的下拉框都已經被設定成了空值,這樣我們就不會去修改它們的在資料庫中的值。

[翻譯] 165: 批量修改記錄(Edit Multiple)

編寫用于更新的action

現在我們已經幾近完成了,但我們還要為 update_multiple 方法編寫代碼,當上圖的form送出時,更新所有被選中的産品。

def update_multiple
  @products = Product.find(params[:product_ids])
  @products.each do |product|
    product.update_attributes!(params[:product].reject { |k,v| v.blank? })
  end
  flash[:notice] = "Updated products!"
  redirect_to products_path
end
           

在 update_multiple 的開始,我們用産品的 id s 數組(從form的隐藏字段中傳上來的)擷取了被選中的産品,然後我們周遊每個産品并進行更新。因為我們隻要更新不為空的屬性,是以我們用 reject 周遊每一個參數,去掉了值為空的屬性。注意我們用的是帶感歎号(!)的 update_attributes! 方法,因為我們沒有對模型做任何校驗。如果這個程式是一個實際産品,我們會去做校驗,但這超出了這一集讨論的範圍。使用 update_attributes! 表示如果有些資料不正确,程式将會抛出異常。一旦所有的産品都更新完成,我們設定一個flash資訊然後跳轉回到産品清單頁面。

讓我們看看它是否能正常工作。我們有兩個産品,Video Game Console和Video Game Disc,被放在Toys & Games分類下。現在我們要把分類改成Electronics。如果我們選中這兩個産品并點選“Edit Checked”按鈕(譯者注:原文是“Submit”按鈕,但實際頁面上是"Edit Checked")。我們将會看到它們被列在edit_multiple頁面上。

[翻譯] 165: 批量修改記錄(Edit Multiple)

如果我們選擇從category下拉框中選擇“Electronics”然後點選“Submit”按鈕,我們會回到産品清單頁面。

[翻譯] 165: 批量修改記錄(Edit Multiple)

可以看到,這兩個産品的分類已經改成了“Electronics”,但其他的屬性都沒有變化。

更進一步

目前我們已經提供了一種很有效的方式去同時修改多個模型對象。但在這一集的結尾,我們将更進一步,讓價格可以按相對的數值來改變。比如,這樣我們就可以讓所有家具的價格降低20%。我們現在可以選中多個産品,但如果我們改變form中的價格,那麼所有的産品都會變成一樣的價格。如果我們利用虛拟屬性,更改相對價格的邏輯就變得非常直覺。虛拟屬性在第16集中有介紹,如果想了解它們,你可以去 看看視訊 或 讀讀文章 。

我們将在 Product 模型中建立一個虛拟屬性price_modification 。我們會修改 edit_multiple 視圖中的form這樣它會修改我們的新屬性而不是直接修改 price 屬性。

<li>
  <%= form.label :price_modification %>
  <%= form.text_field :price_modification %>
</li>      

現在我們要在 Product 模型中設定新屬性的getter和setter。

class Product < ActiveRecord::Base
  belongs_to :category

  def price_modification
    price
  end

  def price_modification=(new_price)
    if new_price.ends_with? "%"
      self.price += (price * (new_price.to_f / 100)).round(2)
    else
      self.price = new_price
    end
  end
end
           

getter方法很直覺:我們隻需要傳回price就行,但setter方法稍微複雜一點。如果輸入的值(new_value)以百分号結束,我們将把值轉換成浮點數(new_price.to_f ),除以100然後乘以原始的價格,來求出價格改變的具體數值。然後把它和原始價格相加。如果那個具體數值是負數,價格就會降低。最後,我們用ActiveSupport擴充的 round 方法讓新的價格保留小數點後兩位。

現在,讓我們開始賤賣家具。我們選中兩個家具産品然後修改他們。

[翻譯] 165: 批量修改記錄(Edit Multiple)

然後在“Price modification”一欄中,填寫“-20%”。

[翻譯] 165: 批量修改記錄(Edit Multiple)

當我們送出form并傳回産品清單,我們可以看到選中的那些産品都降價了20%。

[翻譯] 165: 批量修改記錄(Edit Multiple)

這一集中講的技術非常有用,并且可以應用在一系列的場景中。經過這次實驗,你應該可以發現在你的Rails應用的很多地方,能相對地修改屬性都是非常有用的。