Railsを学ぶ③ ~投稿とユーザーの紐づけ~

はじめに

Railsの学習記録です。

※前回の記録

Railsを学ぶ② ~画像投稿&ユーザー機能追加~

Railsを学ぶ ~環境構築とCRUD実装~

※Rubyに関しては以下で記録

Rubyを学ぶ

✅参考

Progate:Rails学習コース

書籍:現場Rails

投稿とユーザーの紐づけ

ProgateⅨの内容

①postsテーブルにuser_idカラムを追加して紐づけ

rails g migration add_user_id_to_posts

マイグレーションファイルの編集

class AddUserIdPosts < ActiveRecord::Migration[5.2]
  def change
    add_column :posts, :user_id, :integer
  end
end

マイグレートでカラム追加

rails db:migrate

Postモデルへバリデーションを追加

validates :user_id, {presence:true}

②投稿をログインユーザーへ紐づける

postコントローラのcreateアクションのnewメソッド引数にプロパティ追加。

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

🙄current_userはapplicationコントローラで定義しているログインユーザーの情報が諸々入ったオブジェクトですね!

③誰の投稿かわかるようにビューを変更

postコントローラのshowアクション内で変数を定義

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

🙄post変数のuser_idカラムを参照してUserモデルから取り出し定義!※この後さらにリファクタリングします。

post/show.html.slimの編集

.ly
  img src="/user_images/#{@user.image_name}"
  = link_to(@user.name, "/users/#{@user.id}")

表示されることを確認

④モデルにインスタンスメソッドを定義

※投稿に紐づくユーザーの情報を簡単に使えるようにメソッドを定義する。

postモデル内にインスタンスメソッドを定義
→postインスタンスで使うことができる。

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

※selfはインスタンス自身を指す

確認

post = Post.find_by(id: 9)
post.user
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? 
LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "sato", email: "sato@example.com", created_at: "2020-07-02 08:39:39", updated_at: "2020-07-04 12:44:58", image_name: "1.jpg", password: "testtest">

postコントローラを書き換える

# @user = User.find_by(id: @post.user_id)
@user = @post.user

🙄Postモデルのユーザーidでusersテーブルのidカラムを探してますね。

投稿一覧にもユーザー名を表示するため、ビューの変更

index.html.slim

.ly
  - @post.each do |post|
    .display-block
      img src="/user_images/#{post.user.image_name}"
      span = link_to(post.user.name, "/users/#{post.user.id}")
      span = link_to(post.content, "/post/#{post.id}")

確認

※備忘録:ここでuser_idがnilだったりするとエラーになったのでデータの整理をしました。

⑤ユーザー詳細ページに投稿を表示

find_byメソッドは条件に合致するデータ1件のみの取得なので、複数のデータを取得するためにwhereメソッドを用いる。

post = Post.where(user_id: 1)

UserモデルにPostインスタンスを戻り値として返すpostsメソッドを定義

def posts
  return Post.where(user_id: self.id)
end

※これでuserインスタンスで作られたインスタンス変数でpostsメソッドが利用できる。

users/show.html.slimに以下追記

.ly
  h2 投稿一覧
  - @user.posts.each do |post|
    .display-block
      img src="/user_images/#{post.user.image_name}"
      p = link_to(post.user.name, "/users/#{post.user.id}")
      p = link_to(post.content, "/post/#{post.id}")

※erbの場合

<% @user.posts.each do |post| %> 
  <div class="posts-index-item"> 
    <div class="post-left"> 
      <img src="<%= "/user_images/#{post.user.image_name}" %>"> 
    </div> 
    <div class="post-right"> 
      <div class="post-user-name"> 
        <%= link_to(post.user.name, "/users/#{post.user.id}") %> 
      </div> 
      <%= link_to(post.content, "/posts/#{post.id}") %> 
    </div> 
  </div> 
<% end %>

🙄コードの量がだいぶ変わってしまうのでerbとslimは頭の切り替えが必要な印象。。。

確認

⑥投稿者のみ編集できるようにする

リンクの非表示

posts/show.html.erbの編集

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

posts_controller.rbにensure_corrent_userメソッドを定義

