Help us understand the problem. What is going on with this article?

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

  • Rails 6.0.1
  • Active Admin 2.4.0

を使います。

結論

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

# 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 で検索している
スクリーンショット 2019-11-21 23.46.24.png

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

うまくいかないケース

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

# 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 なレコードだけ表示されている
スクリーンショット 2019-11-21 23.51.24.png

何が問題か

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

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

<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タグの valuestatus のキーになっています。これをサブミットすると、サーバサイドで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)

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

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

<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)

参考

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away