轉換的重點其實隻有一個,重點在于lft和rgt兩個字段的初始化,否則其他字段的初始
化對會寫程式的人應該難不倒才是,轉換的相異點來自于新增四個字段:root_id(根ID)
、lft(左值)、rgt(右值)、depth(深)
改成這樣好處多多,可以使用單一query取得"所有"子代(不限階層),且所有子代count
詳情請參閱Acts_As_Threaded
以下是轉換實做的部份,用于"Cate"這個model
[b][[Migrate]][/b]
class FixCate < ActiveRecord::Migration
def self.up
puts "fixing Cate"
Cate.reset_column_information
Cate.find(:all).each do |c|
if c.parent_id == nil
c.parent_id = 0
c.update
end
end
puts "fixed Cate"
change_column "cates" , "parent_id", :integer, :default => 0, :null => false
add_column "cates" , "root_id", :integer, :default => 0, :null => false
add_column "cates" , "lft", :integer
add_column "cates" , "rgt", :integer
add_column "cates" , "depth", :integer, :default => 0, :null => false
puts "fixing Cate"
Cate.reset_column_information
Cate.find(:all).each do |c|
if c.parent_id == 0
c.root_id = c.id
c.update
end
end
puts "fixed Cate"
end
def self.down
change_column "cates" , "parent_id", :integer
remove_column "cates" , "root_id"
remove_column "cates" , "lft"
remove_column "cates" , "rgt"
remove_column "cates" , "depth"
puts "fixing Cate"
Cate.reset_column_information
Cate.find(:all).each do |c|
if c.parent_id == 0
c.parent_id = nil
c.update
end
end
puts "fixed Cate"
end
end
[b][[cate.rb (Model)]] [/b]
class Cate < ActiveRecord::Base
acts_as_threaded
def parent
if self.parent_id == 0
return nil
else
return Cate.find(self.parent_id)
end
end
def children
#direct_children與all_children選一個使用
return self.direct_children
end
end
[[cates_controller.rb (Controller)]]
class CatesController < ApplicationController
def init
Cate.find(:all).each do |c|
c.before_create
c.after_create
end
end
end
簡單的來說,就是在migrate時做基本的轉換與初始化,将parent_id = null轉為parent_id = 0,然後将parent_id = 0的實體(root)的root_id改為自己(self.id),這樣就完成基本的兩個字段的初始化。
Model内新增兩個method來複寫掉繼承的mothod,Acts_As_Threaded預設沒有parent與children這兩個method,且如果之前用Acts_As_Tree實做的話,通常root實體的parent的值通常為null,而修正後會變為0,是以需要另外複寫。
因為migrate沒有支援完整的Rails與AR與plugin,是以最後一步的初始化要留到全部建立好後,在Controller内執行才行,上面的Controller内寫的init就是初始化的方式,執行一次即可。
如果詳細看Acts_As_Threaded内的文法就會得知lft與rgt的migeate為何沒加入":default => 0, :null => false"這段文法與為何要那樣的初始化,不過還是希望有神人可以想出在migrate内一次做完全部的方法 :)