Admin-LTE3を使用した管理画面の実装③(掲示板/ユーザーのCRUD)
前回に続いて、Admin-LTE3を使った管理画面の実装を進めていきました。 その中で学んだ事をまとめました。
enum_helpを使用したプルダウンのセレクトボックスの実装
セレクトボックスの実装で少し詰まったので、色々な記事を参考にして自分なりに解釈し、わかりやすくまとめてみた。
#権限選択画面のコード <%= f.select :role_eq, User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]}, { include_blank: t(‘defaults.unspecified’) }, { class: ‘form-control mr-1’ } %>
User.roles_i18n.invert.map{|key, value| [key, User.roles[value]]}
の部分が非常にややこしいので順に読み解いていく。
まずはenum_helpを使って日本語化する前のコード
<%= f.select :role_eq, User.roles.values %>
#rails console内 User.roles => {"general"=>0, "admin"=>1} User.roles.values => [0, 1]
User.roles.values
により、roleカラムの値を配列として取得できる。
これを、enum_helpを使用しi18nで日本語化する。
#rails.console内 User.roles_i18n => {"general"=>"一般", "admin"=>"管理者"} User.roles_i18n.values => ["一般", "管理者"]
# admin/users/index.html.erb内 <%= search_form_for(@q, url: admin_users_path, method: :get) do |f| %> <div class="input-group mb-3"> <%= f.label :role_eq, '権限' %> <%= f.select :role_eq, User.roles_i18n.values, include_blank: '指定なし' %> </div> <% end %>
しかし、このコードだと発行されるSQLが常に”role” = 0
となってしまい正常に動作しない。
Processing by Admin::UsersController#index as HTML Parameters: {"utf8"=>"✓", "q"=>{"first_name_or_last_name_cont"=>"","role_eq"=>"管理者"}, "commit"=>"検索"} User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 8], ["LIMIT", 1]] ↳ vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98 Rendering admin/users/index.html.erb within layouts/admin_application User Load (0.3ms) SELECT DISTINCT "users".* FROM "users" WHERE "users"."role" = 0 ORDER BY "users"."created_at" DESC ↳ app/views/admin/users/index.html.erb:23
どうやらこれは、ransackがそもそもenumに対応していない事が原因で、status_idが常に0になってしまうというバグらしい。
参考記事
RansackはRailsのenumに対応していないっぽい | 地方でリモートワーク
そこで、invertメソッドとmapメソッドを組み合わせてstatus_idを正しく取得できるようにする。
Invertメソッドとmapメソッドの挙動について
#rails.console内 User.roles_i18n => {"general"=>"一般", "admin"=>"管理者"} User.roles_i18n.invert => {"一般"=>"general", "管理者"=>"admin"} User.roles_i18n.invert.map{|key,value| [key, User.roles[value]]} => [["一般", 0], ["管理者", 1]]
3つ目の挙動が少し難しいかもしれないが、これは、
“一般” “管理者”というキーが key に、”general” “admin”という値が valueにそれぞれ代入され、mapメソッドによってそれぞれに対して処理が行われている。
参考記事
ハッシュのキー、値の配列を取得する (keys, values) | まくまくRubyノート
#rails.console内 User.roles["general"] => 0 User.roles["admin"] => 1
この結果、一般タブが選択されるとvalue=“0”
が、管理者タブが選択されるとvalue="1"
が選択されるようになり、正常に動作するようになった。
#発行されているhtml <select name="q[role_eq]" id="q_role_eq"><option value="">指定なし</option> <option value="0">一般</option> <option value="1">管理者</option></select>
ransackのカスタムpredicateについて
Ransackでは様々な検索機能がある。また、検索に使うpredicate(デフォルトで用意されている検索用のメソッドのようなもの)は、カスタムすることも可能である。
今回の日付範囲検索は、デフォルトpredicateのcreated_at_lteq
だと、0:00までとなってしまい、正しく検索する事ができないので、created_at_lteq_end_of_day
というカスタムpredicateを作る必要がある。
Config/initializerに、ransack.rbというファイルを作って以下のコードを記述する
# config/initializers/ransack.rb Ransack.configure do |config| config.add_predicate ‘lteq_end_of_day’, arel_predicate: ‘lteq’, formatter: proc { |v| v.end_of_day } end
arel_predicate: ‘lteq’
でカスタマイズしたいpredicateを指定し、
formatter: proc { |v| v.end_of_day }
の箇所で、23:59:59までを指定している。
class=“active”を動的にして、サイドバーのアクティブ機能を実装する
Admin/boardsコントローラーのアクションが実行されているときは、「掲示板一覧」が、Admin/usersコントローラーのアクションが実行されているときは「ユーザー一覧」がアクティブになるようにする。
Application.helerにこのように記述し、
def active?(controller_name) return 'active' if controller_name == params[:controller] end
これはあまり綺麗ではない。三項演算子を使って、else時の返り値も明示してあげた方が良い。
修正後のコード
def active_if(path) path == controller_path ? 'active' : '' end
# _sidebar.html.erb内 <%= link_to admin_boards_path, class: "nav-link #{active_if('admin/boards')}" do %>