[Rails3]Rails3での入れ子集合モデル その1

Rails3での入れ子集合モデル(nested set model)


理論は入れ子集合モデルで検索していただきたい。
一般的に、検索性能に優れ、更新性能は悪いモデルです。
更新頻度が高く、件数が多いものに適用すべきではありません。


Rails3における実装例


RubyGemsに上がっているものとして
awesome_nested_set
nested_set
があります。
nested_setも元をたどればawesome_nested_setと同じ実装です。
今回はawesome_nested_setを例にとります。


基本的な考え方は以下のようです


  • lft, rgt, parent_id, depthの4つの属性で入れ子集合モデルの管理を行なっている
  • 複数のroot(親が存在しない)に対応している

awesome_nested_setの基本的な使い方


Categoryというクラスを例にとります


class Category < ActiveRecord::Base
attr_accessible :depth, :lft, :name, :parent_id, :rgt
# awesome_nested_setの設定箇所
acts_as_nested_set
end

ここで、DBにはattr_accessibleに記述されているフィールドが存在し、nameはstring, その他はintegerで定義されているものとします。


親カテゴリの登録


親カテゴリの登録(位置は一番右側)


Category.create!(name: 'foo')

位置を変更する


以下のようにカテゴリが登録されているとします


category_a = Category.create!(name: 'foo')
# parent_id: nil, lft: 1, rgt: 2, depth: 0
category_b = Category.create!(name: 'bar')
# parent_id: nil, lft: 3, rgt: 4, depth: 0
category_c = Category.create!(name: 'baz')
# parent_id: nil, lft: 5, rgt: 6, depth: 0

category_cを一つ左にずらす


category_c.move_left

なお、各カテゴリの値は以下のようになります


category_a
# parent_id: nil, lft: 1, rgt: 2, depth: 0 (変わらず)
category_b
# parent_id: nil, lft: 5, rgt: 6, depth: 0
category_c
# parent_id: nil, lft: 3, rgt: 4, depth: 0

category_cを一つ右にずらす


category_c.move_right

category_cをcategory_aの左側に移動させる


category_c.move_to_left_of(category_a)

category_cをcategory_bの右側に移動させる


category_c.move_to_left_of(category_b)

子カテゴリの登録方法


基本的な方法


parent = Category.create!(name: 'parent')
child = Category.create!(name: 'child')
child.move_to_child_of(parent)

指定した位置の子カテゴリにする


parent = Category.create!(name: 'parent')
child_1 = Category.create!(name: 'child_1')
child_1.move_to_child_of(parent)
child_2 = Category.create!(name: 'child_2')
child_2.move_to_child_of(parent)

上記のようになっていた場合


child_3 = Category.create!(name: 'child_3')
# 一番左側にいれる
child_3.move_to_child_with_index(parent, 0)
# 左から2番目にいれる
child_3.move_to_child_with_index(parent, 1)

子カテゴリをルートカテゴリにする


parent = Category.create!(name: 'parent')
child = Category.create!(name: 'child')
child.move_to_child_of(parent)

上記のようになっていた場合


child.move_to_root

例外について


循環参照となる場合は以下のエラーが発生する


ActiveRecord::ActiveRecordError:
Impossible move, target node cannot be inside moved tree.

ポイント


awesome_nested_setは、先の例で示したように複数のrootをもてます。
複数のrootは以下のように表現されています。


category_a = Category.create!(name: 'foo')
# parent_id: nil, lft: 1, rgt: 2, depth: 0
category_b = Category.create!(name: 'bar')
# parent_id: nil, lft: 3, rgt: 4, depth: 0
category_c = Category.create!(name: 'baz')
# parent_id: nil, lft: 5, rgt: 6, depth: 0

これが何を意味するかというと、rootが位置情報を持っており、以下の検索の仕方で、
親子関係及び位置関係を崩さずにすべてのレコードが取得できます


Category.order('lft').all

これでlft: 1, 3, 5の順にレコードが取り出せます
なお、子が存在する場合ももちろん問題なく、以下のようにデータが登録されます


category_a = Category.create!(name: 'foo')
category_b = Category.create!(name: 'bar')
category_c = Category.create!(name: 'baz')
child_a = Category.create(name: 'qux')
child_a.move_to_child_of(category_a)

category_a
# id: 1, lft: 1, rgt: 4, depth: 0
category_b
# id: 2, lft: 5, rgt: 6, depth: 0
category_c
# id: 3, lft: 7, rgt: 8, depth: 0
child_a
# id: 4, lft: 2, rgt: 3, depth: 1, parent_id: 1

この状態で上記の検索をすると
category_a, child_a, category_b, category_cの順で取り出すことができます


注意点


レコードを追加してから、位置を変更するというスタイルをとっている
入れ子集合モデルの更新系のだめなところが解消されていない
(要素の追加や位置変更などのたびに全レコードに対して処理が走る) ... 有理数モデルを採用しているのはなさそう??


helper系とチェック系、探索系は次回



スポンサーサイト
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。