管理画面のCRUD作成

CRUD作成

以下のような、管理画面に掲示板とユーザーのCRUD(Create(生成)、Read(読み取り)、Update(更新)、Delete(削除))機能を作成する。 f:id:meo2:20210710180941p:plain

ルーティング

  namespace :admin do
    root to: 'dashboards#index'
    resources :users, only: %i[index show edit update destroy]
    resources :boards, only: %i[index show edit update destroy]
    get 'login', to: 'user_sessions#new'
    post 'login', to: 'user_sessions#create'
    delete 'logout', to: 'user_sessions#destroy'  
  end

f:id:meo2:20210710180722p:plain

掲示板のCRUD作成

掲示板一覧では、ID、タイトル、作成者、作成日の項目を表示させる。
それぞれに対して「詳細」「編集」「削除」ボタンの項目を表示させ、削除時には確認アラートを表示させる。

controller

boards_controllerの作成
$rails g controller admin::boards
継承元をbase_controlerにする。
ransackで検索機能をつける。→参考

  class Admin::BoardsController < Admin::BaseController
  before_action :set_board, only: %i[edit show update destroy]
  def index
    @q = Board.ransack(params[:q])
    @boards = @q.result(distinct: true).includes(:user).order(created_at: :desc).page(params[:page])
  end

  def edit; end

  def show
    @board = Board.find(params[:id])
    @comment = Comment.new
    @comments = @board.comments.includes(:user).order(created_at: :desc)
  end

  def update
    if @board.update(board_update_params)
      redirect_to admin_board_path(@board), success: '掲示板を更新しました'
    else
      flash.now['danger'] = '編集に失敗しました'
    end
  end

  def destroy
    @board.destroy!
    redirect_to admin_boards_path, success: '掲示板を削除しました'
  end

  private

  def board_update_params
    params.require(:board).permit(:title, :body, :user_id, :board_image, :board_image_cache)
  end

  def set_board
    @board = Board.find(params[:id])
  end
end

掲示板一覧のviewを作成

パーシャルを使い一覧画面を表示させる。

  <%= content_for(:title, t('.title')) %>
 <div class="container mb-5 pt-2">
   <h1>掲示板一覧</h1>
   <div class="row">
     <div class="col-md-12 mb-3">
       <%= render 'search_form' %>
     </div>
   </div>
   <div class="row">
     <div class="col-sm-12">
       <table class="table table-striped">
         <thead>
         <tr>
           <th>ID</th>
           <th>タイトル</th>
           <th>作成者</th>
           <th>作成日時</th>
           <th></th>
         </tr>
         </thead>
         <tbody>
         <%= render @boards %>
         </tbody>
       </table>
     </div>
   </div>
   <div class="row">
     <div class="col-sm-12">
       <!-- ページネーション -->
       <%= paginate @boards %>
     </div>
   </div>
 </div>

検索機能のパーシャル

  <%= search_form_for @q, url: boards_path do |f| %>
  <div class="row">
    <div class="form-inline align-items-center mx-auto">
      <div class="col-auto">
        <%= f.search_field :title_or_body_cont, placeholder:'検索ワード', class:'form-control' %>
      </div>
      <div class="col-auto">
        <%= f.date_field :created_at_gteq, include_blank: true, class:'form-control' %><%= f.date_field :created_at_lteq_end_of_day, include_blank: true, class:'form-control' %>
      </div>
      <div class="col-auto">
        <%= f.submit '検索', class:'btn btn-primary' %>
      </div>
    </div>
  </div>
<% end %>

リンクなどのパーシャル

  <tr>
    <td>
        <%= board.id %>
    </td>
    <td>
        <%= link_to board.title, admin_board_path(board) %>
    </td>
    <td>
        <%= board.user.decorate.full_name %>
    </td>
    <td>
        <%= l board.created_at, format: :long %>
    </td>
    <td>
        <%= link_to '詳細', admin_board_path(board), class:"btn btn-info" %>
        <%= link_to '編集', edit_admin_board_path(board), class:"btn btn-success" %>
        <%= link_to '削除', admin_board_path(board), method: :delete, data: { confirm: '本当に削除しますか?' }, class:"btn btn-danger" %>
    </td>
