コメント(投稿・削除)のAjax化について

どうもどハマりエンジニアです。

 

本日は前回のブックマーク機能のAjax化に引き続き、コメント(投稿・削除)のAjax化をしていきたいと思います。

 

①コントローラー

(comments_controller.rb)

def create
  @comment = current_user.comments.build(comment_params)
  @comment.save
 
def destroy
  @comment = current_user.comments.find(params[:id])
  @comment.destroy
end

 

 

②テンプレート

(comments/_form.html.erb) createアクションのための

<%= form_with model: comment, url: [board, comment],
remote: true, id: 'new_comment' do |f| %>
<%= f.label :body%>
<%= f.text_area :body, class: 'form-control mb-3', placeholder: 'コメント',
rows: "4", id: 'js-new-comment-body' %>
<%= f.submit '投稿', class: 'btn btn-primary' %>
<% end %>

・form_withのとろこで、remote:trueをつけること。

・id: 'new_comment' → エラーメッセージを表示させるための

・ id: 'js-new-comment-body' → createの時はidは必要なし

 

(shared/error_messages.html.erb)

<% if object.errors.any? %>
  <div class = 'alert alert-warning', id="error_messages">
    <ul>
      <% object.errors.full_messages.each do |message| %>
      <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

・id='error_messages' → 付け加える!!

 

(comments/create.js.erb)

$("#error_messages").remove()
<% if @comment.errors.present? %>
  $("#new_comment").prepend("<%= j(render('shared/error_messages',
                 object: @comment)) %>")
<% else %>
  $("#js-table-comment").prepend("<%= j(render('comments/comment',
                  comment: @comment)) %>")
  $("#js-new-comment-body").val('')
<% end %>

$("#error_messages").remove() → エラーメッセージが表示された場合に、例えばコメントが作成された場合はエラーメッセージが消えたり、エラーメッセージが再び出た場合は一つ目のエラーメッセージが消え、二つ目のエラーメッセージが表示される。

2段落目からはエラーメッセージがあれば表示させるといった意味合い。prependは1番前に表示させるという意味。

$("#js-new-comment-body").val('') → コメントが作成された場合に、コメントフォームを空白にするためのもの。

 

(comments/_comment.html.erb)

<tr id="comment-<%= comment.id %>">
  ・
       .
  <%= link_to comment_path(comment),
        class: 'js-delete-comment-button',
        method: :delete,
        data: { confirm: '削除してもよろしいですか?' },
        remote: true do %>
        <%= icon 'fa', 'trash' %>
  <% end %>

remote: true を加える

 

(comments/destroy)

$("tr#comment-<%= @comment.id %>").remove()

 

 

以上でコメント(投稿・削除)のAjax化についての実装が完了しました。

次回は編集機能を実装していきます。

 

 

 

ブックマークボタンのajax化について

どうもどハマりエンジニアです。

本日はajaxについて説明してくよ!

 

ajaxとは、Asynchronous JavaScript + XML の略で、非同期通信と呼ばれる通信方法のことを指す。

railsにおいてajax通信を実装する場合二通りある。

  • remote: trueを指定する方法
  • JSファイルに任意のタイミングでajax処理を発火させるように記述を施す方法

 

今回はremote:trueを使い実装していく。

 

<処理の流れ>

リンクボタンをクリックした時、ルーティングを経由してアクションに、remote:trueオプション付きで遷移する。いつもならhtml.erbにいくが、今回はremote:trueに設定しているので、html.erbにいくのではなく、js.erbに向かうことになる。

 

ではここからはブックマークをajax化させて処理を書いていく

 

 

remote:trueに直していく

(_unbookmark.html.erb)

<%= link_to board_bookmarks_path(board.id),
method: :post, remote: true,
id: :"js-bookmark-button-for-board-#{board.id}" do %>
<%= icon 'far', 'star' %>
<% end %>

(_bookmark.html.erb)

<%= link_to board_bookmarks_path(board.id),
method: :delete, remote: true,
id: :"js-bookmark-button-for-board-#{board.id}" do %>
<%= icon 'fas', 'star' %>
<% end %>

まずはremote:trueを追加する。

idは必ず一つのhtmlの中では被らないようにしないといけない!

 

 

ブックマークのコントローラを修正していく

