一直很想實作 tag 相關的功能,這項功能雖然在 blog 體系中相當常見但我一直覺得如果能更廣泛的應用,例如在 file management 上使用 tag system 會是一件想當棒的事情。
在現在常用的資料夾結構下,一個資料夾只能在一個分類目錄下面,但當某篇文章同時提到 rails 跟 javascript 的時候,如果把它放到 rails 資料夾下,當你想要找所有 javascript 的檔案的時候便可能遺漏,反之亦然,所以如果多加運用這樣的方式的話,也許可以讓很多事情方便不少。
啊!好像有點離題,接下來進入正題:
Tag Association
這裡我已經先建置好article modal
跟tag modal
Tag 系統的好處在於一篇文同時可以有多個 tag 而一個 tag 也可以附著在多篇文章上面,所以在關聯上面應該是多對多的關係,這裡我選用的是has_and_belongs_to_many
這個關聯。
所以分別在article modal
加上:
class Article < ActiveRecord::Base has_and_belongs_to_many :tag end
tag modal
則加上:
class Tag < ActiveRecord::Base has_and_belongs_to_many :article end
Create article_tag Table
但只有這樣是不夠的,多對多關聯還必須透過一個關聯的資料表去查詢,在has_and_belongs_to_many
關係中,預設會去找一個由兩個 model 名稱由底線連結的資料表查詢關係,由本案例來說就是article_tag
。
所以新建一個 migration :
class CreateArticleTag < ActiveRecord::Migration def change #create article_tag table create_table :article_tag, id: false do |t| t.belongs_to :article, index: true t.belongs_to :tag, index: true end end end
這裡不必使用 id 所以將它設為 false
然後下rake db:migrate
,接下來問題是要如何儲存?
Create Checkboxes
首先在view/articles/new.html.erb
(edit 也要加)加上:
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
collection_check_boxes
非常完美地為我們實現了用 checkbox upadate 的功能。至於它是如何實現的,就讓我們實際檢視一下它所產生的 html :
<input type="checkbox" value="1" name="article[tag_ids][]" id="article_tag_ids_1"> <label for="article_tag_ids_1">thoughts</label> <input type="checkbox" value="2" checked="checked" name="article[tag_ids][]" id="article_tag_ids_2"> <label for="article_tag_ids_2">work</label> <input type="checkbox" value="3" name="article[tag_ids][]" id="article_tag_ids_3"> <label for="article_tag_ids_3">example</label> <input type="hidden" name="article[tag_ids][]" value="">
這裡可以看出總共有三個 tag 分別為:
- thoughts
- work
- example
這三個 tag 是由Tag.all
產生出來的,而 value 的是由其中的tag.id
產生, label 則是tag.name
。而第一個參數,也是最重要的,是使用的方法,也就是@article.tag_ids
。這個方法會回傳所有 tag 的id
並以陣列方式呈獻,也可以傳入一個陣列用以 update ,而這裡正是用來更新。
另外一個值得注意的是,name
欄位用的是article[tag_ids][]
,這個用意是為了讓收到的參數為params[:article][tag_ids]
並且以陣列方式呈獻,如此直接匯入原本@article.update_attributes(article_params)
即可,不必多做修改。
最後有疑問的點應該是:那最下面那段 hidden 欄位的用意是什麼?
其實很簡單,因為如果你希望刪除所有 tag ,通常你會把所有選項都解除勾選,但是 html 在面對完全沒被勾選的時候會什麼都不送出,也就是tag_ids
欄位根本不會存在,所以欄位會原風不動,完全沒被更新到。所以 rails 在這裡幫你用 hidden 欄位送出一個空的元素,使得如果完全沒有勾選,就會更新成空的。
既然是使用update_attrbutes
那一樣要注意 strong params 的問題,所以記得在將tag_ids
整個陣列加入許可的參數中:
private def article_params params.require(:article).permit({ tag_ids: [] }) end