最後に「いいね」機能の追加、パスワードの暗号化を実装します。
投稿とユーザの紐付け
いいね機能を実現するため、まずは投稿とユーザの情報を紐付けします。
最初に 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ですかね。。。
コメント