プログラミング学習 備忘録

Railsを学習していく上での技術メモ。学んだことや解決したエラーなどを記録していきます。

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