more better nested set
TRANSCRIPT
イケテルbetter nested set
研究会藤岡岳之(xibbar)
アジェンダ
better nested setを使って組織図を書く手軽に書きたいんだ~!
Sectionモデルを作成1
better nested setをインストール
Sectionモデルを作成2migrationファイルモデルはSectionという名前にすることにして、使用するカラムはnameだけにbetter nested setにはparent_id、lft、rgtという:integerなカラムが必要
class CreateSections < ActiveRecord::Migration def self.up create_table :sections do |t| t.column :name, :string, :null=>false t.column :parent_id, :integer t.column :lft, :integer t.column :rgt, :integer end end def self.down drop_table :sections endend
Sectionモデルを作成3
app/models/section.rbの中身
class Section < ActiveRecord::Base acts_as_nested_set
end
データを作成
ツリーを作成してみる% ./script/runner 'Section.create(:name=>"日本Ruby会議")'% ./script/runner 'Section.create(:name=>"東京Ruby会議").move_to_child_of Section.find_by_name("日本Ruby会議")'% ./script/runner 'Section.create(:name=>"札幌Ruby会議").move_to_child_of Section.find_by_name("日本Ruby会議")' % ./script/runner 'Section.create(:name=>"Akasaka.rb").move_to_child_of Section.find_by_name("東京Ruby会議")'% ./script/runner 'Section.create(:name=>"Asakusa.rb").move_to_child_of Section.find_by_name("東京Ruby会議")'
DBの中身は# select * from sections; id | name | parent_id | lft | rgt ----+--------------+-----------+-----+----- 1 | 日本Ruby会議 | | 1 | 10 2 | 東京Ruby会議 | 1 | 2 | 7 3 | 札幌Ruby会議 | 1 | 8 | 9 4 | Akasaka.rb | 2 | 3 | 4 5 | Asakusa.rb | 2 | 5 | 6(5 rows)
自作プラグインを導入
./script/plugin install http://xibbar.net/svn/rails/plugins/trunk/acts_as_section_map/
モデルに一行追加
class Section < ActiveRecord::Base acts_as_nested_set acts_as_section_mapend
※ acts_as_section_mapは acts_as_nested_setの下に書くこと
コントローラー
class WelcomeController <ApplicationController def index @table2=Section.table2 endend
1行追加
ビュー
<% section_map(@table2) do |table| %> <%=table.name%> <% end %>
index.rhtml
※ テーブルの中身はブロックで指定
※ プラグインの魔法で、たったこれだけで組織図完成
関連テーブル
class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.column :name, :string, :null=>false t.column :section_id, :integer end end def self.down drop_table :users endend
user.rb
class User < ActiveRecord::Base belongs_to :sectionend
section.rbclass Section < ActiveRecord::Base acts_as_nested_set acts_as_section_map has_many :usersend
データ作成
% ./script/runner 'Section.find_by_name("Akasaka.rb").users << User.create(:name=>"takai")'% ./script/runner 'Section.find_by_name("Akasaka.rb").users << User.create(:name=>"koichiroo")'% ./script/runner 'Section.find_by_name("Akasaka.rb").users << User.create(:name=>"takedasoft")'% ./script/runner 'Section.find_by_name("Asakusa.rb").users << User.create(:name=>"a_matsuda")' % ./script/runner 'Section.find_by_name("Asakusa.rb").users << User.create(:name=>"kakutani")' % ./script/runner 'Section.find_by_name("Asakusa.rb").users << User.create(:name=>"maiha")'
userを作成し、sectionにぶら下げる
ビューも直す
<% section_map(@table2) do |table| %> <%=table.name%> <ul> <% table.users.each do |user|%> <li><%=user.name%></li> <%end%> </ul><% end %>
index.rhtml
見た目を直そう
<% section_map(@table2,:table=>{:cellspacing=>"0",:style=>"border-collapse:collapse;"}, :td=>{:valign=>"top",:style=>"border:1px solid #666666;color:navy;"}) do |table| %> <%=table.name%> <ul> <% table.users.each do |user|%> <li style="font-size:12px;color:green"><%=user.name%></li> <%end%> </ul><% end %>
index.rhtml
のろくない?
数が多いとのろいぞでも、なんとかしちゃるでも今日はおしまい続きは次回以降
イケテルDBチューニング
xibbarこと藤岡岳之
ブログのぶくま
これでぎりぎり卒論になるんじゃなかろうか。長谷川がこけたら、これにしようかな。。。
なんだこれ!!
巨大な組織
(1..4).each{|n|Section.create(:name=>"Section:#{n}")}(5..60).each{|n|2.times{|m|Section.find_or_create_by_name("Section:#{n*2+m+3}").move_to_child_of Section.find_by_name("Section:#{n}")}}
120組織のツリーを作るスクリプト
遅すぎる
User Load (0.000204) SELECT * FROM users WHERE (users.section_id = 124) Completed in 18.13171 (0 reqs/sec) | Rendering: 7.49291 (41%) | DB: 10.46457 (57%) | 200 OK [http://localhost/welcome]
表示に18秒っておい!
インデックスを張ってみる
User Load (0.000182) SELECT * FROM users WHERE (users.section_id = 124) Completed in 10.01978 (0 reqs/sec) | Rendering: 6.97071 (69%) | DB: 2.87357 (28%) | 200 OK [http://localhost/welcome]
10秒に短縮!まだ使い物にならないけど
すばやさの種を投入class AddDepthToSections < ActiveRecord::Migration def self.up add_column :sections,:depth,:integer Section.set_depth end
def self.down remove_column :sections,:depth endend
depthという項目を追加し、値をセットする。depthというのは文字通り深さの値(levelの結果)
すばやさの種の結果
User Load (0.000180) SELECT * FROM users WHERE (users.section_id = 124) Completed in 0.84391 (1 reqs/sec) | Rendering: 0.40329 (47%) | DB: 0.27316 (32%) | 200 OK [http://localhost/welcome]
1秒切ってメデタシメデタシ
ちょっとだけ解説
のろさの原因はsectionが自分自身の深さを知るためにselect count(*)していることにある。(levelメソッド)これをカラムとして持つとツリーの構築の早さが圧倒的に違う(つまりdepthカラム)
benchmark% ./script/performance/benchmarker 100 'Section.find(:all).map(&:level)' user system total real#1 5.790000 0.470000 6.260000 ( 10.331682)
% ./script/performance/benchmarker 100 'Section.find(:all).map(&:depth)' user system total real#1 0.610000 0.010000 0.620000 ( 0.784886)
% ./script/performance/benchmarker 100 'Section.find(:all).map(&:level)' user system total real#1 6.180000 0.530000 6.710000 ( 20.693455)
ちなみにindex張る前は
20.693÷0.784=26.39426倍早くなった!!
グー
おしまい
http://www001.upp.so-net.ne.jp/masa_gallery/edo.html