管理画面 掲示板/ユーザの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にする。