Active AdminでEnumsを用いた属性に基づいたチェックボックフィルタを作る

- Rails 6.0.1
- Active Admin 2.4.0

を使います。

# 結論

Active Adminで、Active RecordのEnums (`ActiveRecord::Enum`) を用いたモデルの属性のフィルタをチェックボックス形式で作りたいときは、Enumsのkey-valueマッピングのHashを渡します。

```ruby
# app/models/user.rb
class User
  enum status: { active: 0, suspended: 1, inactive: 2 }
end

# app/admin/user.rb
ActiveAdmin.register User do
  filter :status, as: :check_boxes, collection: User.statuses
  # User.statuses #=> { active: 0, suspended: 1, inactive: 2 }
end
```

|`active` / `suspended` で検索している|
|:--|
|<img width="896" alt="スクリーンショット 2019-11-21 23.46.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62097/4a2c59f7-333a-3c8d-5bf2-63c79103cf53.png">|

ここから下はうまくいかないケースについて説明します。

# うまくいかないケース

キーのリスト (`User.statuses.keys`) だけ渡すと、キーのリストだけ渡したときに生成するフォームでサーバへサブミットすると、サーバサイドでRansackによって検索するときにうまく動きません。

```ruby
# app/admin/user.rb
ActiveAdmin.register User do
  filter :status, as: :check_boxes, collection: User.statuses.keys
  # User.statuses.keys #=> ["active", "suspended", "inactive"]
end
```

|`suspended` で検索したが `active` なレコードだけ表示されている|
|:--|
|<img width="900" alt="スクリーンショット 2019-11-21 23.51.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62097/fc943157-060c-7d95-4b8c-bda974f71db6.png">|

## 何が問題か

### 生成されるフォームの構造

`check_boxes` filterにEnumsのキーのリストだけを渡すと、生成されるフォームのinputタグの `value` がステータスの文字列そのままになります:

```html
<div class="check_boxes input optional filter_form_field filter_check_boxes" id="q_status_input">
  <fieldset class="choices">
    <legend class="label">
      <label>Status</label>
    </legend>
    <label for="q_status_preparing">
      <input type="checkbox" name="q[status_in][]" id="q_status_preparing" value="preparing"> preparing
    </label>
    <label for="q_status_transferring">
      <input type="checkbox" name="q[status_in][]" id="q_status_transferring" value="transferring">transferring
    </label>
    <label for="q_status_succeeded">
      <input type="checkbox" name="q[status_in][]" id="q_status_succeeded" value="succeeded">succeeded
    </label>
    <label for="q_status_failed"><input type="checkbox" name="q[status_in][]" id="q_status_failed" value="failed"> failed
    </label>
  </fieldset>
</div>
```

inputタグの `value` が `status` のキーになっています。これをサブミットすると、サーバサイドでRansackがこの値を元にレコードを検索します。このとき、Enumsの属性は本来数値で検索しますが、文字列を渡すと、Enumsの最初の要素の値（いまだと0）が使われてしまいます。このときのログは次のとおりです:

```
Started GET "/admin/users?q%5Bstatus_in%5D%5B%5D=active&q%5Bstatus_in%5D%5B%5D=suspended&commit=Filter&order=id_desc" for ::1 at 2019-11-22 10:24:30 +0900
Processing by Admin::UsersController#index as HTML
  Parameters: {"q"=>{"status_in"=>["active", "suspended"]}, "commit"=>"Filter", "order"=>"id_desc"}
  Rendering /Users/kymmt90/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activeadmin-2.4.0/app/views/active_admin/resource/index.html.arb
   (0.2ms)  SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."status" IN (0, 0) LIMIT ? OFFSET ?) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
  CACHE  (0.0ms)  SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."status" IN (0, 0) LIMIT ? OFFSET ?) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
   (0.1ms)  SELECT COUNT(*) FROM "users" WHERE "users"."status" IN (0, 0)
  CACHE  (0.0ms)  SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."status" IN (0, 0) LIMIT ? OFFSET ?) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."status" IN (0, 0) ORDER BY "users"."id" desc LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
  Rendered /Users/kymmt90/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activeadmin-2.4.0/app/views/active_admin/resource/index.html.arb (Duration: 145.3ms | Allocations: 117153)
Completed 200 OK in 147ms (Views: 145.2ms | ActiveRecord: 0.6ms | Allocations: 118179)
```

こうなると、どのチェックボックスでフィルタしようとしても、つねに最初の要素だけが検索されてしまいます。

正解の方法だと、次のようなフォームが生成されます。

```html
<div class="check_boxes input optional filter_form_field filter_check_boxes" id="q_status_input">
  <fieldset class="choices">
    <legend class="label">
      <label>Status</label>
    </legend>
    <label for="q_status_0">
      <input type="checkbox" name="q[status_in][]" id="q_status_0" value="0"> preparing
    </label>
    <label for="q_status_1">
      <input type="checkbox" name="q[status_in][]" id="q_status_1" value="1"> transferring
    </label>
    <label for="q_status_2">
      <input type="checkbox" name="q[status_in][]" id="q_status_2" value="2"> succeeded
    </label>
    <label for="q_status_3">
      <input type="checkbox" name="q[status_in][]" id="q_status_3" value="3"> failed
    </label>
  </fieldset>
</div>
```

inputタグの `value` が整数値となっているので、これをサブミットすると正しく検索できます。このときのログは次のとおりです:

```
Started GET "/admin/users?q%5Bstatus_in%5D%5B%5D=0&q%5Bstatus_in%5D%5B%5D=1&commit=Filter&order=id_desc" for ::1 at 2019-11-22 10:22:18 +0900
Processing by Admin::UsersController#index as HTML
  Parameters: {"q"=>{"status_in"=>["0", "1"]}, "commit"=>"Filter", "order"=>"id_desc"}
  Rendering /Users/kymmt90/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activeadmin-2.4.0/app/views/active_admin/resource/index.html.arb
   (0.2ms)  SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."status" IN (0, 1) LIMIT ? OFFSET ?) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
  CACHE  (0.0ms)  SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."status" IN (0, 1) LIMIT ? OFFSET ?) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
   (0.1ms)  SELECT COUNT(*) FROM "users" WHERE "users"."status" IN (0, 1)
  CACHE  (0.0ms)  SELECT COUNT(*) FROM (SELECT 1 AS one FROM "users" WHERE "users"."status" IN (0, 1) LIMIT ? OFFSET ?) subquery_for_count  [["LIMIT", 30], ["OFFSET", 0]]
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."status" IN (0, 1) ORDER BY "users"."id" desc LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
  Rendered /Users/kymmt90/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activeadmin-2.4.0/app/views/active_admin/resource/index.html.arb (Duration: 142.3ms | Allocations: 117716)
Completed 200 OK in 144ms (Views: 142.2ms | ActiveRecord: 0.5ms | Allocations: 118736)
```

# 参考

- [Active Admin \| The administration framework for Ruby on Rails の "Customizing the Index Page" のページ](https://activeadmin.info/3-index-pages.html)