勉強にはよいけど、実戦では使ってほしくない事例
SQLで木と階層構造のデータを扱う――入れ子集合モデルっていうのに、はてなブックマークが160もついていた。
別にRDBを否定する気はない。MySQLとかPostgresとかが簡単に入手できて誰でも操作できる以上、SQL文は開発者にとっての「読み書き算盤」、みたいな基礎知識になっている、といってもいいだろう。
でも、自分の所のプロジェクトではSQL文でぐだぐだ書くのは禁止している。
- ロジックがコード本体とSQLとに分散し、追いにくい。
- 複雑なSQL文はテーブル構造の変更に弱い。
- 読みやすいコードは実現可能だが、読みやすいSQLは実現不可能なことが多い。SQL文の意味を説明する資料が別途必要。仕様変更の際の手間が増える。
Railsだと
ちなみに木構造をRailsで扱う場合は acts_as_tree を使う。
SQLで木と階層構造のデータを扱う――入れ子集合モデルと同じようなことを書いてみた。まずはテーブル定義。acts_as_treeを使う場合、自己参照外部キーとしてparent_idが必要となる。
class CreateEmployees < ActiveRecord::Migration def self.up create_table :employees do |t| t.column :name, :string t.column :parent_id, :integer end end def self.down drop_table :employees end end
Employeeモデル
class Employee < ActiveRecord::Base acts_as_tree :order => :name # rootはacts_as_treeの組込みメソッドで定義されている # 子供なしノード一覧を取り出す def self.leaves ret = [] self.find(:all).each {|e| ret << e if e.children.count == 0} ret end # あるノードの深さを計算する def level parent ? parent.level + 1 : 0 end # 木全体の高さを計算する def self.hight max = 0 Employee.find(:all).each {|e| max = e.level if max < e.level} max end end
単体テストで使うテストデータ。
adachi: id: 1 name: Adachi igari: id: 2 name: Igari parent_id: 1 ueda: id: 3 name: Ueda parent_id: 1 ezaki: id: 4 name: Ezaki parent_id: 3 ohgami: id: 5 name: Ohgami parent_id: 3 kato: id: 6 name: Kato parent_id: 3 kijima: id: 7 name: Kijima parent_id: 4
class EmployeeTest < Test::Unit::TestCase fixtures :employees # まずはノード定義がちゃんとできているか確認 def test_read employee = employees(:adachi) assert_equal 2, employee.children.count employee = employees(:ueda) assert_equal 3, employee.children.count employee = employees(:ezaki) assert_equal 1, employee.children.count assert_equal employees(:adachi), Employee.root end # 子なしノード一覧の確認 def test_leaves assert_equal [employees(:igari), employees(:ohgami), employees(:kato), employees(:kijima)], Employee.leaves end # ノードの深さ確認 def test_level assert_equal 0, employees(:adachi).level assert_equal 1, employees(:igari).level assert_equal 1, employees(:ueda).level assert_equal 2, employees(:ezaki).level assert_equal 2, employees(:ohgami).level assert_equal 2, employees(:kato).level assert_equal 3, employees(:kijima).level end # 高さ確認 def test_hight assert_equal 3, Employee.hight end end
データ操作ロジックはあくまでもmodelで記述する。SQL文は使わない。これにより可読性と保守性があがる。