[Ruby on Rails5]Progate 学習メモ4


引き続きメモ。とっ散らかっている内容は後でまとめます。

▼投稿した内容に記事の内容とユーザIDを保存する

新規投稿作成時に、投稿したユーザの値をいれて保存する。
投稿したユーザーは、現在ログインしているユーザなので、ログインユーザの変数で取得可能。
xxx.controller.rbにて記載。

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

▼ユーザIDからユーザ情報(ユーザ名、アイコン)を取得する
user_idカラムの値から、そのユーザーの情報を取得します。
投稿詳細ページなので、postsコントローラのshowアクション内で、
そのidに該当するユーザーの情報をデータベースから取得する。
xxx.controller.rbにて記載。

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

取得した情報を表示する。
xxx.html.erbにて記載。

<div class="post-user-name">
<img src="<%= "/user_images/#{@user.image_name}" %>">
<%= link_to(@user.name, "/users/#{@user.id}") %>
</div>

▼モデルにインスタントメソッドを定義する
モデルにインスタンスメソッドを定義する。
models/xxx.rbにて記載。

class
def hello(インスタントメソッド)
puts "It is test."
end
end

ターミナルで確認してみる。

$ rails console
> post = Post.find_by(id:1)
> post.hello
It is test.

これを応用してユーザidに紐付いている情報を表示させる。
インスタンスメソッド内で、selfはそのインスタンス自身を表す。
models/xxx.rbにて記載。

class Post < ApplicationRecord
...
def user
return User.find_by(id: self.user_id)
end
end

ターミナルで確認してみる。

$ rails console
> post = Post.find_by(id: 1)
> post.user
id :1
name: "XXX"
password: "YYY"
...

実際にコードに記述してみる。
xxx.controller.rbに記載。

書き換え前

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

書き換え後

@user = @post.user

▼ユーザに紐づく複数のデータを取得する
投稿一覧を表示させるためには、投稿からidを抽出することになる。
投稿は複数に渡るため、1つのデータしか取得できないfind_byメソッドではなく、whereメソッドを利用することになる。
ターミナルで確認してみる。

$ rails console             | 
> post = Post.where(id: 1)  | idの値が「1]であること
> [...] (idが1である投稿の配列) | idが「1」である投稿のすべて)
...
posts[0].content
=> "XXXXX"

▼ユーザの投稿を一覧で表示させる
各投稿を表示させるには、whereメソッドで取得した値は配列に入っているので、ビュー側でeach文を用いて、1つずつ投稿を表示していく。
xxx.html.erbにて記載。

<% @user.posts.each do |post| %>
...
<% end %>

▼2つのテーブルが持つカラムを連携させる
いいね機能は、ユーザ情報を持ったテーブルと投稿情報を持ったテーブルが各々持ったカラムを保持するテーブルにその情報が書き込まれる。

テーブルの追加し、マイグレーションを行う。

rails g model コントロール テーブル名, :カラム名, :データ型
rails db:migrate

model配下のファイルにバリデーションを設定しておく。
両方存在しないと不完全なデータになってしまうため、

validates :1つ目のカラム, {presence: true}
validates :2つ目のカラム, {presence: true}

コンソールで挙動を確認する。
コマンド後、DBが更新されている。

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

▼いいね機能で表示する仕組み
いいねの存在で画面表示を変化させる
「ログインしているユーザーがその投稿にいいねしたデータが存在する」という条件のために、user_idとpost_idが合致するデータがlikesテーブルに存在するかどうか、find_byを用いてチェックします。(find_byは該当するデータが見つからなかった時にnilを返す)
下記では、2つのテーブルのカラムがLikesテーブルに存在するかチェックしている。

<% if Like.find_by(user_id: @current_user.id, post_id: @post.id) %>
いいね!済み
<% else %>
いいね!していません
<% end %>

▼いいねボタンの実装(ボタンを押下するといいねされる)
作成するアクションの追加を行う

likes_contoroller.rb

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

route.rb

rails.application.routes.draw do
...
post "likes/:post_id/create" => "likes#create"
end

上記によって「どの投稿をいいねしたのか」という情報が連携される。
リンクも反映させる。
posts/show.html.erb

<%= link_to("いいね!", "/likes/#{@post.id}/create", {method: "post"}) %>

