ユーザ機能について 記録2

# ユーザ機能一覧
ユーザに関する機能は簡単なものは以下の通りになります
* ユーザの一覧
* ユーザの詳細
* ユーザの新規登録
* ユーザの情報編集
* ユーザのプロフィール画像編集
* ユーザの画像編集
* ユーザのログイン機能
* ユーザのログアウト機能
* ユーザ登録時のログイン
* ログイン状態での機能
    * ログインしているユーザ名の表示
    * ログインしていない場合のアクセス制限
    * ログインしている場合のアクセス制限
    * ユーザの編集を制限する
* パスワードのハッシュ化 


　初めにユーザのデータを保存できるようにデータベースにusers テーブルを用意し、それを操作するためのUserモデルを用意する。**rails g model** を用意します。
## ユーザの一覧
ユーザ一覧の表示させるには次のことを意識します
* ルーティングにuserモデルのindexアクションを定義する
* ユーザデータを全て取得して変数に格納する
* 取得したユーザデータをeachメソッドを用いて表示する
## ユーザの詳細
ユーザの詳細を表示させるには次のことを意識します
* ルーティングに特定ユーザの詳細画面へ遷移するshowアクションを定義する
* showアクション内でURLに記載されたidを受け取ってfind_byメソッドでユーザデータを変数に格納
* link_toメソッドを用いてユーザ一覧の名前に特定のユーザの詳細画面へのリンクを貼る

## ユーザの新規登録
ユーザの新規登録を実装するには次のことを意識します
* ルーティングでユーザ登録ページを表示するnewアクションを定義する
* ユーザー登録ページの入力フォーム等の作成
* ヘッダーにユーザ新規登録へのリンクを貼る
## ユーザのデータ保存
* ルーティングでcreateアクションを定義する、URLはsignupを使用
* form_tagメソッドで入力したデータの送信先を指定
* < input>にname属性をつける
* createアクション内で＠userにnewメソッドで新しく作ったuserインスタンスへハッシュのキーで指定したユーザ名とemailを代入する
* データを保存後、上記の＠postのidを利用してユーザ詳細ページへリダイレクトする
* ユーザの詳細ページへリダイレクト後フラッシュでサクセスメッセージを表示
* [失敗時]
* 再度、新規登録画面を表示する
* エラー文を表示する
* < input>タグに初期値を入れてrenderで直前に入力したデータのまま@userのデータをフォームに格納する

## ユーザの編集機能
* ルーティングでeditアクションを定義する　URLには編集するユーザidを入れる
* ユーザ詳細画面から特定のユーザの編集画面へと移動できるようにlink_toメソッドでリンクを作成する
* ユーザ編集ページでは現在のユーザ名とメールアドレスを初期値として格納しておく、その際にフォームに初期値を格納したいのでvalueに代入する
## 変更情報の保存
* ルーティングでupdateアクションを定義する　
* 変数に特定のidのユーザデータをfind_byメソッドを使用し、データを取得する
* その変数のユーザ名メールアドレスに、ハッシュキーを利用して送信されたデータを代入する
* 保存できればサクセスメッセージを表示し、保存できなければrenderで再度編集ページへリダイレクトされる

## プロフィール画像を表示させる
ユーザのプロフィール画像の変更をするには次のことを意識します

### usersテーブルにimage_nameカラムを用意
* マイグレーションファイルのみを作成　（**Userモデルは既にあるので、rails g migrationでファイルのみ作成する**）
* マイグレーションファイルのchangeアクション内を編集する　（**カラムを追加する場合はadd_column :テーブル名, :カラム名, :データ型と記述する**）
* マイグレーションファイルの内容をデータベースに反映させる　（**rails db:migrateデータベースに変更を反映させる**）
### 初期画像の設定
* ユーザ登録時に初期画像が保存されるように、userモデルのcreateアクション内でimage_nameにデフォルト画像を代入する
* ユーザ画像を表示させるためには、< img>タグを使用して、user_imagesフォルダ内のファイル名を指定します。
* **ユーザ一覧画面にユーザ画像を表示するにはeachメソッドの繰り返し処理を利用して全てのユーザへ画像を渡していきます。**
### ユーザ画像の編集
* ユーザ画像を送信できるようにフォームをユーザ編集ページに用意する
* 画像の送信は特殊なので、**form_tagに{multipart: true}を追加する必要がある**
* updateアクションでファイル名をデータベースに保存し、フォルダ内に画像を作成します。**ファイルの作成にはFileクラスのwriteメソッドを使います。File.write(ファイルの場所, ファイルの中身)とすることでファイルを作成できます。**
* 送信された画像はprams[:image]で受け取ることができるのでupdateアクション内で一時的に変数imageに代入する（**変数imageはここで初めて出てきましたが、一時的に送信された画像データを代入し、その後画像データを作成する際にimageに対して画像データのフォーマットへ変換をしますf**）
* **画像ファイルの作成はFile.binwriteメソッドを用います。また、変数imageに対し、readメソッドを用いることでその画像データを取得できます**
* 画像を保存する処理は、画像データが送信された場合だけにしたいのでif文で条件分岐させる。

 ## ユーザのログイン機能
