ActiveAdmin リソースの扱い

## ActiveAdmin リソースの扱い
***
【これは http://activeadmin.info/docs/2-resource-customization.html を翻訳したものです。】
***

### リソースの作成
リソースは基本的に `rails g active_admin:resource Post` といったコマンドで作成します。これは `app/admin/post.rb` といった以下のような空のファイルを生成します。

```ruby
ActiveAdmin.register Post do
  # everything happens here :D
end
```

### Strong parameters の設定
Rails 4 では `attr_accessible` の代わりに [Strong Parameters](https://github.com/rails/strong_parameters) が導入されました。これはホワイトリスト化されたアトリビュートをモデルからコントローラへ渡します。

どのパラメータが変化するかを定義する `permit_params` メソッドを用います。

```ruby
ActiveAdmin.register Post do
  permit_params :title, :content, :publisher_id
end
```

HABTM アソシエーションや単一の配列アトリビュートといった、複数の値を持つフォーム・フィールドからパラメータを送るには、空の配列を指定します。

```ruby
ActiveAdmin.register Post do
  permit_params :title, :content, :publisher_id, roles: []
end
```

同一フォームのネストしたアソシエーションもひとつの配列を必要としますが、アトリビュートを指定せねばなりません。

```ruby
ActiveAdmin.register Post do
  permit_params :title, :content, :publisher_id,
    tags_attributes: [:id, :name, :description, :_destroy]
end

# 「accepts_nested_attributes_for」が必要なことに注意
class Post < ActiveRecord::Base
  accepts_nested_attributes_for :tags, allow_destroy: true
end
```

アトリビュートを動的に指定するには、ブロックを指定します。

```ruby
ActiveAdmin.register Post do
  permit_params do
    params = [:title, :content, :publisher_id]
    params.push :author_id if current_user.admin?
    params
  end
end
```

`permit_params` を呼ぶと、 `permitted_params` というメソッドが生成されます。このメソッドを用いて `create` または `update` アクションをオーバーライドします。

```ruby
ActiveAdmin.register Post do
  controller do
    def create
      # 良い例
      @post = Post.new(permitted_params[:post])
      # 悪い例
      @post = Post.new(params[:post])

      if @post.save
        # ...
      end
    end
  end
end
```

### リソースに対するアクションを無効にする
デフォルトではすべての CRUD アクションが有効です。これらは無効にできます。

```ruby
ActiveAdmin.register Post do
  actions :all, except: [:update, :destroy]
end
```

### リソースの名前を変える
デフォルトでは、インタフェース中のリソースに対するすべてのリファレンス (メニュー, routes, ボタンなど) は、クラスの名前として用いられます。リソースの名前を変えるには `:as` オプションを用います。

```ruby
ActiveAdmin.register Post, as: "Article"
```

上記のリソースには `/admin/articles` でアクセスできます。

### 名前空間のカスタマイズ
デフォルトの `admin` 名前空間は、以下のように変更できます。

```ruby
# /today/posts でアクセス
ActiveAdmin.register Post, namespace: :today

# /posts でアクセス
ActiveAdmin.register Post, namespace: false
```

### メニューのカスタマイズ
デフォルトでは、グローバル・ナビゲーションにリソースが表示されます。グローバル・ナビゲーションにリソースが表示されないようにするには以下のようにします。

```ruby
ActiveAdmin.register Post do
  menu false
end
```

`menu` メソッドは以下のオプションをハッシュとして受け取ります。

- `:label` \- メニューに表示する文字列または proc label。proc を指定すると、メニューがレンダリングされるたびにその proc が呼ばれます。

- `:parent` \- 親がそのメニューを用いるとき使用する文字列ID (またはラベル)

- `:if` \- そのメニューを表示するかどうかを決定するブロックまたはメソッドのシンボル

- `:priority` \- プライオリティ値。デフォルトは10。

#### ラベル
メニューのラベルを変更するには、以下のようにします。

```ruby
ActiveAdmin.register Post do
  menu label: "My Posts"
end
```

より動的に変更するには、proc を指定します。

```ruby
ActiveAdmin.register Post do
  menu label: proc{ I18n.t "mypost" }
end
```

#### メニュー・プライオリティ
メニューの要素は、まずそのプライオリティ数に従って並べられ、次にアルファベット順に並べられます。なのですべてのメニューのプライオリティがデフォルトの10である場合は、常にアルファベット順になります。

これをカスタマイズするには以下のようにします。

```ruby
ActiveAdmin.register Post do
  menu priority: 1 # 左端になります
end
```

#### メニュー要素の表示/非表示の制御
`:if` オプションでメニューを表示したりしなかったりすることができます。

```ruby
ActiveAdmin.register Post do
  menu if: proc{ current_user.can_edit_posts? }
end
```

#### ドロップダウンメニュー
多くの場合、大きなアプリケーションではシングル・レベルのナビゲーションは不十分です。そのような場合、メニュー要素をグループ化して親メニューの下にすることができます。

```ruby
ActiveAdmin.register Post do
  menu parent: "Blog"
end
```

「Blog」というメニュー要素を作成する必要がない点に注意して下さい。動的に作成されます。

#### 親メニュー要素のカスタマイズ
普通のメニュー要素に使えるオプションは、すべて親メニュー要素に使えます。複雑な親メニュー要素を使う場合には、Active Admin 初期化ファイルで設定してください。

```ruby
# config/initializers/active_admin.rb
config.namespace :admin do |admin|
  admin.build_menu do |menu|
    menu.add label: 'Blog', priority: 0
  end
end

# app/admin/post.rb
ActiveAdmin.register Post do
  menu parent: 'Blog'
end
```

#### 動的親メニュー要素
上記がうまく動いたとしても、親メニュー要素のラベルを動的に変更するにはどうすればよいでしょうか？その場合は親メニュー要素の `:id` を参照するようにしてください。

```ruby
# config/initializers/active_admin.rb
config.namespace :admin do |admin|
  admin.build_menu do |menu|
    menu.add id: 'blog', label: proc{"Something dynamic"}, priority: 0
  end
end

# app/admin/post.rb
ActiveAdmin.register Post do
  menu parent: 'blog'
end
```

#### カスタム・メニュー要素の追加
メニュー・ラベルをもっとカスタマイズしたい場合があります。この場合は、Active Admin 初期化ファイルの名前空間でメニューをカスタマイズできます。

```ruby
# config/initializers/active_admin.rb
config.namespace :admin do |admin|
  admin.build_menu do |menu|
    menu.add label: "The Application", url: "/", priority: 0

    menu.add label: "Sites" do |sites|
      sites.add label: "Google",   url: "http://google.com", html_options: { target: :blank }
      sites.add label: "Facebook", url: "http://facebook.com"
      sites.add label: "Github",   url: "http://github.com"
    end
  end
end
```

上記は、アプリケーションが開始した後で、リソースがロードされる前に登録されます。

### アクセス権限
管理者に異なるアクセスレベルがある場合、アクセス権を持っているかどうか調べる必要があるでしょう。ユーザ・モデルに has_many リレーションシップがあると仮定した場合、以下のように簡単に制御できます。

```ruby
ActiveAdmin.register Post do
  scope_to :current_user # アクセスできるポストを `current_user.posts` に制限する

  # アソシエーションがデフォルト名でない場合
  scope_to :current_user, association_method: :blog_posts

  # ブロックを渡すこともできます
  scope_to do
    User.most_popular_posts
  end
end
```

アクセス権を条件付きにすることもできます。

```ruby
ActiveAdmin.register Post do
  scope_to :current_user, if:     proc{ current_user.limited_access? }
  scope_to :current_user, unless: proc{ current_user.admin? }
end
```

### Eager Loading
eager loading を使って N+1 問題を解決すると、一般的にページのパフォーマンスが良くなります。

```ruby
ActiveAdmin.register Post do
  includes :author, :categories
end
```


### リソース検索のカスタマイズ
ActiveAdminのコントローラは[継承リソース](https://github.com/activeadmin/inherited_resources)で実装されているので、[そのすべての機能](https://github.com/activeadmin/inherited_resources#overwriting-defaults)を使うことができます。

コレクションのプロパティをカスタマイズする場合は、`scoped_collection` メソッドをオーバーライドしてください。

```ruby
ActiveAdmin.register Post do
  controller do
    def scoped_collection
      end_of_association_chain.where(visibility: true)
    end
  end
end
```

レコード検索コードを完全に書き換えなければならない場合 (例: モデル内で `to_param` をカスタム実装している)、コントローラの `resource` メソッドをオーバーライドしてください。

```ruby
ActiveAdmin.register Post do
  controller do
    def find_resource
      scoped_collection.where(id: params[:id]).first!
    end
  end
end
```

CanCan のような権限管理ライブラリを使っている場合、以下のように書かないよう注意してください。このようにすると、**権限ルールが無効になってしまいます**。

```ruby
ActiveAdmin.register Post do
  controller do
    def find_resource
      Post.where(id: params[:id]).first!
    end
  end
end
```

### belongs_to
リレーションシップに対するリソースを、アクセス制御したいことはよくあります。例えば、プロジェクトはマイルストーンとチケットを持っています。あるリソースを他のリソースに対してネストするには、 `belongs_to` メソッドを使います。

```ruby
ActiveAdmin.register Project
ActiveAdmin.register Ticket do
  belongs_to :project
end
```

`/admin/projects/1/tickets` にアクセスすることでプロジェクトには普通にアクセスでき、プロジェクトは id が 1 のものがひとつだけあると仮定します。Active Admin は「チケット」をナビゲーションに追加しないのですが、それはプロジェクトの id がないと routes を決定できないからです。

リソースへのリンクを作成するには、サイドバーを使います。(サイドバーはとても使い勝手の良いユーザ・インタフェースです。)

```ruby
ActiveAdmin.register Project do

  sidebar "Project Details", only: [:show, :edit] do
    ul do
      li link_to "Tickets",    admin_project_tickets_path(project)
      li link_to "Milestones", admin_project_milestones_path(project)
    end
  end
end

ActiveAdmin.register Ticket do
  belongs_to :project
end

ActiveAdmin.register Milestone do
  belongs_to :project
end
```

ときおり (プロジェクトのように)、たくさんのサブ・リソースがあると、プロジェクトの「情報」をグローバル・ナビゲーションで切り替えたくなることが実際あります。これに対応するために、Active Admin は `belongs_to` リソースを別のメニューに分けています。

```ruby
ActiveAdmin.register Ticket do
  belongs_to :project
  navigation_menu :project
end

ActiveAdmin.register Milestone do
  belongs_to :project
  navigation_menu :project
end
```

これで、「チケット」セクションにナビゲートする際、グローバル・ナビゲーションには「チケット」と「マイルストーン」だけが表示されるようになります。belongs_to でないリソースに戻ると、グローバル・ナビゲーションもデフォルト・メニューに戻ります。

多分ユーザ権限の関連で、異なるメニューを実行時に動的に決定したい場合があるでしょう。以下のようにします。

```ruby
ActiveAdmin.register Ticket do
  belongs_to :project
  navigation_menu do
    authorized?(:manage, SomeResource) ? :project : :restricted_menu
  end
end
```

# お勧め記事
[Active Admin 徹底解説 - Qiita](https://qiita.com/MariMurotani/items/aed93986e29249fd65e5)