</tr>

編集画面のview

form_withの書き方
<%= form_with model: @board, url: admin_boards_path, local: true do |f| %>とURLを指定しなくても、簡潔に書ける。
<%= form_with model: [:admin, @board], local: true do |f| %>
form_withの引数に[:admin, @board]を渡すことで、
/admin/boardsというurlをRailsが生成してくれる。

  <% content_for(:title, @board.title) %>
<div class="container">
  <div class="row">
    <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
       <h1><%= t('.title') %></h1>
        <%= form_with model: [:admin, @board], local: true do |f| %>

        <%= render 'shared/error_messages', object: f.object %>
        <div class="form-group">
          <%= f.label :title, Board.human_attribute_name(:title) %>
          <%= f.text_field :title, class: 'form-control' %>
        </div>

        <div class="form-group">
          <%= f.label :body, Board.human_attribute_name(:body) %>
          <%= f.text_area :body, class: 'form-control' %>
        </div>

        <div class="form-group">
          <%= f.label :board_image %>
          <%= f.file_field :board_image, onchange: 'previewImage()', class: 'form-control mb-3', accept: 'image/*' %>
          <%= f.hidden_field :board_image_cache %>
        </div>
        <div class='mt-3 mb-3'>
          <%= image_tag @board.board_image.url, id: 'preview', size: '300x200' %>
        </div>
          <%= f.submit class: 'btn btn-primary' %>
      <% end %>
    </div>
  </div>
</div>

f:id:meo2:20210712004504p:plain:w400:h250

詳細画面のview

<%= @board.user.decorate.full_name %>
user_decorator.rbfull_nameメソッドを呼び出し、ユーザー名を表示する。
<%= l @board.created_at, format: :long %>
lメソッドはi18nにおいて日付や時刻を表す際に使用する。
config/locales/ja.ymlに書かれた内容を読み込む。
formatオプションを使うことで複数の書式を使い分けることができる。

  <% content_for(:title, @board.title) %>
<div class="container">
  <div class="row">
    <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
     <h1><%= t('.title') %></h1>
      <div class="text-right mb-3">
        <%= link_to '編集', edit_admin_board_path(@board), class:"btn btn-success" %>
        <%= link_to '削除', admin_board_path(@board), method: :delete, data: { confirm: '本当に削除しますか?' }, class:"btn btn-danger" %>
      </div>
      <table class="table table-bordered bg-white">
        <tbody><tr>
          <th scope="row">ID</th>
          <td><%= @board.id %></td>
        </tr>
        <tr>
          <th scope="row">タイトル</th>
          <td><%= @board.title %></td>
        </tr>
        <tr>
          <th scope="row">作成者</th>
          <td><%= @board.user.decorate.full_name %></td>
        </tr>
        <tr>
          <th scope="row">本文</th>
          <td><%= @board.body %></td>
        </tr>
        <tr>
          <th scope="row">作成日時</th>
          <td><%= l @board.created_at, format: :long %></td>
        </tr>
      </tbody></table>
    </div>
  </div>
</div>

f:id:meo2:20210712004521p:plain:w500:h250

ユーザー一覧画面のviewを作成

一覧画面と同じくパーシャルを使用する。

  <% content_for(:title, t('.title')) %>
 <div class="container mb-5 pt-2">
   <h1>ユーザー一覧</h1>
   <div class="row">
     <div class="col-md-12 mb-3">
       <%= render 'search_form' %>
     </div>
   </div>
   <div class="row">
     <div class="col-md-12">
       <table class="table table-striped">
         <thead>
         <tr>
           <th>ID</th>
           <th>氏名</th>
           <th>権限</th>
           <th>日時</th>
           <th></th>
         </tr>
         </thead>
         <tbody>
         <%= render @users %>
         </tbody>
       </table>
     </div>
   </div>
   <div class="row">
     <div class="col-md-12">
       <!-- ページネーション -->
       <%= paginate @users %>
     </div>
   </div>
</div>

