ActiveAdminでメニューの並び順を1箇所で管理する


2016/11/22 追記

この記事の内容が(さらに改善されて)gemになりました。
https://github.com/yhirano55/active_admin_menu

シチュエーション

上記ヘルプに(2015/05/24時点で)あるように、ActiveAdminでページを作ってメニューの設定を行うとき、並び順を決定するための優先度は各ページに書く必要があります。
しかしこれではページが多くなってくると管理が面倒になります。

実装

config/initializers/active_admin_menus.rb
MENU_ITEMS = [
  [:item, 'Dashboard', {label: proc{ I18n.t("active_admin.dashboard") }}],
  [:item, 'Item', {}],
  [:item, 'Skill', {label: '特技検索'}],
  [:item, 'Evolution', {label: '進化検索'}],
  [:category, 'master',
    [
      ['Element', {}],
      ['BoxKind', {}],
    ]
  ],
  [:item, 'Comment', {}],
]

MENU_ITEM_EXCLUDES = %w()

def inverted_menu_items
  MENU_ITEMS.each_with_object([]).with_index do |(source_item, inverted), index|
    case source_item.first
    when :item
      _, page, args = source_item
      inverted << [page, args.merge(priority: index)]
    when :category
      _, category, children = source_item
      inverted << [category, {label: I18n.t("active_admin.menu.#{category}"), priority: index}]
      children.each_with_index do |category_item, index2|
        page, args = category_item
        inverted << [page, args.merge(parent: I18n.t("active_admin.menu.#{category}"), priority: index2)]
      end
    end
  end
end

def check_index_and_add_priority_and(name, &block)
  index = inverted_menu_items.map(&:first).index(name)
  if index
    args = inverted_menu_items[index][1]
    block.call(args)
  elsif !(MENU_ITEM_EXCLUDES.include? name)
    raise "#{name} is NOT placed into the menu"
  end
end

ActiveAdmin.application.namespace(false).build_menu do |menu|
  MENU_ITEMS.select {|i| i.first == :category}.each do |item|
    name = item[1]
    check_index_and_add_priority_and(name) do |args|
      menu.add args
    end
  end
end

class ActiveAdmin::DSL
  def allocate_to_menu
    name = config.resource_name.name
    check_index_and_add_priority_and(name) do |args|
      menu args
    end
  end
end

module MenuAllocator
  def register(*args, &block)
    super *args do
      allocate_to_menu
      self.instance_eval &block
    end
  end

  def register_page(*args, &block)
    super *args do
      allocate_to_menu
      self.instance_eval &block
    end
  end
end

module ActiveAdmin
  class Namespace
    prepend MenuAllocator
  end
end

解説

基本方針

ページの登録時の処理をフックして、各ページのコードから呼び出すmenuコマンドを、設定したメニューの並び順(priority)を含んだ形で呼びだします。

例外的な処理

注意点

  • menuコマンドは1つのページから複数回呼び出せますが、最後に呼び出したものの引数の内容だけが適用されるので、各ページの実装からmenuコマンドを呼び出すとここでの設定が反映されません。
  • namespace(false)なのは、 http://qiita.com/skuroki@github/items/fbded7846a78feb8bef1 の例2のやりかたで / 直下にActiveAdminのアプリケーションを配置しているためです。そうでない人は適宜読み替えてください。