LoginSignup
2
1

More than 3 years have passed since last update.

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

Posted at
  • 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)

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1