8c. ProgateでRuby on RailsⅨ~Ⅺ(備忘録)

ここでは、ユーザに紐付く投稿を取得し、いいね機能を実装し、パスワードを暗号化していきます。

Ruby on Rails5 学習コースⅨ

・postsテーブルにuser_idカラムを追加する

$ rails g migration add_user_id_to_posts

# (YYYYMMDDHHMMHH_add_user_id_to_posts.rb)
def change
	add_column :posts, :user_id, :integer
end

$ rails db:migrate

・user_idのバリデーション

# (models/post.rb)
	validates :user_id, {presence: true}

・投稿したユーザのidを保存する

# (posts_controller.rb)
def create
	@post = Post.new(
		content: params[:content],
		user_id: @current_user.id					# 投稿したユーザidを保存
	)
		:
end

・user_idからユーザ情報を取得する

# (posts_controller.rb)
def show
	@post = Post.find_by(id: params[:id])
	@user = User.find_by(id: @post.user_id)		# @post.user_idの値からユーザ情報を取得
end

・ユーザ画像とユーザ名を表示する

# (posts/show.html.erb)
	<img src="<%= "/user_images/#{@user.image_name}" %>">		# ユーザ画像
	<%= link_to(@user.name, "/users/#{@user.id}") %>			# ユーザ名(リンク)

・モデルにインスタンスメソッドを定義する

# (models/post.rb)
def user
	return User.find_by(id: self.user_id)	# インスタンスメソッド内で、selfはそのインスタンス自身を指す
end

$ rails c
> post = Post.find_by(id: 1)			# Postモデルのインスタンスを取得する
> post.user							# 変数postに対してuserメソッドを呼び出す
id: 1,								# 投稿に紐付いているユーザ情報が表示される
name: "にんじゃわんこ",
	:
password: "wanko"

・ユーザに紐付く複数の投稿を取得する

$ rails c
> posts = Post.where(user_id: 1)		# whereメソッドで複数のデータを取得

# (models/user.rb)
def posts
	return Post.where(user_id: self.id)
end

$ rails c
> user = User.find_by(id: 1)
> user.posts								# 変数userに対してpostsメソッドを呼び出す
# ユーザに紐付いている複数の投稿情報が表示される

# (users/show.html.erb)
    <% @user.posts.each do |post| %>				# ユーザに紐付いている複数の投稿を1つづつ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 %>

・投稿者だけが編集できるようにする

# (posts/show.html.erb)
# 投稿を作成したユーザidとログインしているユーザidが等しいことを確認
<% 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)
before_action :ensure_corrent_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

・完成版

# (YYYYMMDDHHMMHH_add_user_id_to_posts.rb)
def change
	add_column :posts, :user_id, :integer
end

# (models/post.rb)
  validates :content, {presence: true, length: {maximum: 140}}
  validates :user_id, {presence: true}									# 追加
  
  def user
    return User.find_by(id: self.user_id)								# 追加
  end

# (models/user.rb)
  validates :name, {presence: true}
  validates :email, {presence: true, uniqueness: true}
  validates :password, {presence: true}
  
  def posts
    return Post.where(user_id: self.id)									# 追加
  end

# (posts_controller.rb)
  before_action :authenticate_user
  before_action :ensure_correct_user, {only: [:edit, :update, :destroy]}	# 追加
  
  def index
    @posts = Post.all.order(created_at: :desc)
  end
  
  def show
    @post = Post.find_by(id: params[:id])
    @user = @post.user								# 追加
  end
  
  def new
    @post = Post.new
  end
  
  def create
    @post = Post.new(
      content: params[:content],
      user_id: @current_user.id									# 追加
    )
    if @post.save
      flash[:notice] = "投稿を作成しました"
      redirect_to("/posts/index")
    else
      render("posts/new")
    end
  end
  
  def edit
    @post = Post.find_by(id: params[:id])
  end
  
  def update
    @post = Post.find_by(id: params[:id])
    @post.content = params[:content]
    if @post.save
      flash[:notice] = "投稿を編集しました"
      redirect_to("/posts/index")
    else
      render("posts/edit")
    end
  end
  
  def destroy
    @post = Post.find_by(id: params[:id])
    @post.destroy
    flash[:notice] = "投稿を削除しました"
    redirect_to("/posts/index")
  end
  
  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

# (posts/show.html.erb)
      <div class="post-user-name">
        <img src="<%= "/user_images/#{@user.image_name}" %>">					# 追加
        <%= link_to(@user.name, "/users/#{@user.id}") %>						# 追加
      </div>
      <p>
        <%= @post.content %>
      </p>
      <div class="post-time">
        <%= @post.created_at %>
      </div>
      <% if @post.user_id == @current_user.id %>							# 追加
        <div class="post-menus">
          <%= link_to("編集", "/posts/#{@post.id}/edit") %>
          <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %>
        </div>
      <% end %>

# (posts/index.html.erb)
    <% @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 %>

# (users/show.html.erb)
    <div class="user">
      <img src="<%= "/user_images/#{@user.image_name}" %>">
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <% if @user.id == @current_user.id %>
        <%= link_to("編集", "/users/#{@user.id}/edit") %>
      <% end %>
    </div>
    
    <% @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 %>


・確認すること

  • 投稿詳細ページで、ユーザ名のリンクが表示されていて、ユーザ詳細ページへ画面遷移する
  • ユーザ詳細ページにそのユーザが出品したアイテムのみ一覧表示されている
  • 投稿者のみ編集/削除ボタンが表示されていて、実行できる
  • 投稿者以外は編集/削除ができない