ユーザのログイン機能を実現するには次のことを意識します
* ルーティングでlogin_formアクションを呼び出せるようにする
* ログイン用ページを用意し、パスワード用のフォームを追加する。
* usersテーブルにパスワードカラムを追加する
    * マイグレーションファイルのみを作成　（**Userモデルは既にあるので、rails g migrationでファイルのみ作成する**）
    * マイグレーションファイルのchangeアクション内を編集する　（**カラムを追加する場合はadd_column :テーブル名, :カラム名, :データ型と記述する**）
    * マイグレーションファイルの内容をデータベースに反映させる　（**rails db:migrateでデータベースに変更を反映させる**）
    * ユーザ情報には必ずパスワードの値が必要なのでuserモデルでpasswordカラムに対してバリデーションをかける。（空値を禁止するのでpresenceを使用する）
* 入力内容をコントローラ側に送信できるようにルーティングにloginアクションの呼び出しを設定する。
* form_tagメソッドを用いてフォームの送信先を指定し、また、フォームに入力された値がRails側に送信されるように、inputタグにname属性を追加する
### 送信されたメールアドレスとパスワードでユーザを特定する
* 送信された値は変数paramsで受け取ることができます。
* 送信された値に合致するユーザが存在した場合と存在しなかった場合の条件分岐を追加する
* 存在しなかった場合は、loginアクション内で受け取った値を変数に代入して、フォームに初期値となるようにする
### 特定したユーザでログインする
* **ページを移動してもユーザー情報を保持し続けられるsessionという変数を用いる。値を代入するときはuser_idをキーとして値を代入する**

## ユーザのログアウト機能
ユーザのログアウト機能を実現するには次のことを意識します
* ルーティングでlogoutアクションを呼び出すように設定する。**sessionの値を変更するときもルーティングではpostを使用する**
* logoutアクション内で変数sessionのuser_idの値を削除する為、nilを代入する
* ログアウト後はredirect_toメソッドを用いて、ログインページへリダイレクトする。
* ログイン中ではヘッダーのログインページへのリンク先などは必要なく、ログアウト時は新規投稿や投稿一覧へのリンク先を見れないようにしたいので、if文でsession[user_id]の値が等しいかどうかで条件分岐を作る

## ユーザ登録時のログイン
* ユーザ登録フォームにパスワードの入力欄を用意し、createアクション内のnewメソッドの引数としてpasswordを追加する。
* ユーザ登録成功時にログイン状態となるようにif文内でsession[:user_id]に作成したユーザidを代入する

## アクション側で共通の変数を定義
　各コントローラの全アクションで共通の処理がある場合はapplication.controllerで全アクションで共通する処理を書いて、before_actionで使用するアクションを呼び出すと便利になります。（**application.html.erbでアクション側の変数を使う場合は、各アクションに対応したビューファイルが全アクションから呼び出されて<%= yield %>部分に代入される為、全アクションで定義する必要がある**）


## ログインしているユーザ名の表示
　application.controllerで定義した＠current_userを使用して現在のユーザ名を取得し、link_toメソッドを用いて各current_user.idのリンク先も指定する

## ログインしていない場合のアクセス制限
* applicationコントローラにauthenticate_userというメソッドを作成しアクセス制限の処理を共通化する
　authenticate_userメソッドでは現在ログインしているユーザが存在するかどうかをif文で確認する。もしcurrent_userがnil(空値)だったらログインが必要とフラッシュメッセージを表示し、ログインフォームへとリダイレクトさせる。
* before_actionでauthenticate_userメソッドに対してonlyを用いることで、指定したアクションでしかメソッドは適用されないようにできます。
* ログイン中のみアクセスを許可したいアクション
    * 投稿一覧画面(index)
    * 投稿詳細画面(show)
    * 編集画面、編集保存(edit,update) 