▼いいねボタンの実装(いいね中にボタンを押下すると取り消される)
likes_contoroller.rb

class LikesController < ApplicationContoroller
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

route.rb

rails.application.routes.draw do
...
post "likes/:post_id/destory" => "likes#destroy"
end

リンクも反映させる。
posts/show.html.erb

<%= link_to("いいね!済み", "/likes/#{@post.id}/destroy", {method: "post"}) %>

▼いいねボタンをハートアイコンにする
「Font Awesome」を利用する際は

タグ内で読み込みをする必要があります。
タグなどの共通のHTMLはapplication.html.erbに書きますので、
今回はそこに読み込み用のタグを追加します。
共通なので、application.html.erbに記載
<!DOCTYPE html>
<html>
<head>
...
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
...
</head>
...

HTML上には下記のように記載。

<span class="fa fa-heart"></span>

xxx.html.erbには下記のように記載。
通常のlink_toメソッド

<%= link_to("表示する文字列", "URL") %>

HTML要素を記述したい場合のlink_toメソッド

<%= link_to("URL") do %>
ここに記載
<% end %>

つまりこうなる

<%= link_to("/likes/#{@post.id}/create", {method: "post"}) do %>
<span class="fa fa-heart like-btn"></span>
<% end %>

いいねがついていないときは

<%= link_to("/likes/#{@post.id}/destroy", {method: "post"}) do %>
<span class="fa fa-heart like-btn-unlike"></span>
<% end %>

後はCSSで色を調整する

▼いいねの件数を表示する
ikesテーブルからデータの件数を取得するには、countメソッドを用います。countメソッドは配列の要素数を取得するメソッドですが、テーブルのデータ数を取得するためにも利用することができます。

$ rails console
> Like.all.count
=> 3
> Like.where(post_id: 1).count
=> 2
  def show
    ...
    @likes_count = Like.where(post_id: @post.id).count
  end

▼いいねの一覧を作成する
route.rbにて

Rails.application.routes.draw do
...
get "users/:id/likes" => "users#likes"
...
end

user_controller.rbにて

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

xxx.html.erbにて

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

▼パスワードを暗号化する
DB上に平文でパスワード管理するのは危険です。
Railsでは暗号化するためにbcryptを使用する。

今回はbcryptという「暗号化するためのgem」を使います。

RailsにはGemfileというファイルがあり、インストールするgemが書かれています。「gem 'bcrypt'」という1行を追加し、ターミナルで「bundle install」というコマンドを実行すると、書かれたgemをインストールすることができます。

gem 'bcrypt'
$ bundle install

bcryptをインストールすると、has_secure_passwordというメソッドが使えるようになります。図のようにパスワードを扱うUserモデルにhas_secure_passwordを追加します。こうすることで、ユーザーを保存する際に自動的にパスワードを暗号化してくれます。

user.rb

class User < ApplicationRecord
has_secure_password
...
end

次にpasswordカラムのバリデーションを削除する

  validates :password, {presence: true}

パスワード管理するにあたり、下記2点の変更を行う
・passwordカラムの削除
・暗号化されたパスワードが格納されるpassword_digestカラムの追加

$ rails g migration change_users_column

マイグレーションファイル(xxx.rb)

  add_column :users, :password_digest, :string
  remove_column :users, :password, :string
$ rails db:migration

password_digestカラムに暗号化されたパスワードを保存するためには、今まで通りpasswordに値を代入します。
こうすることで、has_secure_passwordによってpasswordに代入された値が暗号化され、password_digestカラムに保存されます。
このため、既にあるpasswordに関するコードを変更する必要はありません。

has_secure_passwordメソッドを有効にすると、authenticateメソッドを使えるようになります。authenticateメソッドは渡された引数を暗号化し、password_digestの値と一致するかどうかを判定してくれます。
authenticateメソッドを使って、「送信されたメールアドレスと一致するユーザー」のpassword_digestと、送信されたパスワードが一致するかどうかでログイン処理をします。

まずはユーザの呼び出しにメールアドレスとパスワードにしていたが、メールアドレスのみとし、ユーザの存在確認のif文にパスワード一致をand条件にする。

if @user && @user.authenticate(params[:password])