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

カッパでも分かるiOSアプリゲーム開発

カッパがひたすらゲーム制作に関することを書くブログです。Railsに関するTipsもたまにまとめてます。

【Rails】awesome_nested_set と the_sortable_tree でツリー構造を表示する

スポンサードリンク

パーフェクト Ruby on Rails
f:id:InvokeTwoA:20151023183618p:plain「ツリー構造の表示をしたいなー」
f:id:InvokeTwoA:20151215172640p:plain「そんな時は awesome_nested_set !」
f:id:InvokeTwoA:20151023183618p:plain「ふむふむ。対応情報のテーブルを作らないで良いのかー」
【目次】

awesome_nested_set って何?

  • 対応表のテーブルを用意せず、そのモデル単体で階層構造を持つ事ができるライブラリ
  • 様々なメソッドが用意されていて、子や親を簡単に取得できるようになる

GitHub - collectiveidea/awesome_nested_set: An awesome replacement for acts_as_nested_set and better_nested_set.

使い方

gem から awesome_nested_set をインストール

f:id:InvokeTwoA:20151023183618p:plain「まずはgemから bundle install 」
f:id:InvokeTwoA:20151215172640p:plain「最近はライブラリを入れるのが楽で良いですねー」

gem 'awesome_nested_set'
bundle install

awesome_nested_set に必要なカラムを追加し、migrationを実行

f:id:InvokeTwoA:20151023183618p:plain「今回はTeamというモデルに階層構造を持たせるよ」
f:id:InvokeTwoA:20151215172640p:plain「このgemを入れなければ、 team_relations みたいなテーブルを作る羽目になってました」
f:id:InvokeTwoA:20151023183618p:plain「その代わり teams テーブルに awesome_nested_set に必要なカラムを三つ追加しないといけないんだよねー」

  def change
    add_column :teams, :parent_id, :integer
    add_column :teams, :lft,       :integer
    add_column :teams, :rgt,       :integer

    add_index :teams, :parent_id
    add_index :teams, :lft
    add_index :teams, :rgt

    Team.rebuild!
  end

f:id:InvokeTwoA:20151023183618p:plain「lft,rgt っていうのが何のカラムなのか分かりにくいよね」
f:id:InvokeTwoA:20151215172640p:plain「人が気にしてはいけないカラムですな」

※ちなみに初回は rebuild! を実行しないと lft, rgt が初期化されずにエラーになってしまうので注意
( comparison of Fixnum with nil failed のエラーが出てしまう)

model 内で awesome_nested_set を使うと宣言

f:id:InvokeTwoA:20151023183618p:plain「Team モデルに下記の行を追加だー!」

acts_as_nested_set

これで自由に awesome_nested_set が使える

f:id:InvokeTwoA:20151023183618p:plain「結構豊富なメソッドが用意されてるよね」
github.com
f:id:InvokeTwoA:20151215172640p:plain「よく使うメソッドは下記ですかねー」

Team.rebuild!    # 階層情報を更新。初回のみ、必ず実行しなければならない
team.level         # そのモデルのlevel. トップだと0 1階層深くなるごとに+1されていく
team.parent      # そのモデルの一つ上の親
team.children    # そのモデルの一つ下の子を全て取得

f:id:InvokeTwoA:20151023183618p:plain「続いてUI編」

the_sortable_tree を使ってグラフィカルなUIを表示

the_sortable_tree って何?

  • awesome_nested_set をグラフィカルに表示してくれる

(リスト構造とかのスタイルシートが付いてる)

  • ドラッグ&ドロップで階層情報の更新ができる(ヌルッと出来すぎて更新されてるか不安になる)

GitHub - the-teacher/the_sortable_tree: Nested Set + Drag&Drop GUI. Very fast! Best render helper! 2000 nodes/sec. Ready for rails 4

使い方

まずは gem からインストール

gem 'the_sortable_tree'

bundle install

javascript, css をそれぞれインクルード

javascript

//= require jquery-ui
//= require jquery_ujs 
//= require jquery.ui.nestedSortable
//= require sortable_tree/initializer

css

*= require tree
*= require sortable_tree
*= require nested_options

routing に rebuild メソッドを追加(javascript で動的にデータを更新するため必須)

 resources :teams do
    collection do
      post :rebuild
    end
  end

TeamModel に下記を追加

 include TheSortableTree::Scopes

ViewController にも下記を追加

include TheSortableTreeController::Rebuild

controller に階層情報を格納したインスタンスを用意

  def index
    @teams = Team.nested_set.select('id, name').all  # 欲しいカラム名を指定
  end

例えば view をこんな感じに書く

%ol.sortable_tree{ data: { max_levels: 5, rebuild_url: rebuild_pages_url } }
  = sortable_tree @teams

f:id:InvokeTwoA:20151023183618p:plain「これだけで階層構造の表示ができてる!」
f:id:InvokeTwoA:20151215172640p:plain「続いてview のカスタマイズ」

view のカスタマイズをする

bundle exec rails g the_sortable_tree:views sortable

このコマンドを実行すると下記ファイルができる
app/helpers/render_sortable_tree_helper.rb
  • いじりたいviewの部分を修正する
      def render_node(h, options)
        @h, @options = h, options
        node = options[:node]
        "
          <li data-node-id='#{ node.id }'>
            <div class='item'>
              <i class='handle'></i>
              #{ show_link }
              #{ controls }
            </div>
            #{ children }
          </li>
        "
      end
controls 右端の edit, destroy の部分。不要ならば消せば非表示になる
show_link リンク先を変更するならば変える

まとめ

f:id:InvokeTwoA:20151023183618p:plain「……めでたしめたし」
f:id:InvokeTwoA:20151215172640p:plain「viewをカスタマイズしてる時は『viewをカスタマイズしてる記事が見当たらねえ。俺が書くしかないぜ』と思いましたが、実はあんまし書く事がなかった感」
f:id:InvokeTwoA:20151023183618p:plain「日本語記事で書く事に意味がある、きっと」