Ryota400’s blog

エンジニアを目指して書いてます。

管理画面 掲示板/ユーザのCRUD機能の作成

メニューのアクティブ・非アクティブ化

app/helpers/application_helper.rb

 def active_if(path)
    path == controller_path ? 'active' : ''
 end

三項演算子を使って、真のときはactive、偽のときは何も返さない('')ようにします。 controller_pathでコントローラー名を取得できる。

app/views/admin/shared/_sidebar.html.erb

 <%= link_to admin_boards_path, class: "nav-link #{active_if('admin/boards')}" do %>

   <%= link_to admin_users_path, class: "nav-link #{active_if('admin/users')}" do %>

ヘルパーメソッドを使って、現在のコントローラーがadmin/boardspathと一致しているときは、activeを返す。 するとclass: "nav-link active"となるので、アクティブになる。

EnumHelp導入

EnumHelpとは、Enumで定義した値を簡単に翻訳できるgem。

gem 'enum_help'
bundle install

config/locales/activerecord/ja.yml

enums:
    user:
      role:
        general: '一般'
        admin: '管理者'

Controller設定

app/controllers/admin/boards_controller.rb

管理者権限の掲示板の設定

class Admin::BoardsController < Admin::BaseController
  before_action :set_board, only: %i[edit update show destroy]

  def index
    # ransackのプルダウン検索実装
    @q = Board.ransack(params[:q])                   
    @boards = @q.result(distinct: true).includes(:user).order(created_at: :desc).page(params[:page])
  end

  def edit; end

  def update
    if @board.update(board_params)
      redirect_to admin_board_path(@board), 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 show; end

  def destroy
    @board.destroy!
    redirect_to admin_boards_path, success: t('defaults.message.deleted', item: Board.model_name.human)
  end

  private

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

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

app/controllers/admin/users_controller.rb

管理者権限ユーザーの設定

class Admin::UsersController < Admin::BaseController
  before_action :set_user, only: %i[edit update show destroy]

  def index
    # ransackのプルダウン検索実装
    @q = User.ransack(params[:q])
    @users = @q.result(distinct: true).order(created_at: :desc).page(params[:page])
  end

  def edit; end

  def update
    if @user.update(user_params)
      redirect_to admin_user_path(@user), success: t('defaults.message.updated', item: User.model_name.human)
    else
      flash.now['danger'] = t('defaults.message.not_updated', item: User.model_name.human)
      render :edit
    end
  end

  def show; end

  def destroy
    @user.destroy!
    redirect_to admin_users_path, success: t('defaults.message.deleted', item: User.model_name.human)
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:email, :last_name, :first_name, :avatar, :avatar_cache, :role)
  end
end

ルーティングの設定

resources :boards, only: %i[index edit update show destroy]
resources :users, only: %i[index edit update show destroy]

Viewの設定

掲示板Viewの設定

app/views/admin/boards/_board.html.erb

<tr>
  <td>
    <%= board.id %>
  </td>
  <td>
    <%= board.title %>
  </td>
  <td>
    <%= board.user.decorate.full_name %>
  </td>
  <td>
    <%= l board.created_at, format: :long %>
  </td>
  <td>
    <%= link_to t('defaults.show'), admin_board_path(board), class: 'btn btn-info' %>
    <%= link_to t('defaults.edit'), edit_admin_board_path(board), class: 'btn btn-success' %>
    <%= link_to t('defaults.delete'), admin_board_path(board), method: :delete, data: { confirm: t('defaults.message.delete_confirm') }, class: 'btn btn-danger' %>
  </td>
</tr>

app/views/admin/boards/_search_form.html.erb

<%= search_form_for @q, url: admin_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, class: 'form-control', placeholder: t('defaults.search_word') %>
      </div>
      <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>
      <div class="col-auto">
        <%= f.submit class: 'btn btn-primary' %>
      </div>
    </div>
  </div>
<% end %>

app/views/admin/boards/edit.html.erb

<% 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: @board, url: admin_board_path(@board), local: true do |f| %>
        <%= render 'shared/error_messages', object: f.object %>
        <div class="form-group">
          <%= f.label :title %>
          <%= f.text_field :title, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :body %>
          <%= f.text_area :body, class: 'form-control', rows: 10 %>
        </div>
        <div class="form-group">
          <%= f.label :board_image %>
          <%= f.file_field :board_image, onchange: 'previewFileWithId(preview)', 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>

app/views/admin/boards/index.html.erb

<% content_for(:title, t('.title')) %>
<div class="container mb-5 pt-2">
  <h1><%= t('.title') %></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 scope="col"><%= Board.human_attribute_name(:id) %></th>
          <th scope="col"><%= Board.human_attribute_name(:title) %></th>
          <th scope="col"><%= Board.human_attribute_name(:user) %></th>
          <th scope="col"><%= Board.human_attribute_name(:created_at) %></th>
          <th scope="col"></th>
        </tr>
        </thead>
        <tbody>
        <%= render @boards %>
        </tbody>
      </table>
    </div>
  </div>
  <div class="row">
    <div class="col-sm-12">
      <!-- ページネーション -->
      <%= paginate @boards %>
    </div>
  </div>
