ver.2橋本亮介の個人ブログ。acts_as_nested_listへ乗り換えましたコメントを残すacts_as_treeからacts_as_nested_listへの乗り換えについては下記のリンクに書かれています。Awesome_Nested_Setあーなんだ、簡単そうだ。思ったらこれが非常に大変でした。アプリケーションを作る前だったら乗り換えるのは確かにこの手順を踏めばあっという間です。アプリケーションをある程度実装している段階では乗換えの前に作戦を立てたほうがよさそうです。大前提としてacts_as_treeのほうは各カラムにparent_idを設定することでツリー構造を実現するという古典的な方法を使っていて、acts_as_nested_listのほうは入れ子集合モデルを応用しているものになります。この違いによっていくつかのクリティカルの仕様変更を余儀なくされます。■違い1:acts_as_treeはルートを2つ以上もてる、acts_as_nested_list:ルートは1つしかもてない実社会でツリー構造をしているもののなかで、一番上がひとつのものは少ないのではないでしょうか。組織でも社長が一人とは限らないし、パソコンのファイルとフォルダをみても、一番上をたどっていったらフォルダがひとつなんてことはありません。acts_as_treeはparent_idをnilにすればすぐにルートになれたので、この点は単純で融通が利きました。■違い2:acts_as_treeに順番は存在しないのに対し、acts_as_nested_listには順番が存在する。nested_listという名前からもわかるように、acts_as_nested_listには何番目の子供かという情報があります。acts_as_treeにはその情報がないので、順番を設定したい場合はacts_as_listなどを追加で使う必要がありました。私も前バージョンの実装時にはこの二つをつかってツリー構造を定義していたわけです。■違い3:acts_as_treeはdestroy時、childrenを消さないのに対し、acts_as_nested_listはchildrenを消す厳密に言うとdescendantsを消します。acts_as_treeにはdescendantsメソッドはないので、すべての下位treeを取得する場合childrenを再帰ループさせる必要がありました。入れ子集合モデルではまさにこの点が有効で、SQLをひとつだけ発行すれば関連する親や子供を取得できることにあります。acts_as_nested_listに乗り換えた後は単にtree.destroyとするだけで子供も全部消してくれます。その際には子供のdestroyは呼ばずにdeleteで消している点も注意すべきポイントです。私の行っていた実装でこれらのことがどのように影響したかということと、どのように解決したかをお話します。■違い1に対してどうしてもルートを2つ持ちたかったわけです。そのため空のツリーを一番上に追加するようにしました。acts_as_nested_listにはいくつかルートに関するメソッドがありますが、それらを次のようにオーバーライドしました。#depthを使っていなければlevelend[/ror]このようにすると一番初めに強制的に作られたルートはずーっとルートのままになります。move_to_rootを呼ぶと実際にはrootの子供として設定されます。root?やchild?もルートのすぐ下の子供をrootと認識するようになります。rootメソッドだけは返すツリーはひとつと決まっていますのでそれは本来のrootを返すままになっています。わかりやすくなったかは微妙ですが、使いやすくなったことは確かです。acts_as_listを併用して使うということに落ち着きました。事情としては私の作ったUIにあわせるため、併用したほうがよさそうだったからです。最大の利点としてはツリー構造を表現する歳にSQLを単純にできたことでした。普通の考えであれば一番上のtreeを取得しそれにchildrenでループさせ、さらにchildrenがあれば、、、と再帰ループさせてツリーを表現します。それにはどうしてもchildrenを取得するためのSQLが発行されてしまいます。%>[/ror]とし、cssでマージンを設定すればよいです。$i}}[/ror]とすればだーっと作成してくれます厄介だったのはacts_as_listでのpositionとacts_as_nested_listにて設定されている何番目の子供かという情報を完全につじつまを合わせる必要があることです。いくつかのロジックを組むことで一応実現できましたが、まだvalidationをするにはいたっていません。移動することで階層構造を変更することができるようなものが必要でした。ツリー1└ツリー2└ツリー3ツリー4とあるとして、「ツリー3を左へ」とするとツリー1└ツリー2ツリー3ツリー4となってほしいというような仕様になります。この場合ツリー構造の視点から考えると「ツリー3をルート」の子供にするということがいえます。したければacts_as_nested_listのmove_to_child_ofメソッドを呼べばすみます。move_to_child_toメソッドは最後の子供として追加してしまうためツリー3はツリー4の弟になってしまいます。何番目の子供にするのかを指定しなければなりません。acts_as_nested_listにはmove_to_child_with_indexメソッドというのがあって、それを実現できます。「上へ」や「下へ」とする場合は兄弟関係が変わります。その場合はpositionも変わります。ツリー構造の変更時にはpositiontと何番目の子供かという情報につじつまを合わせるようにコードを変更する必要がありました。これはデータとしては冗長的です。しないことで、データの取り出す時にその取り出し方が冗長的であるならば、どっちかをとるしかありません。■違い3についてacts_as_nested_listでは親に対しdestroyするとその関連する子供も消すというのは、とても利にかなっていると思えました。実際必要なコードも少なくなり、非常に有効に思えました。destroyがあるとちょっと厄介です。私の場合はtreeは抽象クラスとしてpolymorphicにしています。treeはツリー構造だけを実装し、実際にはそのツリーに必ずひとつくっついている別のモデルがあるわけです。することでアプリケーションにあるいくつかのツリー構造をしたオブジェクトを一括で管理することができます。(実はこの点は後悔する部分があります。polymorphicはデータベース対して使ってしまうとそのテーブルだけ肥大しやすくなるからです。委譲にしとけばよかったかなー。)問題はacts_as_nested_listのdestroyメソッドを呼ぶとdescendantsに対しdeleteメソッドを呼ぶことにあります。destroyとしているオブジェクトが亡霊のようにデータベースに残ることになります。コレを回避するためにはbefore_destroyを定義して消そうとしているオブジェクトのdescendantsを走査し、あらかじめ関連オブジェクトを消しておく必要があります。|tree|tree.tree_item.destroyend[/ror]実際には消してしまいたいヤツと消したくないやつとあったのでもう少し複雑ですが、まこんなところです。現段階でのファイルを張っておきます。特異なコードなので誰の役に立つかわかりませんが、。そのうちプラグイン化したいものです。コメントをキャンセルメールアドレスが公開されることはありません。*サイト次回のコメントで使用するためブラウザーに自分の名前、メールアドレス、サイトを保存する。衝撃はゲーマーにゲームをやめさせる力があるあーやっちまった。