(bookmarks_controller.rb)

class BookmarksController < ApplicationController
def create
@board = Board.find(params[:board_id])
current_user.bookmark(@board)
end

def destroy
@board = current_user.bookmarks.find_by(board_id: params[:board_id]).board
current_user.unbookmark(@board)
end
end

 

ユーザーモデルにメソッドを作ろう!(コントローラーに明示的に何をしているかわかるように)

 

(user.rb)

def bookmark(board)
bookmark_boards << board
end

def unbookmark(board)
bookmark_boards.destroy(board)
end

一つ目のメソッドは、これは「ブックマークをする」という動作を明示的に書くことによって可読性を高めるために作成したメソッド。もし一つ上のコントローラにて別の書き方をするならば、

def create
 board = Board.find(params[:board_id])
 Bookmark.create(user: current_user, board: board)
 # 省略
end

このように書くことが出来るとのこと。

つまりこれは「bookmark_boards << board」はBookmark.create 的な感じかな。難しいよ・・・

 

あとはjs.erbを作っていこう

(views/bookmarks/create.js.erb)

$("#js-bookmark-button-for-board-<%= @board.id %>").
replaceWith("<%= j(render('boards/bookmark', board: @board))%>");

上段はviewで作成したid

下段は入れ替わり後に表示するテンプレート

ちなみに入れ替わり後のテンプレートはこれ

<%= link_to board_bookmarks_path(board.id),
method: :delete, remote: true,
id: :"js-bookmark-button-for-board-#{board.id}" do %>
<%= icon 'fas', 'star' %>
<% end %>

fas,starは色が塗ってある星マーク。

つまり色なしから色ありに変わるということ。

逆にdestroyのコードも作っていこう。

 

(views/bookmarks/destroy.js.erb)

$("#js-bookmark-button-for-board-<%= @board.id %>").
replaceWith("<%= j(render('boards/unbookmark', board: @board)) %>");

これはcreateとほぼ同じである。

 

 

以上でajax処理が出来た!

正直他のパターンを複数やらないとわからないかもしれない・・・

ご視聴ありがとうございました。

 

ブックマーク機能の実装について

どうもどハマりエンジニアです。

 

ブックマーク機能の実装を行っていきます。既にコメント機能まで完成しています。

 

 

 

①bookmarkモデルの作成

 

②bookmarksコントローラの作成。

 

③各モデルに関連付けを行う

 

(bookmark.rb)

class Bookmark < ApplicationRecord
belongs_to :board
belongs_to :user
validates :user_id, uniqueness: { scope: :board_id }
end
validates :user_id, uniqueness: { scope: :board_id }を行うことでuser_idとboard_idの一意性が保たれるため、1人のユーザーは1つの掲示板につき一回しかブックマークが出来なくなる。
 
(board.rb)
has_many :bookmarks, dependent: :destroy
has_many :users, through: :bookmarks
 
def bookmark_by?(user)
bookmarks.where(user_id: user.id).exists?
end

bookmard_by?(user)メソッドはviewのところ、ブックマークをすでに行っているかいないかを確認をするときに使う。

 

(user.rb)

has_many :bookmarks, dependent: :destroy
has_many :bookmark_boards, through: :bookmarks, source: :board

二つ目のhas_manyは既に掲示板とユーザー情報をつなげた時にboardsという名前を使ってしまっているため、適当な名前を新たにつけて上げる必要がある。新たに名前をつけたので、source: :boardをつけてあげることでこれはboardとつながっているよと示すことができる。

 

(bookmarks.rb)マイグレーション

class CreateBookmarks < ActiveRecord::Migration[5.2]
def change
create_table :bookmarks do |t|
t.references :board, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
t.index [:user_id, :board_id], unique: true
end
end
end

t.index [:user_id, :board_id], unique: true

上記は先ほどbookmarkモデルでしたようにデータベースでも一意性を保つために行う。

 

④ルーティングの設定

<routes.rb>

resources :boards, shallow: true do
resources :comments, only: %i[create]
resource :bookmarks, only: %i[create destroy]
get :bookmarks, on: :collection

get :bookmarks, on: :collection