before_action :ensure_correct_user, {only: [:edit, :update, :destroy]}
# 中略
def ensure_correct_user
  @post = Post.find_by(id: params[:id])
  if @post.user_id != @current_user.id
    flash[:notice] = "権限がありません"
    redirect_to("/posts/index")
  end
end

※ここでローカル環境ではエラー出るようになってしまったので作業ステイ(備忘録)。

モデルにメソッド定義して、self活用しだすあたりの理解があいまい。

いいね機能の実装

ProgateⅩの内容 非同期ではなくリロードで実装している。また詳細ページでのみの実装。

※以下は実際にローカルで作ったわけではありません。

いいね機能のためのlikesテーブルを作成

rails g model Like user_id:integer post_id:integer
// user_id,post_idカラム
rails db:migrate

バリデーション追加

validates :user_id, {presence: true}
validates :post_id, {presence: true}
# validates :user_id ,:post_id ,{presence: true} # 1行で書くことも可能

テーブルにデータ挿入

rails c
> like = Like.new(user_id: 1,post_id:2)
> like.save

posts/show.html.erbで投稿にいいねしたかを表示

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

いいねボタンの作成※ビューは不要のためrails gは用いない!!

touch app/controllers/likes_controller.rb

Likeデータを作成するためのcreateアクション、削除のためのdestroyアクションを作成

class LikesController < ApplicationController
 
 before_action :authenticate_user
 
 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
end

routes.rbの編集

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

Font Awesomeの利用

<head>タグで読み込み

HTML要素をリンク化する場合。posts/show.html/erb

<% 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 %>

いいねの数取得

rails c
> Like.all.count
=> 3
> Like.where(post_id: 1).count
=> 2

posts_controller.rbのshowアクションに@likes_count変数を定義

@likes_count = Like.where(post_id: @post.id).count

posts/show.html.erbに追記

<%= @likes_count %>

いいねした投稿の一覧表示

routes.rbに追記

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

users_controller.rb

def likes
  @user = User.find_by(id: params[:id])
  @likes = Like.where(user_id: @user.id) # @userに紐付くLikeデータを取得
end

users/show.html.erbの編集

<ul class="user-tabs">
  <li class="active"><%= link_to("投稿", "/users/#{@user.id}") %></li>
  <li><%= link_to("いいね!", "/users/#{@user.id}/likes") %></li>
</ul>

users/likes.html.erb

※コードだいぶ割愛してます

<% @likes.each do |like| %>
  <% post = Post.find_by(id: like.post_id) %>
<% end %>

パスワードの取り扱い

ProgateⅪの内容

パスワードを普通の文字列でDB上に保管してしまうと、ハッキングでデータを盗まれた場合などのリスクが大きいので、安全性を高める必要がある。

パスワードの暗号化にbcryptというパッケージをgemを活用してインストールする。※以下Gemfileを編集

gem 'bcrypt'

インストール

bundle install

Userモデルを編集

has_secure_password
※パスワードをpassword_digestカラムに保存してくれる。

※bcryptをインストールすることで上記メソッドを使えるようになる。

マイグレーションファイルの作成

rails g migration change_users_columns

複数カラムの変更

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

※remove_columnでパスワードのカラムを取り消し

データベースの変更を反映

rails db:migrate

password_digestカラムに暗号化されたパスワードを保存するためには、今まで通りpasswordカラムに値を代入して保存。自動でpassword_digestカラムに保存される。

暗号化されたパスワードを用いたログイン

has_secure_passwordメソッドを有効にするとauthenticateメソッドが使えるようになる。

authenticateメソッドは引数を暗号化しpassword_digestの値と一致するか判定する。

users_controller.rb

def login
  @user = User.find_by(email: params[:email])
  if @user && @user.authenticate(params[:password])
  ~~~~
  end
end

 

おわりに

Progateの実装も、後半になるにつれてだいぶ繰り返しの実装になるように感じた。

テスト含め基本的な部分をもうちょっと固めたいと感じたので明日は書籍もしくはRailsチュートリアルを用いて学習していきたいと思います。

コメントを残す