19. 軟刪除和版本控制
19-1 需求說明
在實際運作的網站中,用戶可能會不小心刪除資料,或是修改時不小心改錯等等。這時候用戶可能會透過客服希望管理員能進行復原的動作。另一方面,針對重要的資料,我們也希望建立追蹤和稽核的機制。
這種需求叫做軟刪除(Soft Deletion)和版本控管(Versions)。
軟刪除(Soft Deletion)的意思是不要真的刪除這一筆資料,常見的作法是增加一個刪除的標記欄位(例如 deleted_at
欄位),如果被標記刪除了,那就不要顯示出來即可,使用 Paranoia 這個 gem 可以完成這個功能。
至於版本控管的作法,則會另外建立一個 Version Model 來存儲編修紀錄。如果本來的資料被刪除或修改,則會複製資料到這個 Model 去。等會我們示範用 paper_trail gem。如果用了 paper_trail 來做版本控管,也就不需要用 paranoia 了。因為前者也可以做刪除的復原。
不建議用 paranoia 的方式,這種方式有一個嚴重的資料庫問題,會干擾 unique index。例如 User 的 email 是唯一的,如果不真的刪除這一筆資料,只是標記被刪除的話,那麽被刪除的用戶資料,該 email 依然無法註冊。因為資料庫認為 email 重復了。
19-2 安裝使用 paper_trail
編輯 Gemfile
+ gem 'paper_trail'
執行 bundle
,重啟伺服器
執行 bundle exec rails generate paper_trail:install --with-changes
執行 bundle exec rake db:migrate
編輯 app/models/registration.rb
class Registration < ApplicationRecord
+ has_paper_trail
這樣就完成了,所有的修改和刪除,都會紀錄在 paper_trail 的 Version model 裡面。
在 paper_trail 文檔上,有完整的版本瀏覽、比較和復原的作法。
19-3 編修紀錄的 UI 和復原
我們可以在後台新增一個 UI 來瀏覽最近的編修紀錄:
編輯 config/routes.rb
namespace :admin do
root "events#index"
+ resources :versions do
+ post :undo
+ end
執行 rails g controller admin::versions
編輯 app/controllers/admin/versions_controller.rb
- class Admin::VersionsController < ApplicationController
+ class Admin::VersionsController < AdminController
+ def index
+ @versions = PaperTrail::Version.order("id DESC").page(params[:page])
+ end
+ def undo
+ @version = PaperTrail::Version.find(params[:version_id])
+ @version.reify.save!
+
+ redirect_to admin_versions_path
+ end
end
新增 app/views/admin/versions/index.html.erb
<h2>編修紀錄</h2>
<table class="table">
<tr>
<td>ID</td>
<td>Model</td>
<td>Model ID</td>
<td>事件</td>
<td></td>
<td>操作者</td>
<td></td>
</tr>
<% @versions.each do |version| %>
<tr>
<td><%= version.id %></td>
<td><%= version.item_type %></td>
<td><%= version.item_id %></td>
<td><%= version.event %></td>
<td>
<ul>
<% version.changeset.each do |key, value| %>
<li>從 <%= value[0] %> 改成 <%= value[1] %></li>
<% end %>
</ul>
</td>
<td><%= version.whodunnit && User.find(version.whodunnit).display_name %></td>
<td>
<% if version.event != 'create' %>
<%= link_to "Undo", admin_version_undo_path(version), :data => { :confirm => "Are you sure?"}, :method => :post, :class => "btn btn-danger" %>
<% end %>
</td>
</tr>
<% end %>
</table>
<%= paginate @versions %>
瀏覽 http://localhost:3000/admin/versions
你可以試著刪除或編輯報名資料看看,可以看到編修紀錄,並進行復原(Undo)。