ブックマーク一覧ページをboards/bookmarksにルーティンングを設定したいのでcollectionオプションを使用する。
ネスト(入れ子)すると外部キーのboard_idを取得するのに容易になります。また単数形のメソッドであるresourceとしたのはbookmarkのidが必要ないため。

 

⑤各コントローラーの設定

(bookmarks_controller.rb)

class BookmarksController < ApplicationController
def create
bookmark = current_user.bookmarks.build(board_id: params[:board_id])
bookmark.save!
redirect_to boards_path, success: 'ブックマークしました'
end

def destroy
current_user.bookmarks.find_by(board_id: params[:board_id]).destroy!
redirect_to boards_path, success: 'ブックマークを外しました'
end
end

createアクションでは先ほど、ルーティングでboardにネストをしたおかげでparamsからboard_idをもってくることができ、簡単にコードをかける。

destroyアクションも同様にparamsでboard_idを引っ張ってきて、削除するだけ。

 

(boards_controller.rb)

def bookmarks
@boards = current_user.bookmark_boards.includes(:user).order(created_at: :desc)
end

先ほどルーティングにて、collectionでbookmarksを追加したので、bookmarksアクションを定義していく。

これはブックマークの一覧に繋がっている。そのためログインユーザーがブックマークした情報だけを持ってきたいので、current_user(ログインしているユーザー).bookmark_boards(モデルでhas_manyと書いたおかげでブックマークしている情報を引っ張れる)

includes(:user)はもしかしたらuserだけではなくboardの情報を持ってくる必要があるかも。(n+1問題を解決するために)

 

⑥各viewの設定

(_board.html.erb)

<% if current_user.own?(board) %>
<div class='mr10 float-right'>
<%= link_to icon('fa', 'pen'), edit_board_path(board),
id: "button-edit-#{board.id}" %>
<%= link_to icon('fas', 'trash'), "/boards/#{board.id}",
id: "button-delete-#{board.id}", method: :delete,
data: {confirm: '本当に削除しますか?'} %>
</div>
<% else %>
<div class= "display: inline float: right">
<%= render 'boards/bookmark_area', board: board %>
</div>
<% end %>

前回作成したcurrent_user.own?(board)のelse以下にrenderでブックマークの状況に応じたテンプレートをもってくる。current_user.own?(board)はログインしているユーザーであれば掲示板に編集や削除ボタンがでてくるというもの。今回は逆にログインしているユーザーは自分の掲示板をブックマークすることが出来ないようにするためこのようにelse以下に書いている。

 

(_bookmard_area.html.erb)

<% if board.bookmark_by?(current_user) %>
<%= render 'boards/bookmark', board: board %>
<% else %>
<%= render 'boards/unbookmark', board: board %>
<% end %>

bookmark_by?(current_user)はboardモデルで定義したもので、既にブックマークに登録しているかいないかを判断してくれる。

ックマークに登録していれば'boards/bookmark'、していなければ、'boards/unbookmark'といった感じ。

 

(_bookmark.html.erb)

<%= link_to icon('fas', 'star'), board_bookmarks_path(board.id),
method: :delete, id: :"js-bookmark-button-for-board-#{board.id}" %>

すでにブックマークしてある場合は削除したいのでdelete。

(_bookmard.html.erb)

<%= link_to icon('far', 'star'), board_bookmarks_path(board.id),
method: :post, id: :"js-bookmark-button-for-board-#{board.id}" %>

まだブックマークしてないので、post。

 

(bookmarks.html.erb)ブックマーク一覧

<div class="row">
<div class="col-12">
<div class="row">
<% if @boards.present? %>
<%= render @boards %>
<% else %>
<p><%= 'ブックマーク中の掲示板がありません' %></p>
<% end %>
</div>
</div>
</div>

 

以上でブックマーク機能を追加できました!

多対多になると中間テーブルがでてきて複雑になりがちなので難しい。

最後までご視聴ありがとうございます。

 

動的なタイトル表示について

どうもどハマりエンジニアです。

 

本日は動的なタイトル表示について書いていきます。

 

 

content_forメソッドとは

content_forメソッドは、Railsにデフォルトで用意されているもので、画面毎に異なる内容を呼び出したい場合使う。

 

<application_helper.rb>

module ApplicationHelper
def page_title(page_title = '')
base_title = 'RUNTEQ BOARD APP'

