ProgateでRuby on Railsを復習(その5)

Ruby
スポンサーリンク

最後に「いいね」機能の追加、パスワードの暗号化を実装します。

スポンサーリンク

投稿とユーザの紐付け

いいね機能を実現するため、まずは投稿とユーザの情報を紐付けします。

最初に rails g migrationコマンドでファイルを作成→追加カラムを記述→DB反映+バリテーションの設定までを実施します。

rails g migration add_user_id

 

def change
  add_column :posts, :user_id, :integer
end

 

validates :user_id, {presence: true}

 

新規投稿時(posts#create)のアクションを変更し、ユーザIDを保存できるようにします。

@post = Post.new(content: params[:content])
↓
@post = Post.new(content: params[:content], user_id: @current_user.id)

 

投稿情報とユーザ情報を紐付けるため、Postモデルに下記関数を追加します。

def user
  return User.find_by(id: self.user_id)
end

つづけて、postsコントローラ側で以下のように記述することで、投稿詳細画面のビュー(show.html.erb)でも@userとして投稿に紐付くユーザ情報にアクセスできるようになります。(↓のような感じ)

def show
  @post = Post.find_by(id: params[:id])
  @user = @post.user # 投稿に紐付くユーザ情報を取得する(Postモデルで定義した関数)
end

 

データの紐付けができたので、投稿詳細のビュー(posts/show.html.rb)でユーザ名と画像を表示します。

<%= link_to(@user.name, "/users/#{@user.id}") %>

投稿一覧画面でも同様にユーザ名と画像を表示します。
投稿一覧の方は@postsと複数系になっていることから分かるように配列なので、ビュー側のループの途中で user 変数をセットします。

<% @posts.each do |post| %>
  <% user = post.user %>
  <%= link_to( user.name, "/users/#{user.id}" ) %>
  <%= link_to(post.content, "/posts/#{post.id}") %>
<% end %>

ここまで出来たら、ユーザ詳細画面を編集し、該当ユーザの投稿一覧を表示できるようにします。

まずはusersコントローラ側で紐付く投稿一覧を取得して表示します。

def show
  @user = User.find_by(id: params[:id])
  @posts = Post.where(user_id: @user.id)
end

つづけて、ユーザ詳細画面のビュー(users/show.html.erb)に投稿一覧を表示します。(内容自体はposts/index.html.erbと同じなのでコピペ)

自分の投稿のみ編集・削除できるようにアクセス制限をかける

今のままだと自分以外の投稿データも編集・削除できてしまうので、アクセス制限をかけます。

まずはビュー側でリンクを自分の投稿以外は非表示にします。

<% if @current_user.id == @user.id %>
  <%= link_to("編集", "/posts/#{@post.id}/edit") %>
  <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %>
<% end %>

つづけて、直接URL指定にてアクセスされた場合も制限するため、postsコントローラに以下を追記します。

before_action :ensure_correct_user, {only: [:edit, :update, :destroy]}

def ensure_correct_user
  @post = Post.find_by(id: params[:id])
  if @current_user.id != @post.user_id
    flash[:notice] = "権限がありません"
    redirect_to("/posts/index")
  end
end

 

いいね機能の作成

まずはLikeモデルを作成します。(保持する情報は、ユーザIDとポストIDの2つ)

rails g model Like user_id:integer post_id:integer

rails db:migrate

Likeモデルにバリデーションを設定しておきます。

validates :user_id, {presence: true}
validates :post_id, {presence: true}

つづけて、railsコマンドでlikesコントローラをマルッと作成します。

rails g controller likes create

ルーティングを追加します。

post "likes/:post_id/create" => "likes#create"
post "likes/:post_id/destroy" => "likes#destroy"

アクションではpostメソッドでcreateとdestroyを作成します。

post "likes/:post_id/create" => "likes#create"
post "likes/:post_id/destroy" => "likes#destroy"

また、いいね機能はログインしていることが前提なので、likesコントローラにログインチェックをかけておきます。

before_action :authenticate_user

投稿詳細ビュー(posts/show.html.erb)に「いいね」、「いいね取消」を追加します。

<% if Like.find_by(user_id: @user.id, post_id: @post.id) %>
  <%= link_to("いいね取消", "/likes/#{@post.id}/destroy", {method: "post"}) %>
<% else %>
  <%= link_to("いいね", "/likes/#{@post.id}/create", {method: "post"}) %>
<% end %>

 

コントローラ側でいいね(create)といいね取消(destroy)を実装します。

def create
  @like = Like.new(user_id: @current_user.id, post_id: params[:post_id])
  @like.save
  redirect_to("/posts/#{params[:post_id]}")
end

def destroy
  @like = Like.find_by(user_id: @current_user.id, post_id: params[:post_id])
  @like.destroy
  redirect_to("/posts/#{params[:post_id]}")
end

ここまでで、ひとまず機能の動作が出来あがったので、ちょっと見た目を良くするため「いいね」をハートの絵文字(FontAwsome)に変更します。

allication.html.erbのheadタグで以下を読み込みます。

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">

ビューを書き換えます。
ポイントは、link_to()でHTML自体をリンク文字列に指定する場合は、「link_to(“url”) do HTML」という形で指定する必要がある点です。
具体的には以下のようになります。

      <% if Like.find_by(user_id: @current_user.id, post_id: @post.id) %>
        <%= link_to("/likes/#{@post.id}/destroy", {method: "post"}) do %>
          <span class="fa fa-heart like-btn-unlike"></span>
        <% end %>
      <% else %>
        <%= link_to("/likes/#{@post.id}/create", {method: "post"}) do %>
          <span class="fa fa-heart like-btn"></span>
        <% end %>
      <% end %>
      <%= Like.where(post_id: @post.id).count %>

CSSで「いいね」と「いいねの取消」で文字色を変えます。

.like-btn {
  color: #8899a6;
}

.like-btn-unlike {
  color: #ff2581;
}

 

ユーザ詳細画面に「いいね」した投稿一覧を表示する

ユーザ詳細画面で自分が「いいね」した投稿一覧を表示できるように、ビュー(users/show.html.erb)にリンクを追加します。

<%= link_to("いいね!", "/users/#{@user.id}/likes") %>

ルーティングを追加します。

get "users/:id/likes" => "users#likes"

users#likesアクションを作成します。

def likes
  @user = User.find_by(id: params[:id])
  @likes = Like.where(user_id: @user.id)
end

 

いいね記事一覧のビュー(likes.html.erb)を作成します。

<% @likes.each do |like| %>
  <% post = Post.find_by(id: like.post_id) %>
  <% user = User.find_by(id: post.user_id) %>
  <%= link_to( user.name, "/users/#{user.id}" ) %>
  <%= link_to(post.content, "/posts/#{post.id}") %>
<% end %>

 

パスワードの暗号化

今はユーザのパスワードがデータベース上に平文で保存されているので、暗号化した状態で保管するように修正します。

まずはbcryptというモジュールを組み込むため、Gemfileに「gem ‘bcrypt’」を記述して「bundle install」コマンドを実行します。

モジュールの組み込みができたので、Userモデルに以下1行を追加し、暗号化を有効にします。

has_secure_password

rails g migration add_password_digestを実行し、マイグレーションファイルに以下を記述します。
ここでのポイントは、password_digestカラムを追加し、既存のpasswordカラムを削除する点です。

def change
  add_column :users, :password_digest, :string
  remove_column :users, :password, :string
end

 

ここまでで一旦コンソールから確認します。

rails console
user = User.find_by(id: 1)
user.password = "hogehoge"
user.save

これで、usersテーブルのpassword_digestカラムに暗号化された文字列が格納されます。利用する側からするとpasswordカラムに設定しているにも関わらず、password_digestカラムに格納されます。(なんだか不思議ですが・・・)

データベース側の準備ができたので、暗号化されたパスワードでログインできるように処理を変更します。
といってもログイン時のチェックのみです。作成と変更はコンソールで確認したように従来通りpasswordカラムを設定すれば勝手に暗号化されて格納されます。

@user = User.find_by(email: params[:email], password: params[:password])
if @user

@user = User.find_by(email: params[:email])
if @user.authenticate(params[:password])

と変更します。

has_secure_passwordメソッドを有効化することで、authenticateメソッドが使えるようになります。このメソッドにより、引数に渡した文字列が暗号化され、DBに登録されているpassword_digestと比較してくれるようになります。(仕組みがよく分からないけど・・・)

ここまででRuby on Railsコースはひと通り完了です。
これを何度か繰り返してスラスラできるようになれば、次のステップにGOですかね。。。

 

コメント