## ログインしている場合のアクセス制限
* applicationコントローラにforbid_login_userメソッドを作成しアクセス制限の処理を共通化する
* forbid_login_userメソッドでは現在ログインしているユーザが存在しているかどうかをif文で確認し、current_userが存在する場合は投稿一覧ページにリダイレクトするようにする。
* メソッドの実行にはbefore_actionを用い、onlyで適用したいアクションを指定します
* ログイン中にアクセスを許可しないアクション
    *　トップページへのアクセス（homeモデルのtop)
    *　アカウントの新規登録(new,create)
    *　アカウントへのログイン(login_form,login) 

## ユーザの編集を制限する
* ユーザの詳細ページで、ログインしているユーザと詳細ページのユーザidが等しい時のみ編集ページへのリンクを表示するif文を作る(ビュー側の対応)
* URLに直接ログインすると編集ページに入ることができるのでアクション側でも対応する必要がある。(アクション側の対応)
    * ensure_correct_userメソッドを用意し、ログイン中のユーザーのidと編集したいユーザーのidが等しいか判定する。
    * **ログイン中のユーザーのidは@current_user.idに、編集したいユーザーのidはparams[:id]にそれぞれ代入されているが、paramsで取得できるのは文字列なのでto_iメソッドで数値に変換する必要がある。**

# 投稿とユーザーを紐づける
投稿とユーザを紐づけると次のようなことができます。
* 投稿一覧で各投稿を作成したユーザ名が表示される
* ユーザ詳細ページでそのユーザの投稿が一覧で表示される
* いいね機能
    * 既にログインユーザがいいねしたデータがある場合はいいね済みと表示 
    * 各投稿の詳細画面からクリックして投稿をいいねできる
    * 再度クリックでいいねを取り消すことができる
    * 投稿ごとにいいね数が集計されて投稿の詳細画面で表示される
    * ユーザ詳細画面でそのユーザがいいねした投稿一覧が表示される
 


### postsテーブルにuser_idカラムを用意
* postsテーブルにuser_idのカラムを用意してどのユーザーがその投稿を作成したかわかるようにする。
* postsテーブルにuser_idカラムを追加する
    * マイグレーションファイルのみを作成　（**Postsモデルは既にあるので、rails g migrationでファイルのみ作成する**）
    * マイグレーションファイルのchangeアクション内を編集する　（**カラムを追加する場合はadd_column :テーブル名, :カラム名, :データ型と記述する**）
    * マイグレーションファイルの内容をデータベースに反映させる　（**rails db:migrateでデータベースに変更を反映させる**）
    * 投稿には必ずuser_idの値が必要なのでpostsモデルでuser_idカラムに対してバリデーションをかける。（空値を禁止するのでpresenceを使用する）

### 新規投稿時とログインユーザの紐付け
　新規投稿作成時にuser_idの値を入れて保存したいので、Postsモデルのcreatekアクション内でuser_idに@current_user.idを代入することで自動で紐付けされます。
### 投稿にユーザ情報を表示
　投稿に紐づかれてるuser_idからshowアクション内でユーザ情報を取得します。その後定義した@userを用いてユーザ画像とユーザ名を表示します。また、ユーザ名にはlink_toメソッドを用いてユーザの詳細ページへのリンクを貼ります。

## 複数の投稿を取得するには
　find_byメソッドではその条件に合致するデータを1件しか取得できないので、条件に合致する複数の投稿を取得することはできません。しかし、**whereメソッドを用いると、ある条件に合致する複数のデータを取得できます。**

### インスタンスメソッド
postモデル内で定義したインスタンスメソッドを用いることでpostインスタンスに対して使うことができます。ですので上記のwhereメソッドをインスタンスメソッド（postメソッド）で用いてユーザに紐づく複数の投稿データを取得します。

## 詳細画面で複数投稿を表示
　詳細画面で全ての投稿を表示するには、特定ユーザの情報が格納されている＠userにpostsメソッド（インスタンスメソッド）を用いることで取得できます。
　さらに詳細画面のビューでeachメソッドを用いて全ての投稿データを取り出すことで繰り返し処理で投稿データを表示させることができます。

## いいね機能
* どのユーザがどの投稿にいいねしたか保存するためlikesテーブルを作成する
    * likeモデルはないので、likeモデルとマイグレーションファイルを用意します。なのでコマンドは**rails generete model テーブル名 カラム名:データ型**を用います。
    * いいねのデータは、user_idとpost_idの両方が常に存在していないと不完全なデータとなるので、バリデーション(空値禁止)を追加します。
* 既にログインユーザがいいねしたデータがある場合はいいね済みと表示させる
    * find_byメソッドを用いて、user_idとpost_idが合致するデータがlikesテーブルに存在するかif文で判断する