</div>

app/views/admin/boards/show.html.erb

<% 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 t('defaults.edit'), edit_admin_board_path(@board), class: 'btn btn-success' %>
        <%= link_to t('defaults.delete'), admin_board_path(@board), method: :delete, data: { confirm: t('defaults.message.delete_confirm') }, class: 'btn btn-danger' %>
      </div>
      <table class="table table-bordered bg-white">
        <tr>
          <th scope="row"><%= Board.human_attribute_name(:id) %></th>
          <td><%= @board.id %></td>
        </tr>
        <tr>
          <th scope="row"><%= Board.human_attribute_name(:title) %></th>
          <td><%= @board.title %></td>
        </tr>
        <tr>
          <th scope="row"><%= Board.human_attribute_name(:user) %></th>
          <td><%= @board.user.decorate.full_name %></td>
        </tr>
        <tr>
          <th scope="row"><%= Board.human_attribute_name(:body) %></th>
          <td><%= @board.body %></td>
        </tr>
        <tr>
          <th scope="row"><%= Board.human_attribute_name(:created_at) %></th>
          <td><%= l @board.created_at, format: :long %></td>
        </tr>
      </table>
    </div>
  </div>
</div>

・ユーザーViewの設定

app/views/admin/users/_search_form.html.erb

<%= 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, class: 'form-control', placeholder: t('defaults.search_word') %>
      </div>
      <div class="col-auto">
        <%= 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' } %>
      </div>
      <div class="col-auto">
        <%= f.submit class: 'btn btn-primary' %>
      </div>
    </div>
  </div>
<% end %>

app/views/admin/users/_user.html.erb

<tr>
  <td>
    <%= user.id %>
  </td>
  <td>
    <%= user.decorate.full_name %>
  </td>
  <td>
    <%= user.role_i18n %>
  </td>
  <td>
    <%= l user.created_at, format: :long %>
  </td>
  <td>
    <%= link_to t('defaults.show'), admin_user_path(user), class: 'btn btn-info' %>
    <%= link_to t('defaults.edit'), edit_admin_user_path(user), class: 'btn btn-success' %>
    <%= link_to t('defaults.delete'), admin_user_path(user), method: :delete, data: { confirm: t('defaults.message.delete_confirm') }, class: 'btn btn-danger' %>
  </td>
</tr>

app/views/admin/users/edit.html.erb

<% 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: @user, url: admin_user_path(@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: 'previewFileWithId(preview)', class: 'form-control', 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>

app/views/admin/users/index.html.erb

<% content_for(:title, t('.title')) %>
<div class="container mb-5 pt-2">
  <h1><%= t('.title') %></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 scope="col"><%= User.human_attribute_name(:id) %></th>
          <th scope="col"><%= User.human_attribute_name(:full_name) %></th>
          <th scope="col"><%= User.human_attribute_name(:role) %></th>
          <th scope="col"><%= User.human_attribute_name(:created_at) %></th>
          <th scope="col"></th>
        </tr>
        </thead>
        <tbody>
        <%= render @users %>
        </tbody>
      </table>
    </div>
  </div>
  <div class="row">
    <div class="col-md-12">
      <!-- ページネーション -->
      <%= paginate @users %>
    </div>
  </div>
</div>

app/views/admin/users/show.html.erb

<% 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 t('defaults.edit'), edit_admin_user_path(@user), class: 'btn btn-success' %>
        <%= link_to t('defaults.delete'), admin_user_path(@user), method: :delete, data: { confirm: t('defaults.message.delete_confirm') }, class: 'btn btn-danger' %>
      </div>
      <table class="table table-bordered bg-white">
        <tr>
          <th scope="row"><%= User.human_attribute_name(:id) %></th>
          <td><%= @user.id %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:role) %></th>
          <td><%= @user.role_i18n %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:full_name) %></th>
          <td><%= @user.decorate.full_name %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:avatar) %></th>
          <td><%= image_tag @user.avatar.url %></td>
        </tr>
        <tr>
          <th scope="row"><%= User.human_attribute_name(:created_at) %></th>
          <td><%= l @user.created_at, format: :long %></td>
        </tr>
      </table>
    </div>
  </div>
</div>

掲示板一覧画面に日付の検索機能を追加

config/initializers/ransack.rb

Ransack.configure do |config|
  config.add_predicate 'lteq_end_of_day', #設定するpredicateに名前をつける
                       arel_predicate: 'lteq',  #使いたいpredicate
                       formatter: proc { |v| v.end_of_day } # ここでend_of_dayメソッドを実行している
end

end_of_day: もともとあるメソッドで、1日の終わりを23:59:59にする。