ProgateでRuby on Railsコースの復習(その4)

Ruby
スポンサーリンク

つづけて、ログイン処理、、、の前にユーザ情報として画像ファイルを表示できるようにします。(これで見た目がグッとそれっぽくなりますね)

ユーザ情報に画像ファイルを紐付ける

まずは画像を保存するための情報として、usersテーブルにimage_nameカラムを追加します。

rails g migration add_image_name

ファイル(db/migrate/20181010071154_add_image_name.rb)が生成されるので、これに追記するカラムの定義を記載して、「rails db:migrate」を実行すればデータベースに反映されます。

def change
  add_column :users, :image_name, :string
end

ユーザに紐付く画像ファイルの仕様は以下の2点とします。
・初期登録時には「default_user.jpg」という初期画像で登録する
・ユーザ登録後にユーザ変更画面から画像を変更可能とする

まずはユーザ登録処理時に画像情報(image_name)を追加します。

@user = User.new(name: params[:name], email: params[:email])
↓
@user = User.new(name: params[:name], email: params[:email], image_name: "default_user.jpg")

 

ユーザ情報の詳細表示画面で、紐付く画像を表示できるようにビューにimgタグを追記します。

<img src="/user_images/<%= @user.image_name %>">

ポイントは、データベースにはファイル名だけ記録し、ファイルの実態は決まったディレクトリに保存しておく、といったところでしょうか。

ユーザ編集画面で画像ファイルをアップロードできるようビューに追記します。
ポイントは、バイナリファイルを扱う時には form_tagのオプションに{multipart: true}を付ける点です。(忘れがち)

<%= form_tag("/users/#{@user.id}/update",{multipart: true}) do %>
  <p>ユーザー名</p>
  <input name="name" value="<%= @user.name %>">
  <p>メールアドレス</p>
  <input name="email" value="<%= @user.email %>">
  <p>画像</p>
  <input type="file" name="image_name">
  <input type="submit" value="保存">
<% end %>

 

