Railsで管理画面 - 掲示板 編
前回の続き
・コントローラー
adminの時はbase_controllerを使っているので、今まで使っていたApplicationControllerで作ったboards_controllerやuser_controller。。。などは引き継がれない(引っ張ってくることが出来ない)
つまり、もう一度boards_controllerやuser_controllerを定義し直さなければならない
とは言え一度作成しているので使いたい中身はほぼ同じ。ただ挙動を変える必要があるくらいだ。
また、今課題には今までに無かったuserの一覧画面の作成があるのでuserの情報を取って来なければならない。その辺りはコントローラーで明記し、ビューで表示させる。
rails g controller admin::Boards # この時オプションでビューも作っていいがビューは後から個別で作る
今まで作ってきた元々のboardsコントローラーの中身を上で作ったコントローラー内に移植し部分修正
・自分の回答 # controllers/admin/boards_controller
class Admin::BoardsController < Admin::BaseController
before_action :set_params, only: %i[edit show update destroy]
def index # ↓回答例と違う変数なのでindexのビューで呼ぶ時は@searchを使う
@search = Board.ransack(params[:q]) #bookmarkは要らない↓と思ったのでuserだけ
@boards = @search.result(distinct: true).includes(%i[user]).order(created_at: :desc).page(params[:page])
end
def show; end #
def edit; end
def update
if @board.update(board_params)
redirect_to admin_board_path, success: t('defaults.message.updated', item: Board.model_name.human)
else # ↑ パスを変更
flash.now[:danger] = t('defaults.message.not_updated', item: Board.model_name.human)
render :edit
end
end
def destroy
@board.destroy!
redirect_to admin_boards_path, success: t('defaults.message.deleted', item: Board.model_name.human)
end # ↑ パスを変更
private
def set_params
@board = Board.find(params[:id]) # ここで詰まってた
end
def board_params
params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
end
end
・ビュー
コントローラーでboardのidを取ってくることが出来たので、
・一覧(index),詳細(show),編集(edit)ページを作成する。 ・ファイルとフォルダは新たに作成しadmin下にすること。
一覧表示と言っても今までのページの表示ではなく管理者用のページなので画像は必要なく、リスト化された物で課題詳細にあったように
掲示板一覧では、ID、タイトル、作成者、作成日の項目を表示
が出来れば良い。
参考にするのは以下のBootstrap
一覧画面(index)
・自分の回答 # views/admin/boards/index.html.erb
<%= content_for(:title, t('.title')) %> # ページタイトル部分
<h1><%= t('.title') %></h1> # タイトル部分は元々のビューから引っ張ってきた
<div class="col-lg-10 offset-lg-1">
<!-- 検索フォーム -->
<form>
<%= render 'search_form', url: admin_boards_path, search: @search %>
</form> # ↑ 検索フォームもパーシャルにした、パスも気をつける事 - 後述
</div>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col">タイトル</th>
<th scope="col">作成者</th>
<th scope="col">作成日時</th>
</tr>
</thead>
<tbody>
<%= render @boards %> # パーシャル作った
</tbody>
</table>
例にはページネーションが付いてた。 また、<th>タグの中の文字もきちんとja.ymlに記載しているが故の書き方になっている(activerecordの方) 検索のパーシャル部分もスマートになっている
パーシャル部分
# views/admin/boards/_board.html.erb
<%= link_to board.title, admin_board_path(board) %>
# パスに注意
<%= board.user.decorate.full_name %>
...
<%= l board.created_at, format: :long %>
<%= link_to '編集', edit_admin_board_path(board), class:"btn btn-success", id: "button-edit-#{board.id}" %>
<%= link_to '削除', admin_board_path(board), id: "button-delete-#{board.id}", class:"btn btn-danger",
method: :delete, data: {confirm: t('defaults.message.d_confirm')} %>
検索のパーシャル
・自分の書き方 # /views/admin/boards/_search_form.html.erb
...
<%= search_form_for search, url: url do |f| %>
...
<%= f.search_field :title_or_body_cont, class:"form-control", placeholder:"検索ワード" %>
...
<%= f.date_field :created_at_gteq, include_blank: true, class: 'form-conrol' %>
<span>~</span>
<%= f.date_field :created_at_lteq_end_of_day, include_blank: true, class: 'form-conrol' %>
...
<%= f.submit class:"btn btn-primary input-group-append" %>
...
<% end %>
後述するが :created_at_gteq と言う部分が、今回の検索機能の「〜から〜まで」の部分のメソッドになる
form_forで使用できるhtmlタグ
f.selectやf.date_selectを使うことで選択肢や日付選択ボックスを生成してくれる。 よく見かけるf.submitが送信ボタンの生成してくれる様に。
セレクトボックスの書き方は以下
【開発メモ】Ruby on Railsのform_forでドロップダウンリストの選択ボックスを設置する方法 | FREE SWORDER
詳細(show)
・ # views/admin/boards/show.html.erb
<% content_for(:title, @board.title) %>
...
<h1><%= t('.title') %></h1>
...
<%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %>
...
<h3 class="d-inline"><%= @board.title %></h3>
<%= render 'crud_menus', board: @board %> # 元々の掲示板同様にアイコンの編集削除のパーシャルを作った
<ul class="list-inline">
<li class="list-inline-item">by <%= @board.user.decorate.full_name %></li>
<li class="list-inline-item"><%= l @board.created_at, format: :long %></li>
...
<p><%= simple_format(@board.body) %></p>
...
※自作のパーシャル部分 # admin/boards/_crud_menus.html.erb
<%= link_to t('defaults.edit'), edit_admin_board_path(board), id: "button-edit-#{board.id}" %>
...
<%= link_to t('defaults.delete'), admin_board_path(board), id: "button-delete-#{board.id}", method: :delete, data: {confirm: t('defaults.message.d_confirm')} %>
機能的には問題なかった。
・編集(edit)
これも元々の編集ファイルからコピーしたものにパスを変えただけの様なものになった
・# /views/admin/boards/edit.html.erb
# ↓ :adminを追加している
<%= form_with model: [:admin, @board], local: true do |f| %>
<%= render 'layouts/error_messages', object: f.object %>
...
エラーメッセージのファイルは自分はlayoutsに入れてるのでエラーは出なかった
範囲検索(ransack)
ハッキリ言ってgithubの公式を見ただけで実装はできなかった。
上のリンクの中にある Custom Predicates というところをクリックすると以下のコードがある
# config/initializers/ransack.rb
Ransack.configure do |config|
config.add_predicate 'equals_diddly', # Name your predicate
# What non-compound ARel predicate will it use? (eq, matches, etc)
arel_predicate: 'eq',
# Format incoming values as you see fit. (Default: Don't do formatting)
formatter: proc { |v| "#{v}-diddly" }, #
# Validate a value. An "invalid" value won't be used in a search.
# Below is default.
validator: proc { |v| v.present? },
# Should compounds be created? Will use the compound (any/all) version
# of the arel_predicate to create a corresponding any/all version of
# your predicate. (Default: true)
compounds: true,
# Force a specific column type for type-casting of supplied values.
# (Default: use type from DB column)
type: :string,
# Use LOWER(column on database).
# (Default: false)
case_insensitive: true
end
この時は翻訳通してもイマイチ言ってることが分からず。
あと上部にコメントアウトしてる # config/initializers/ransack.rb ってなんなん?と思って
config/initializersの中を見たけどそんなファイル無いし
arel_predicateをgithub内で検索したり
'ransack 日付 範囲' とか 'ransack arel_predicate' でググってたら既にまとめてくれている人がいました( ありがたや🙏 )
以下のページで'arel_predicate'と検索すると上の公式とかも出てくることろのちょい下に欲しいものがあった{第5章 044部分}
一応、上の中で出てきたlteqをgithub内で検索
あ〜。。。 以下より小さいか等しい って意味ね。。。
かなり深いとこまで行かないとわかりませんでした。
逆に、 は以下より大きいか等しい となるとgteqに訳が充てられていたので、lteqとgteqを使えば良さそうです(以下参照)
ということで
猫Railsさんのページを見てransackファイルから作成します
config/initializers/ransack.rbが存在しない場合は追加(猫Railsから参照)
# config/initializers/ransack.rb
Ransack.configure do |config|
# 述語名
config.add_predicate 'lteq_end_of_day',
# Arelの述語を指定。<=で検索したいからlteqを使うよ。
arel_predicate: 'lteq',
# インプットの整形。その日の終わりまでを検索対象に含めるよ。
formatter: proc { |v| v.end_of_day }
end
predicateは述語という意味です
ここまでで実装は完了。
以下は考察
検索のパーシャルに書かれていたものの中の下のピンク文字部分
<div class="col-auto">
<%= f.date_field :created_at_gteq, class: 'form-control' %>
<span>〜</span>
<%= f.date_field :created_at_lteq_end_of_day, class: 'form-control' %>
</div>
これによって範囲を指定しているんですね。??
formatterの上のコメントアウト部分を翻訳すると
# 入力される値を好きなようにフォーマットします。(デフォルト: フォーマットを行わない)
formatter: proc { |v| "#{v}-diddly" },
# 値を検証します。無効」な値は検索に使用されません。
# 以下はデフォルトです。
validator: proc { |v| v.present? },
ransack.rbにあるprocがオブジェクト化してくれているおかげ、と解釈しています
formatter: proc { |v| v.end_of_day }
更に、created_at_lteqだと0:00が基準になってしまうので上記でカスタマイズしたということです。
end_of_day はなんで使えるの?
end_of_dayはrailsのメソッドです 23:59:999999までを認識してくれます
沼にハマったので先に言っておくと
中の動きを知る事も大事だがその機能を使うことが出来れば良い
極論言えば、「動けばいい」=͟͟͞͞( •̀д•́)
変数 | v | の v は、正直なんでも良いが、
よく見る| i | や | f | などの i は index の i で、f はform の f というようになっている
肝心のvは忘れた。すまん
以下、殴り書き
|v|はformatterの引数
formatter: proc { |v| v.〇〇} # の〇〇に代入すれば、lteqをend_of_dayに置き換えることができる。
add_predicateで検索の動作、formatterで入力値の変換
config.add_predicate 'to_age_lt',
arel_predicate: 'gteq',
formatter: -> (v) { (v.years.ago + 1.day).to_date },
type: :integer,
compounds: false
formatterで入力値を変換するので、
User.search(birthday_to_age_gteq: 18).result
これで検索する値が
18.years.ago + 1.day
18年前の今日、
2003/05/09
になる
rails cで見る
select * from users
where birthday >= '2003/05/09'