page_title.empty? ? base_title : page_title + ' | ' + base_title
end
end

 

三項演算子を使って、空だった場合は「サイト名」、タイトルがある場合は「タイトル | サイト名」のように表示される。

<application.html.erb>

<title><%= page_title(yield :title ) %></title>

デフォルトのタイトルは、サイト名だけを表示するようにしている。

 

<new.html.erb>

<% content_for(:title, t('.title')) %>

タイトルは、I18nで日本語に変換している。そのまま'タイトル'のように記述しても問題ない。

 

 

page_title.empty? ? base_title : page_title + ' | ' + base_title

上記のように条件式(.empty?) ? trueの処理 : falseの処理と言う記載を使って1行で済ませている。

コード量を減らせるため三項演算子はよく使われますが、分かりづらいと言う方は一旦下の形式で記載して理解しても大丈夫。

if page_title.empty?
  base_title
else
  page_title + " | " + base_title
end

最後まで読んでいただきありがとうございます。

コメント機能の作成について

どうもどハマりエンジニアです。

 

ここからリレーションなども絡んできてかなり初学者(自分)には難しく感じるとろこになりました。ですが気合を入れて頑張っていきましょう!

 

 ではまず最初にイメージ図(ER図)を簡単に手書きでパパッと書いておくとイメージが湧きやすいのではないかと思います。簡単なところは省いていきます!

 

①コメントのモデル作成(Comment)

 

②コメントコントローラの作成(Comments)

 

③各モデルにアソシエーションを追加

<user.rb>

has_many :comments, dependent: :destroy

<board.rb>

has_many :comments, dependent: :destroy

<comment>

belongs_to :board
belongs_to :user

 

④commetのマイグレーションの設定

class CreateComments < ActiveRecord::Migration[5.2]
def change
create_table :comments do |t|
t.text :body, null: false
t.references :board, foreign_key: true
t.references :user, foreign_key: true

t.timestamps
end
end
end

 

⑤ルーティングの設定

resources :boards, only: %i[index new create show] do
resources :comments, only: %i[create], shallow: true
end

shallow: true このオプションを使うことで、浅いネストを作ることができる。

例えば、boards/board_id/comments/comments_id といいたように最後のコメントのidは不要なのでこれを省いて作成してくれる。

 

⑤boards_controller

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

ボードのテンプレートの下記にコメント欄を作りたいので、インスタンスを作成し、

@commentsでコメントの情報とuserの情報を取得する。SQLへの問い合わせが減る。

 

⑥boards/show.html.erbにてrenderでテンプレートをもってくる

<boards/show.html.erb>

<!-- コメントフォーム -->
<%= render 'comments/form', board: @board, comment: @comment %>
<!-- コメントエリア -->
<%= render 'comments/comments', comments: @comments %>

<_form.html.erb>

<%= form_with model: comment, url: [board, comment], local: true do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :body%>
<%= f.text_area :body, class: 'form-control mb-3' %>
<%= f.submit '投稿', class: 'btn btn-primary' %>
<% end %>

urlの送り先が大事!!深く理解出来てないのですが、boardとcomment両方に情報が必要なため。

 

 

ここから難しかった・・・

<boards/show.html.erb>

 
<!-- コメントエリア -->
<%= render 'comments/comments', comments: @comments %>

今回はshowのテンプレートにコメントを入れたい。コメントのパーシャルは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>

 boardとcommentの関係でパーシャルを二段階ふむ必要があるのではないかと考えた。。。 多分・・・

 render comments の複数形でcommentパーシャルをコメントの件数分eachして表示させている。これは、renderでオブジェクトの集合(コレクション)を指定する記法の省略形。

<%= render partial: "comment", collection: @comments %>

# 省略形
<%= render @comments %>

講師に聞いたところ「

上記のコレクションをrenderすると、render先のパーシャルでコレクションを単数系にしたローカル変数が利用できる様になります。

本来であれば自分でeach文を作ってループ変数やローカル変数を定義するといった必要がある部分を、Railsの記法に従うことで省略して利用できる形になります。

こういった一見ブラックボックスとなる挙動は最初は分かりづらいですが、「設定より規約」というRailsの設計思想に従って「規約」として覚えてしまうことで、コードの記述量が減るので実装の効率が良くなります。

