掲示板にコメント機能を付与する方法
掲示板にコメント機能を付与する方法
モデルの関係は以下のようになっている
[コメントモデルの作成]
$ rails generate model comment body:text
↓
作成されたマイグレーションファイルにNOT NULL制約を追加
[db/migrate ファイル] def change create_table :comments do |t| t.text :body, null:false t.timestamps end end
↓
$ rails db:migrate
↓
$ rails generate migration AddForeignToComments
↓
上記で作成したマイグレーションファイルに外部キーを付ける
[db/migrate ファイル] def up add_reference :comments, :user, foreign_key: true add_reference :comments, :board, foreign_key: true end def down remove_reference :comments, :user remove_reference :comments, :board end
↓
$ rails db:migrate
上記のCommentsテーブルの作成と外部キーの付与の工程は、Commentsテーブルを作成する時に以下のコマンドと手順でも可能。
$ rails generate model comment body:text user:references board:references
↓
$ raild db:migrate
↓
下記のようにコメントのbody属性にNOT NULL制約を付与する。
[db/migrate ファイル] def change create_table :comments do |t| t.text :body, null: false t.references :user, foreign_key: true t.references :board, foreign_key: true t.timestamps end end
[アソシエーションの設定]
[app/models/user.rb] has_many :boards, dependent: :destroy has_many :comments, dependent: :destroy
[app/models/board.rb] belongs_to :user has_many :comments, dependent: :destroy
[app/models/comment.rb] belongs_to :user belongs_to :board
[ルーティングの設定]
ルーティングをネスト化する事によって、モデルの親子関係をルーティングで表せる。
ルーティングのネスト化の記載方法と生成されるURLは、以下のようになる。
[config/routes.rb] resources :boards do resources :comments end
[生成されるパスとそれぞれのアクション]
HTTPメソッド | パス | コントローラ#アクショ |
---|---|---|
GET | /boards/:board_id/comments | comments#index |
GET | /boards/:board_id/comments/new | comments#new |
POST | /boards/:board_id/comments | comments#create |
GET | /boards/:board_id/comments/:id | comments#show |
GET | /boards/:board_id/comments/:id/edit | comments#edit |
PATCH/PUT | /boards/:board_id/comments/:id | comments#update |
DELETE | /boards/:board_id/comments/:id | comments#destroy |
※上記のパスの[/boards/:board_id/comments]の[:board_id]は、boardに格納されているidの事。
上記の表からも分かる通り、全部のアクションのパスに[:board_id]が入っている。commentsコントローラのshowアクション、editアクション、updateアクション、destroyアクションにもパスに[:board_id]が入ってしまっているが、これらは[:board_id]情報が必要ない。 ルーティングをネスト化する際には、ネスト化をやりすぎると「深い」ネストになってしまい分かりにくくなってしまう為、ネスト化をするものは必要最低限に留め「浅い」ネストにするのが好ましい。 浅いネスト化のルーティングは、以下のように記載できる。
[config/routes.rb] resources :articles do resources :comments, only: [:index, :new, :create] end resources :comments, only: [:show, :edit, :update, :destroy]
また、上記と同じ内容を以下のように記載することもできる。
[config/routes.rb] resources :articles do resources :comments, shallow: true end
また必要なアクションに限定して以下のように記載することもできる。
[config/routes.rb] resources :boards, only: %i[index new create show] do resources :comments, only: %i[create], shallow: true end
[コントローラの設定]
Commentsコントローラを作成
$ rails generate controller comments
↓
commentを保存する際に、外部キー[user_id,board_id]を格納するのを忘れないようにする。
[app/controllers/comments_controller.rb] def create @comment = current_user.comments.new(comment_params) if @comment.save redirect_to board_url(@comment.board_id), success: (t '.success') else redirect_to board_url(@comment.board_id), danger: (t '.fail') end end private def comment_params params.require(:comment).permit(:body).merge(board_id: params[:board_id]) end
↓
掲示板詳細ページのコントローラの設定
掲示板の詳細ページからコメントフォーム欄とコメントエリアのビューをrenderしているので、render先のビューで使うインスタンス変数も忘れないように記載する。
[app/controllers/boards_controller.rb] def show @board = Board.find_by(id: params[:id]) @comment = Comment.new @comments = @board.comments.includes(:user).order(created_at: :desc) end
↓
掲示板詳細のビューとrenderしているビュー
[app/views/boards/show.html.erb] <div class="container pt-5"> <div class="row mb-3"> <div class="col-lg-8 offset-lg-2"> <h1>掲示板詳細</h1> <!-- 掲示板内容 --> <article class="card"> <div class="card-body"> <div class='row'> <div class='col-md-3'> <%= image_tag @board.image.thumb.url, class: "card-img-top img-fluid", width: "300", height: "200" %> </div> <div class='col-md-9'> <h3 style='display: inline;'><%= @board.title %></h3> <%= render 'crud_menus', { board: @board } %> <ul class="list-inline"> <li class="list-inline-item"><%= @board.user.decorate.full_name %></li> <li class="list-inline-item"><%= l @board.created_at %></li> </ul> </div> </div> <p><%= simple_format(@board.body) %></p> </div> </article> </div> </div> <!-- コメントフォーム --> <%= render partial: 'comments/form', locals: { board: @board, comment: @comment } %> <!-- コメントエリア --> <%= render 'comments/comments', { comments: @comments } %> </div>
以下の編集や削除する際のアイコンは、様々な場所で使うので切り離してrenderで利用できるようにする。
[app/views/boards/_crud_menus.html.erb] <div class='mr10 float-right'> <%= link_to '#', id: 'button-edit-#{board.id}' do %> <%= icon 'fa', 'pen' %> <% end %> <%= link_to '#', id: 'button-delete-#{board.id}', method: :delete, data: {confirm: ''} do %> <%= icon 'fas', 'trash' %> <% end %> </div>
コメントのフォーム欄のビュー
[app/views/comments/_form.html.erb] <div class="row mb-3"> <div class="col-lg-8 offset-lg-2"> <%= form_with model: [@board, @comment], local: true do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="form-group"> <%= f.label :body %> <%= f.text_area :body, class: 'form-control' %> </div> <%= f.submit (t 'defaults.post'), class: 'btn btn-primary' %> <% end %> </div> </div>
コメントエリアのビュー(render commentsでrenderに変数(コメント一覧)を渡していることでrenderしている)
[app/views/comments/_comments.html.erb] <div class="row"> <div class="col-lg-8 offset-lg-2"> <table id="js-table-comment" class="table"> <%= render comments %> </table> </div> </div>
[app/views/comments/_comment.html.erb] <tr id="comment-<%= comment.id %>"> <td style="width: 60px"> <%= image_tag "sample.jpg", class: "rounded-circle", width: "50", height: "50" %> </td> <td> <h3 class="small"><%= comment.user.decorate.full_name %></h3> <div id="js-comment-25"> <%= simple_format(comment.body) %> </div> <div style="display: none;"> <textarea class="form-control mb-1">コメントです</textarea> <button class="btn btn-light">キャンセル</button> <button class="btn btn-success">更新</button> </div> </td> <% if current_user.own?(comment) %> <td class="action"> <ul class="list-inline justify-content-center" style="float: right;"> <li class="list-inline-item"> <a href="#"><i class='fa fa-pencil-alt'></i></a> </li> <li class="list-inline-item"> <a href="#"> <i class="fa fa-trash"></i> </a> </li> </ul> </td> <% end %> </tr>
※注意
コメントエリアのビューを以下のように記載してしまうと、インスタンス変数@commentsでrenderしているため、render対象の[app/views/comments/_comment.html.erb]が何個も生成されるため、1つ1つのコメントエリアに対して、