つづけて、アクション側(users#update)で画像ファイルの保存処理を追加します。
ここのポイントは、画像ファイルの実体は決まったフォルダ(public/user_images)にユーザID名.jpgという名前で保存する点です。

if params[:image_name]
  @user.image_name = "#{@user.id}.jpg"
  File.binwrite("public/user_images/#{@user.image_name}", params[:image_name].read)
end

ログイン画面の作成

ようやくログイン画面の作成です。
まずはデータベースの準備として、usersテーブルにパスワードの項目を追加します。

画像情報の紐付けと同じように、「rails g migration add_password」で生成したファイルに以下を追記して、「rails db:migrate」で反映します。

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

あわせてパスワードが空欄だと困るので、userモデルにバリデーションを追記します。

validates :password, {presence: true}

ログイン画面へのルーティングを追加します。(/loginでアクセスできるようにします)

get "/login" => "users#login_form"

アクションは特にすること無いので空で作成しておきます。

def login_form
end

ビュー(login_form.html.erb)を作成します。

<% if @error_message %>
  <div class="form-error">
    <%= @error_message %>
  </div>
<% end %>
<%= form_tag("/login") do %>
  <p>メールアドレス</p>
  <input type="text" name="email" value="<%= @email %>">
  <p>パスワード</p>
  <input type="password" name="password" value="<%= @password %>">
  <input type="submit" value="ログイン">
<% end %>

共通レイアウトのヘッダー部分にログイン画面へのリンクを追加します。

<li>
  <%= link_to("ログイン", "/login") %>
</li>

ログイン処理の作成

ログイン処理のルーティングを追加します。ログイン画面と同じ”/login”で重複しそうに思いますが、こっちはPOSTメソッドなので重複しません。

post "/login" => "users#login"

アクションを作成します。emailとpasswordで検索し、レコードがあれば認証成功として投稿一覧画面へ移動します。
ポイントはsessionという特別な変数にユーザIDをセットして保持しておく点でしょうか。この変数はログアウトするまで持ち続けます。
また、失敗した場合、再度ログイン画面を表示しますが、入力内容を保持するためパラメータで受け取った値を変数に格納しておきます。

def login
  @user = User.find_by(email: params[:email], password: params[:password])
  if @user
    session[:user_id] = @user.id
    flash[:notice] = "ログインしました"
    redirect_to("/posts/index")
  else
    @email = params[:email]
    @password = params[:password]
    @error_message = "メールアドレスまたはパスワードが間違っています"
    render("users/login_form")
  end
end

 

ユーザ登録時の処理を変更

登録時にパスワードの入力が無かったので、ユーザ登録ビュー(users/new.html.erb)にパスワード入力欄を追加します。

<p>パスワード</p>
<input type="password" name="password" value="<%= @user.password %>">

ユーザ登録処理(users#create)にパスワードの登録を追加し、あわせてログイン状態(session変数をセット)にしておきます。

def create
  @user = User.new(name: params[:name], email: params[:email], image_name: "default_user.jpg", password: params[:password])
  if @user.save
    flash[:notice] = "ユーザー登録が完了しました"
    session[:user_id] = @user.id # セッション情報をセット
    redirect_to("/posts/index") # 投稿一覧画面に遷移
  else
    render("users/new")
  end
end

ログアウト機能の作成

まずはルーティングを追加します。

post "/logout" => "users#logout"

共通レイアウトのヘッダー部分にログアウトのリンクを追加します。(ここは画面を介さないのでPOSTメソッドでのリンクとします)

<li>
  <%= link_to("ログアウト", "/logout", {method: "post"}) %>
</li>

アクションを作成します。やることはsession変数をnilにしてあげるだけです。

def logout
  session[:user_id] = nil
  flash[:notice] = "ログアウトしました"
  redirect_to("/login")
end

ログイン有無でヘッダ表示の切り替え

ヘッダに表示されるリンクですが、今のままだとログイン前に必要な機能と、ログイン後に必要な機能がゴチャッと表示されているので、ログイン前と後とで表示する項目を整理します。
共通レイアウトビュー(application.html.erb)を編集し、セッション情報の有無でヘッダの表示内容を切り替えます。

<% if session[:user_id] %>
  <li>
    現在ログインしているユーザーのid: <%= session[:user_id] %>
  </li>
  <li>
    <%= link_to("投稿一覧", "/posts/index") %>
  </li>
  <li>
    <%= link_to("新規投稿", "/posts/new") %>
  </li>
  <li>
    <%= link_to("ユーザー一覧", "/users/index") %>
  </li>
  <li>
    <%= link_to("ログアウト", "/logout", {method: "post"}) %>
  </li>
<% else %>
  <li>
    <%= link_to("TweetAppとは", "/about") %>
  </li>
  <li>
    <%= link_to("新規登録", "/signup") %>
  </li>
  <li>
    <%= link_to("ログイン", "/login") %>
  </li>
<% end %>

ログイン機能の改良

ヘッダにユーザIDを表示している部分を、ユーザ名を表示するように改良し、あわせてユーザ詳細画面へのリンクにします。

アプリケーション全体に関係するアクションなので、application_controller.rbに追記します。
ポイントは before_action で指定したアクションは、各アクションの実行前に動くので、各アクション共通で利用する@current_user変数をここで定義しています。

before_action :get_current_user

def get_current_user
  @current_user = User.find_by(id: session[:user_id])
end

 

現在ログインしているユーザーのid: <%= session[:user_id] %>
↓
<%= link_to(@current_user.name, "/users/#{@current_user.id}") %>

ログインしていない場合にアクセス制限をかける

現状だと、URLさえ分かればログインしていなくても全ての機能にアクセス可能な状態となっているので、ログイン前の状態ではアクセスできないよう制限をかけます。
ログインチェックの関数は全アクションで共通なので、これもapplication_controller.rbに作成します。

def authenticate_user 
  if session[:user_id] == nil #ログインしていない場合
    flash[:notice] = "ログインが必要です" #フラッシュを表示
    redirect_to("/login") #ログイン画面にリダイレクト
  end
end

 

つづけて、postsコントローラの全アクション、usersコントローラのユーザ登録以外(index, show, edit, update)にてbefore_actionでさっきのログインチェック関数を呼びます。

before_action :authenticate_user # 全アクションで実行
before_action :authenticate_user, {only: [:index, :show, :edit, :update]} #指定アクションだけ実行

ログインしている場合にアクセス制限をかける

既にログインしている場合、ログイン画面やユーザ登録画面は不要なのでアクセスできないようにします。
まずは、さっきと同じようにapplication_controller.rbにチェック用の関数を作成します。

def is_login?
  if @current_user 
    flash[:notice] = "すでにログインしています"
    redirect_to("/posts/index")
  end
end

usersコントローラにて以下を追記して制限をかけます。

before_action :is_login?, {only: [:new, :login_form, :login, :create]}

あわせてhomeコントローラにてTOPページへのアクセスもログイン後は不要ということで制限しておきます。

before_action :is_login?, {only: :top}

自分以外のユーザ情報を変更できないようにする

現状は自分以外のユーザについてもユーザ情報の変更ができてしまうので、自分の情報のみ変更できるようにアクセス制限をかけます。
まずはユーザ詳細ビュー(users/show.html.erb)で自分の画面のみ「編集」リンクを表示できるようにします。

<% if @current_user.id == @user.id %>
  <%= link_to("編集", "/users/#{@user.id}/edit") %>
<% end %>

これだけだとURL(/users/1/editとか)に直接アクセス可能なままなので、こちらもアクセス制限をかけます。

関係するのはユーザ情報だけなので、usersコントローラに以下を追記します。

ポイントは、@current_user.id が integer型で、params[:id]がstring型なので、比較のため、params[:id].to_i としてデータ型をあわせないと上手く比較できない、といったところでしょうか。

before_action :ensure_correct_user, {only: [:edit, :update]}

def ensure_correct_user
  if @current_user.id != params[:id].to_i
    flash[:notice] = "権限がありません"
    redirect_to("/posts/index")
  end
end


 

ここまでで、ひとまずこのコースは完了です。

次はユーザと投稿の紐付け、いいね機能の実装、パスワード暗号化を実施します。つづく。。

コメント