読者です 読者をやめる 読者になる 読者になる

acts_as_nested_setを詳しく

Rails

なぜこれを?

railsにはツリー構造を表現するプラグインとして、
acts_as_treeってのとacts_as_nested_setというのがあります。
厳密にはnested_setというのはツリーとはいわないかも知れませんが、
ここでは似たようなものとして扱います。
acts_as_treeはDBの設計上、末端の検索まで時間がかかります。
これはacts_as_treeがツリーの表現としてparent_idしかないためです。
簡単な組織ならいいのですが、組織が入れ子になって200とかある
組織だと組織図を作るだけでonzの状態です。時間がかかります。
数が多い場合はnested setを使いましょう。

まず準備

migrationファイルに以下のように書きます。parent_id、lft、rgtは必須。

    create_table :sections do |t|
      t.column :parent_id, :integer
      t.column :lft, :integer
      t.column :rgt, :integer
      t.column :name, :string
    end

カラム名を変えたい場合はoptionsで指定します。:parent_column、:left_column、right_columnが使えます。:scopeは今回は紹介を省略します。

class Section < ActiveRecord::Base
  acts_as_nested_set :options=>{:order=>"id"}
end

使ってみる

データを入力するのが面倒なのでmigrateに全部書いてみます。

  def self.up
    create_table :sections do |t|
      t.column :parent_id, :integer
      t.column :lft, :integer
      t.column :rgt, :integer
      t.column :name, :string
    end
    Section.new(:name=>"福島本社").save! # 0
    Section.new(:name=>"営業部").save! # 1
    Section.new(:name=>"営業課").save!
    Section.new(:name=>"第一営業係").save!
    Section.new(:name=>"第二営業係").save!
    Section.new(:name=>"企画課").save!
    Section.new(:name=>"第一企画係").save!
    Section.new(:name=>"第二企画係").save!

    Section.new(:name=>"経理部").save! # 8
    Section.new(:name=>"会計課").save!
    Section.new(:name=>"第一会計係").save!
    Section.new(:name=>"第二会計係").save!
    Section.new(:name=>"契約課").save!
    Section.new(:name=>"第一契約係").save!
    Section.new(:name=>"第二契約係").save!
    Section.new(:name=>"第三契約係").save!

    Section.new(:name=>"総務部").save! # 16
    Section.new(:name=>"人事課").save!
    Section.new(:name=>"人事係").save!
    Section.new(:name=>"総務課").save!
    Section.new(:name=>"第一総務係").save!
    Section.new(:name=>"第二総務係").save!
    Section.new(:name=>"第三総務係").save!
    Section.new(:name=>"広報課").save!
    Section.new(:name=>"広報係").save!

    Section.new(:name=>"仙台支社").save! # 25
    Section.new(:name=>"東京支社").save!

    sections=Section.find(:all)
    sections[0].add_child(sections[1])
    sections[1].add_child(sections[2])
    sections[2].add_child(sections[3])
    sections[2].add_child(sections[4])
    sections[1].add_child(sections[5])
    sections[5].add_child(sections[6])
    sections[5].add_child(sections[7])
    sections[0].add_child(sections[8])
    sections[8].add_child(sections[9])
    sections[9].add_child(sections[10])
    sections[9].add_child(sections[11])
    sections[8].add_child(sections[12])
    sections[12].add_child(sections[13])
    sections[12].add_child(sections[14])
    sections[12].add_child(sections[15])
    sections[0].add_child(sections[16])
    sections[16].add_child(sections[17])
    sections[17].add_child(sections[18])
    sections[16].add_child(sections[19])
    sections[19].add_child(sections[20])
    sections[19].add_child(sections[21])
    sections[19].add_child(sections[22])
    sections[16].add_child(sections[23])
    sections[23].add_child(sections[24])
  end

この状態でツリー表現になっています。

インスタンスメソッド一覧

  • root?

自分がツリーのトップかどうか

  • child?

自分がどこかにぶら下がっているかどうか?

  • unknown?

自分が迷子かどうか?(親も子もない場合)

  • add_child(child)

自分に子をぶら下げる

  • children_count

自分にぶら下がっている子の総数を返す

  • full_set

自分とぶら下がっている子全てを配列で返す

  • all_children

自分にぶら下がっている子全てを配列で返す

  • direct_children

自分の直接ぶら下がっている子を配列で返す

  • before_destroy

どういう場合に使うのかよくわかりません。あまり使わない方がよさそうです。