* いいね機能の実装
    * like_controllerファイルを手動で用意する。
    * ユーザがいいねした際にuser_idとpost_idをlikesテーブルに保存しないといけないのでルーティングでcreateアクションを用意し、likesコントローラ内でcreateアクションを定義します。
    * createアクションではnewメソッドを用いてデータを保存するためのインスタンスを作成します。このとき、**user_idは現在ログインしている@current_user.idから取得し、post_idはルーティングのURL内の:post_idから受け取ったparams[:post_id]から取得し、それぞれ代入します。**
    * 保存が成功した場合はcreateビューは用意していませんので、redirect_toメソッドを用いて投稿詳細ページへリダイレクトさせます。
    * また、createアクションへのリンクを[いいねしていません]に設定します。このときlikesテーブルにデータを追加するためpostが使われていますので、{method:"post"}を指定します。
* いいね取り消し機能
    * current_userがその投稿をいいねしたデータを削除することがいいねを取り消すことになるので、ルーティングにdestroyアクションの呼び出しを設定し、likesコントローラ内でdestroyアクションを定義する。
    * find_byメソッドでいいねデータを取得する為、current_userとルーティングのURLから受け取ったparams[:post_id]からuser:idとpost_idを利用します。
    * その後destroyメソッドを用いて削除した後は、投稿詳細画面へリダイレクトします。
    * destroyアクションはいいね済みを押した時に実行させたいので、link_toメソッドを用いて「いいね済み」にリンクを指定します。
* いいねボタンをアイコンにする
    * ハートアイコンを表示するHTMLは
    ```html
    <span class="fa fa-heart"><span>
    ```
　アイコンにcreate,destroyアクションへのリンクを貼りたいが、HTML要素をそのままlink_toメソッド内に引数として使うことはできないので次の書き方をする
```rb
<%= link_to("URL") do %>
  <span class="fa fa-heart"><span>
<% end %>
```
* いいねの数を取得
    * countメソッドを用いて、likesテーブルから条件に合ったいいね件数を取得します。whereメソッドと同時に使えるので**Like.where(post_id:1).count**のように使います。（post_idが1の投稿データの件数を取得しなさい。）
* ユーザ詳細画面でそのユーザがいいねした投稿一覧が表示される
    * ルーティングでいいねした一覧を表示させるlikesアクションの呼び出しを指定します。この時どのユーザに関する情報かを表示させるので**get "users/:id/likes" => "users#likes"と記述します**。これまでlikeコントローラにいいねに関することは定義しましたが、ユーザ詳細画面にいいね一覧を載せたいので、userコントローラにlikesアクションを定義します。 
    * likesアクション内でfind_byメソッドでユーザ情報を、whereメソッドでいいねした投稿のデータ全てを取得します。
    * likesアクションに対応するビュー内でeachメソッドを利用していいねした全ての投稿データ@likesをlikeに代入して繰り返し処理を行う
    * eachメソッドの繰り返し処理内で、全投稿データに対してfind_byメソッドでいいね済みデータに紐づかれているpost_idと等しい投稿データの取得を行いpostに代入します（ここが一番言葉にするのが難しい💦）
    * その後代入されたpostを用いていいね済み一覧の表示を行います。


## パスワードのハッシュ化
　パスワードは暗号化しないままサービス内でデータを保存すると他人にデータベースを参照された際に全てのアカウント情報が盗まれて、他人のアカウントに不正にログインできてしまいます。ですのでパスワードは普通の文字列としてデータの受け渡しを行うのではなく、参照されても元の文字列を割り出すのが実質不可能なハッシュ値に置き換えておくことが大切です。
　
### gemfileの編集
　gemとは、RubyやRailsでプログラミングをする際に「よく使う機能」をパッケージ化したもので、**ハッシュ化するgemはbcryptというもの**になります。
  使用するには**Gemfile内にgem 'bcrypt'という1行を追加し、ターミナルでbundle installというコマンドを実行します**。
### バスワードをハッシュ化していく
　bcryptをインストールすると、has_secure_passwordというメソッドが使えるようになるので、パスワードを扱うuserモデルに追加する。
　データベースのpasswordカラムを削除してハッシュ値を保存するpassword_digestカラムを追加したいのでマイグレーションファイルを作成し、編集します。**カラムを追加するのはadd_column,カラムを削除するのはremove_column**になります。編集後データベースに反映させます。
　その後authenticateメソッドを使用して、フォームに入力された値をハッシュ化し、password_digestの値と等しいかを判定します。

## 参考文献
[Progate](https://prog-8.com/)