検索機能のパーシャル

  <%= search_form_for @q, url: admin_users_path do |f| %>
    <div class="row">
        <div class="form-inline align-items-center mx-auto">
            <div class="col-auto">
              <%= f.search_field :first_name_or_last_name_cont, placeholder:'検索ワード', class:'form-control' %>
            </div>
            <div class="col-auto">
              <%= f.select :role_eq, User.roles_i18n.invert.map { |key, value| [key, User.roles[value]]}, include_blank:'指定なし', class:'form-control mr-1' %>
            </div>
            <div class="col-auto">
              <%= f.submit '検索', class:'btn btn-primary' %>
            </div>
        </div>
    </div>
<% end %>

リンクなどのパーシャル

  <tr>
    <td>
        <%= user.id %>
    </td>
    <td>
        <%= link_to user.decorate.full_name, admin_user_path(user) %>
    </td>
    <td>
        <%= user.role_i18n %>
    </td>
    <td>
        <%= l user.created_at, format: :long %>
    </td>
    <td>
        <%= link_to '詳細', admin_user_path(user), class:"btn btn-info" %>
        <%= link_to '編集', edit_admin_user_path(user), class:"btn btn-success" %>
        <%= link_to '削除', admin_user_path(user), method: :delete, data: { confirm: '本当に削除しますか?' }, class:"btn btn-danger" %>
    </td>
</tr>

編集画面のview

  <% content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
       <h1><%= t('.title') %></h1>
      <%= form_with model: [:admin, @user], local: true do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

        <div class="form-group">
          <%= f.label :email %>
          <%= f.email_field :email, class: 'form-control' %>
        </div>

        <div class="form-group">
          <%= f.label :last_name %>
          <%= f.text_field :last_name, class: 'form-control'%>
        </div>
       <div class="form-group">
          <%= f.label :first_name %>
          <%= f.text_field :first_name, class: 'form-control' %>
        </div>

        <div class="form-group">
          <%= f.label :avatar %>
          <%= f.file_field :avatar, onchange: 'previewImage()', class: 'form-control mb-3', accept: 'image/*' %>
          <%= f.hidden_field :avatar_cache %>
        </div>

        <div class='mt-3 mb-3'>
          <%= image_tag @user.avatar.url, class: 'rounded-circle', id: 'preview', size: '100x100' %>
        </div>
        <div class="form-group">
          <%= f.label :role %>
          <%= f.select :role, User.roles_i18n.invert, {}, class: 'form-control' %>
        </div>
        <%= f.submit class: 'btn btn-primary' %>
      <% end %>
    </div>
  </div>
</div>

f:id:meo2:20210712010953p:plain:w400:h400

詳細画面のview

  <% content_for(:title, t('.title')) %>
<div class="container">
  <div class="row">
    <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
      <h1><%= t('.title') %></h1>
      <div class="text-right mb-3">
        <%= link_to '編集', edit_admin_user_path(@user), class:"btn btn-success" %>
        <%= link_to '削除', admin_user_path(@user), method: :delete, data: { confirm: '本当に削除しますか?' }, class:"btn btn-danger" %>
      </div>
      <table class="table table-bordered bg-white">
        <tbody>
        <tr>
          <th scope="row">ID</th>
          <td><%= @user.id %></td>
        </tr>
        <tr>
          <th scope="row">権限</th>
          <td>
            <%= @user.role_i18n %>
          </td>
        </tr>
        <tr>
          <th scope="row">氏名</th>
          <td><%= @user.decorate.full_name %></td>
        </tr>
        <tr>
          <th scope="row">アバター</th>
          <td><img src="/assets/sample-08ca6735af47bfefce4c43e3de2182768289121aa057f141b7f4704a89670827.jpg"></td>
        </tr>
        <tr>
          <th scope="row">作成日時</th>
          <td><%= l @user.created_at, format: :long %></td>
        </tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

f:id:meo2:20210712011030p:plain:w400:h400

i18nの設定

  admin:
     users:
      index:
       title: 'ユーザー一覧'
      new:
       title: 'ユーザー登録'
      show:
       title: 'ユーザー詳細'
      edit:
       title: 'ユーザー編集'
     boards:
      index:
       title: '掲示板一覧'
      show:
       title: '掲示板詳細'
      edit:
       title: '掲示板編集'
     dashboards:
       index:
         title: 'ダッシュボード'