Ruby on Rails5 学習コースⅩ

・いいね機能の実装

# likesテーブルを作成するマイグレーションファイルを作成
$ rails g model Like user_id:integer post_id:integer
$ rails db:migrate										# DBに変更を反映

# (models/like.rb)
	validates :user_id, {presence: true}
	validates :post_id, {presence: true}

$ 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) %>
	いいね!済み
<% else %>
	いいね!していません
<% end %>

・いいねボタンの設置

# (routes.rb)
	post "likes/:post_id/create" => "likes#create"

# (likes_controller.rb)
	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

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

・いいね取り消しボタンの設置

# (routes.rb)
	post "likes/:post_id/destroy" => "likes#destroy"

# (likes_controller.rb)
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

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

・ボタンをアイコンに変更

# (layouts/application.html.erb)
<head>
		:
	# Font Awesomeの読み込み
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>

# (posts/show.html.erb)
<%= link_to("/likes/#{@post.id}/create", {method: "post"}) do %>
	# 文字列として認識されるのを避ける為、HTML要素は以下に記述する
	<span class="fa fa-heart like-btn"></span>
<% end %>

・いいねの数を取得する

$ rails c
> Like.all.count							# likesテーブルの全データの数
> Like.where(post_id: 1).count			# likesテーブルのpost_idが1の数

# (posts_controller.rb)
  def show
    @post = Post.find_by(id: params[:id])
    @user = @post.user
    @likes_count = Like.where(post_id: @post.id).count		# 変数@likes_countを定義
  end

# (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)
	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 %>

・完成版

# (models/like.rb)
  validates :user_id, {presence: true}
  validates :post_id, {presence: true}

# (routes.rb)
  post "likes/:post_id/create" => "likes#create"		# 追加
  post "likes/:post_id/destroy" => "likes#destroy"	# 追加

  post "users/:id/update" => "users#update"
  get "users/:id/edit" => "users#edit"
  post "users/create" => "users#create"
  get "signup" => "users#new"
  get "users/index" => "users#index"
  get "users/:id" => "users#show"
  post "login" => "users#login"
  post "logout" => "users#logout"
  get "login" => "users#login_form"
  get "users/:id/likes" => "users#likes"				# 追加

  get "posts/index" => "posts#index"
  get "posts/new" => "posts#new"
  get "posts/:id" => "posts#show"
  post "posts/create" => "posts#create"
  get "posts/:id/edit" => "posts#edit"
  post "posts/:id/update" => "posts#update"
  post "posts/:id/destroy" => "posts#destroy"

  get "/" => "home#top"
  get "about" => "home#about"

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

# (posts_controller.rb)
  def show
    @post = Post.find_by(id: params[:id])
    @user = @post.user
    @likes_count = Like.where(post_id: @post.id).count		# 追加
  end

# (likes_controller.rb)
  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

# (layouts/application.html.erb)
  <head>
    <title>TweetApp</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">	# 追加
  </head>

# (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 %>
      <%= @likes_count %>
      <% if @post.user_id == @current_user.id %>
        <div class="post-menus">
          <%= link_to("編集", "/posts/#{@post.id}/edit") %>
          <%= link_to("削除", "/posts/#{@post.id}/destroy", {method: "post"}) %>
        </div>
      <% 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)
    <div class="user">
      <img src="<%= "/user_images/#{@user.image_name}" %>">
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <% if @user.id == @current_user.id %>
        <%= link_to("編集", "/users/#{@user.id}/edit") %>
      <% end %>
    </div>
    
    <ul class="user-tabs">
      <li><%= link_to("投稿", "/users/#{@user.id}") %></li>
      <li class="active"><%= link_to("いいね!", "/users/#{@user.id}/likes") %></li>
    </ul>
    
    <% @likes.each do |like| %>
      <% post = Post.find_by(id: like.post_id) %>
      
      <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 %>


・確認すること

  • 投稿詳細ページでいいねボタンといいねの数が表示されている
  • いいねボタンをクリックすると、いいね又はいいね解除ができる(色が変わる)
  • いいね一覧タブが表示されていて、画面遷移できる


Ruby on Rails5 学習コースⅪ

・bcryptのインストール

# (Gemfile)
gem 'bcrypt'				# 暗号化する為のgem

$ bundle install			# 上記で記載したgemをインストール

$ rails s -b $IP -p $PORT		# 「bundle install」後は、railsサーバを「ctrl+c」で停止した後、起動する

# (models/user.rb)
  has_secure_password			# bcryptをインストールすることで暗号化するメソッドを使用できる
  
  validates :name, {presence: true}
  validates :email, {presence: true, uniqueness: true}
  # passwordカラムのバリデーションは削除しておく(has_secure_passwordメソッドが自動的にチェック)
  
  def posts
    return Post.where(user_id: self.id)
  end

・password_digestカラムの追加

$ rails g migration change_users_columns

# YYYYMMDDHHMMSS_change_users_columns.rb
def change
	add_column :users, :password_digest, :string		# password_digestカラムの追加
	remove_column :users, :password, :string				# passwordカラムの削除
end

$ rails db:migrate

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

# (users_controller.rb)
def login
	@user = User.find_by(email: params[:email])
	# authenticateメソッドを使って、「送信されたメールアドレスと一致するユーザー」のpassword_digestと、送信されたパスワードが一致するかを確認
	if @user && @user.authenticate(params[:password])
		:
	end
end


・確認すること

  • 「rails c」にて、パスワードが暗号化されて保存されることを確認
  • > user = User.find_by(id: 1)
    > user.password = “password”
    > user.save

  • 問題なくログインできる