Railsの規約を前提とした実装は現場でも良く出てくるので、1つずつ覚えて使いこなしていきましょう。」

とのこと。なるほど、規約として覚えればいいのか。

<_comment.html.erb>

<h3 class="small"><%= comment.user.decorate.full_name %></h3>
<div id="js-comment-<%= comment.id %>">
<p><%= simple_format(comment.body) %></p>

こんな感じでcommentが使えるようになった!

 

⑦comments_controllerにてcreateしていく!

<comments_controller.rb>

class CommentsController < ApplicationController
def create
comment = current_user.comments.build(comment_params)
if comment.save
redirect_to board_path(comment.board), success: 'コメントを作成しました'
else
flash[:danger] = 'コメントを作成できませんでした'
redirect_to board_path(comment.board)
end
end

private

def comment_params
params.require(:comment).permit(:body).merge(board_id: params[:board_id])
end
end

 

上記の手順で一応保存する作業まで完了!

 

なかなか難しいぞ・・・

だが頑張るしかない!

 

 

 

 

 

APIとは何か?

どうもどハマりエンジニアです。

本日はAPIについて簡単にまとめてみました。

 

APIとは「Application Programming Interface」の頭文字である。APIはソフトフェアやアプリケーションなどの一部を外部にむけて公開することにより、第三者が開発したソフトウェアと機能を共有できるようにしてくれるものであり、ソフトウェア同士をつなげてくれるインターフェースである。つまり異なるソフトウェアやサービス間で認証機能を共有したり、チャット機能を共有したり、片方から数値データを取り込み、別のプログラムでそのデータを解析したりできるようになる。

 例えば、岐阜県Wi-fiの使える喫茶店を探すWebサイトを作ろうとする際に、自分で情報を全て集めるのは非常に手間のかかる作業である。しかし、APIを使えば、ぐるなびやホッとペッパーグルメが持っている膨大な店舗データを取得し、私たちのWebサイト上にも掲載することができるのである。

ソフトウェアにAPIという外部とのやりとりを行う窓口を作り外部アプリとコミュニケーションや連携ができる状態にすることをAPIを公開するという。アプリとアプリをつなげることで、機能性を拡張させ、さらに便利に使えるようになるのである。

 

APIの大きなメリット

既にある信頼できるアプリを使えば一からプログラムを組む必要がなくなり、時間短縮が出来れば他の工程に時間を割くことができ、結果的にコストを削減することができる。

フェイスブックGoogleアカウントを使い、それらのアカウントを持っている人であれば、新たにユーザー登録をしなくてもアカウントを作成することができる。また自社でセキュリティの高い会員登録システムを導入するよりも、すでにあるセキュリティの高い会員システムを導入するほうが良い。

またAPIを使えば、それぞれのサービスの最新情報をこちら側が毎回更新しなくても取得、利用することができる。

 

 上記以外にも多くの情報がありましたが、調べだすときりがないのでここまでにします。

Draperの使い方 まとめ

どうもどハマりエンジニアです。

 

本日はDraperの簡単な使い方について説明するよ。

 

 

まずgemをインストール

gem 'draper'

$bundle install

 

そして、 システムにデコレーター層を追加するために下記のコマンドを実行する

$rails g draper:install

 

指定したモデルに対応したデコレーターファイルを作成する

$rails g decorator User

 

今回はUserモデルのfirst_nameとlast_nameをくっつけて表示させるfull_nameというメソッドを作成したいと思う。

app/decorators/user_decorator.rb

class UserDecorator < Draper::Decorator

def full_name
"#{object.first_name}#{object.last_name}"
end
end

delegate_all というのは、Userモデルの全部のメソッドを呼び出せるようにするための記述。既存のメソッドを組み合わせて、新しいことをしたい場合に便利。なので、新しく作成したfull_nameメソッドで、Userモデルの last_nameやfirst_nameメソッドを使用することができるのです。objectメソッドは、デコレートしているモデルを参照するメソッド、と理解している。

<%= current_user.decorate.full_name %>

viewにはcurrent_userメソッドが返すオブジェクトに対してデコレータ層full_nameのメソッドを実行している。

 

以上が今回実行したDraperの簡単な使い方についてでした。