意識低いRuby on Rails再入門4
2015/07/03
下記の内容を読んでテスト系の処理をすっ飛ばしたメモ。
http://railstutorial.jp/chapters/sign-in-sign-out?version=4.0#top
Userモデルを作ってログインしたりセッション管理したい。
Contents
Userモデルを作る
rails generateコマンドで作る
キーはname,email,password_digestにする。
1 |
rails g model User name:string email:string password_digest:string |
マイグレーションファイルが生成されるので、マイグレーションする
1 |
bundle exec rake db:migrate |
Userモデルができた。
Userモデルにhas_secure_passwordという文を宣言すると、パスワードを平文ではなくハッシュ化された状態で管理することができる。らしい。
何ができるモノなのか全く掴めないのでコードに書いてる説明を読んでみる。
This mechanism requires you to have a +password_digest+ attribute.
password_digestというキーを追加していた理由はこれだ。
Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
gem ‘bcrypt’, ‘~> 3.1.7
bcryptというgemが必要らしい。
このgemを利用してpassword_digestを暗号化して保存してくれてるみたい。
Gemfileを見るとbcryptがコメントアウトされてるので、外してbundle installする。
インストールできたら実際にUserを作ってみる。(サンドボックスモードで)
1 |
bundle exec rails c --sandbox |
まず、パスワード無しでユーザを作ってみる。
1 2 3 4 |
irb(main):002:0> User.create(name: 'hoge', email: 'hoge@gmail.com') (0.1ms) SAVEPOINT active_record_1 (0.1ms) ROLLBACK TO SAVEPOINT active_record_1 => #<User id: nil, name: "hoge", email: "hoge@gmail.com", password_digest: nil, created_at: nil, updated_at: nil> |
しっかりロールバックされている。
次にパスワードを入力してユーザを作ってみる。
1 2 3 4 5 |
irb(main):003:0> User.create(name: 'hoge', email: 'hoge@gmail.com', password:'hogehuga') (0.1ms) SAVEPOINT active_record_1 SQL (0.6ms) INSERT INTO "users" ("name", "email", "password_digest", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["name", "hoge"], ["email", "hoge@gmail.com"], ["password_digest", "$2a$10$.cJYUMFEjuUXI6AF2H9Qg..RBWy56pe2ecAKMAfzqW2zH6qXs0oxK"], ["created_at", "2015-04-01 15:37:29.694749"], ["updated_at", "2015-04-01 15:37:29.694749"]] (0.3ms) RELEASE SAVEPOINT active_record_1 => #<User id: 1, name: "hoge", email: "hoge@gmail.com", password_digest: "$2a$10$.cJYUMFEjuUXI6AF2H9Qg..RBWy56pe2ecAKMAfzqW2...", created_at: "2015-04-01 15:37:29", updated_at: "2015-04-01 15:37:29"> |
作られたっぽい。
Userの新規登録画面を作るなり、コンソールから作るなりして自分用のUserを作る。
自分のアカウントでログインしたい
ログインとは何か
普段、我々は当たり前のように何かのウェブサービスにログインしていると思う。
ログインしているとは何なのだろうか。
ログインしていないと、いいねもできなければ、自分のプロフィールを編集したりすることもできない。
私は日本太郎です。と、各ページを閲覧するため、いいねするため、プロフィールを編集するためにサーバにたいして表明しなければいけない。
ブラウザ上でイケてるhtmlを見るためにhttp、httpsという通信ルールを使ってデータをやりとりしている。
で、このhttp,httpsっていうのは状態を持たない。
『前の通信してきた人が日本太郎さんだったから、この通信も日本太郎さんだね。じゃあ、プロフィール編集していいよ。』と文脈を読んでくれない。
じゃあ、どうすれば良いのか。
『毎回ログインさせる』という方法がある。
ページの遷移をするたび、いいねボタンを押すたび、プロフィール編集画面で『登録』ボタンを押すたびにログインさせるのだ。
そうすれば、日本太郎さんがいいねボタンを押した、プロフィール編集ページに遷移した、ということが保証できるはず。
しかし、面倒すぎる。
セッションの出番だ
なので、セッションという方法を使う。
通行手形のようなモノだ。
これをサーバへのリクエストに付加して通信を行う。
サーバはセッションを受け取り、それが有効であるか確認する。
有効であればリクエスト本来の処理を実行してレスポンスを返す。
無効なセッションであればエラーを表示するなり、ログイン画面に飛ばしたりする。
セッションの受け渡し方はいろいろある、リクエストのパラメータに含める、ヘッダーに追加する、Cookieに追加する、などなど。
Cookieで。
アカウントにセッションを持たせるようにする
セッションを受けたはいいが、そのセッションからUserを取得するにはどうすればいいのだろう。
Userにセッション用のカラムを追加する方法はどうだろう。
既存のテーブルにカラムを追加する場合もModelを作成したときに使ったmigrationファイルが活躍する。
新たにremember_tokenというカラムを追加してみる。
1 2 3 |
anal% bundle exec rails g migration AddColumnToUser remember_token:string invoke active_record create db/migrate/20150402155723_add_column_to_user.rb |
上のように
AddColumnToXXX 追加したいカラム名:型
と、書くと
1 2 3 4 5 |
class AddColumnToUser < ActiveRecord::Migration def change add_column :users, :remember_token, :string end end |
このようにカラムを追加する文を勝手に追加したマイグレーションファイルが生成される。
railsの強力さを垣間見る。
マイグレーションファイルができたらマイグレーションしよう。
1 |
anal% bundle exec rake db:migrate |
これでUserクラスにセッションの文字列を保持する新たなメンバーが追加された。
次にセッション管理用(ログイン・ログアウト)のControllerを作る。
セッション管理用の画面を作る
新しくControllerを作る必要があるので、rails g controllerを使う。
bundle exec rails g controller Sessions
Controllerができた。
ログインページを作る。
エンドポイントはnewが良さそう。
routes.rbにエンドポイントを明記しておくことも忘れないように。
1 |
resources :sessions, only: [:new, :create, :destroy] |
resourcesというrailsが用意してるメソッドを使うとRESTfulなエンドポイントたちを自動で作ってくれる。
http://railsdoc.com/references/resources
onlyオプションをつけて不要なエンドポイントは作らないようにする。
Controllerにnewメソッドを追加したら、次のようなViewファイルをviews/sessions以下につくる
1 2 3 4 5 6 7 8 9 10 11 |
h1 | ログイン .form = form_for :session, url: sessions_path do |f| = f.label :email = f.text_field :email = f.label :password = f.password_field :password = f.submit "ログイン" |
sessions/newにアクセスするとそれらしい画面が表示される。
ログインボタンを押すと、エラーが表示される。
Controllerにcreateがないからだ。
なのでcreateを追加する。
createではリクエストのパラメータからUserクラスを取得、その可否でログインの成功失敗を判断して適当なViewをレンダリングする。
1 2 3 4 5 6 7 8 |
def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) redirect_to items_index_path else render 'new' end end |
与えられたパラメータからUserを取得できたら、remember_tokenとクライアントに返すCookieにセッション情報を格納する必要がある。
さて、肝心のセッション情報は誰が、どうやって作成するべきだろうか。
Userクラスに任せよう。
どうやって作るか、を考える前にセッション情報の性質とはどんなものか考える。
まず、セッション情報は重複してはいけない。
重複するということは1つのセッションに複数人のアカウントが紐付いている状態だ。
例として、プロフィール編集画面でセッションが必要な場合を考える。
セッションに対して複数人のアカウントが紐付いていると、誰のプロフィール情報を表示すればよいかシステム側で判断できない。これは困る。
なので、セッション情報は重複してはいけない。
フルスタック(笑)なRailsには便利な機能があって下記を呼ぶだけでbase64エンコードされたランダムな文字列が生成される。
SecureRandom.urlsafe_base64
Userのクラスメソッドでremember_token用の値を生成できるようにする。
インスタンスメソッドにすると、いちいちUserクラスをnewして生成してからメソッドを呼び出さないといけない。
また、new_remember_tokenメソッドは各Userインスタンスごとのメンバー変数に依存した処理ではない。
なので、クラスメソッドにする。
1 2 3 4 5 6 7 |
class User < ActiveRecord::Base def User.new_remember_token SecureRandom.urlsafe_base64 end has_secure_password end |
このメソッドをつかってログイン用の処理をControllerに書いてゆく。(本当はこういう処理をControllerに書いたら良くないんだけど。。w)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) sign_in(@user) redirect_to items_index_path else render 'new' end end private def sign_in(user) remember_token = User.new_remember_token cookies.permanent[:remember_token] = remember_token user.update_attribute(:remember_token,remember_token) end |
もう一度ログイン画面を開き、必要な情報を入力してログインしてみる。
ブラウザのデベロッパーツールなどでCookieを見てみると、remember_tokenというキーに謎の文字列が格納されていることが確認できるかと思う。
rails consoleで該当するUserの情報を見てみると、remember_tokenに謎の文字列が格納されている。
これでログイン処理ができたので、リクエストごとにセッションをつけて遷移できるようになった。
また「いいね」などのアクションを行う際にも、このセッションを見てユーザを一意に特定することができるためアクションごとにログインさせる必要もない。
ついでにログアウト機能も作る。
ログアウト=セッションを無効化する、と考えてよい。
SessionControllerにdestroyを追加し、ログアウト用の処理を追加する。(本当はこういう処理をControllerに書いたら良くないんだけど。。w)
1 2 3 4 |
def destroy cookies.delete(:remember_token) redirect_to 'new' end |
このログアウト機能を呼ぶためのViewが必要になる。
ウェブアプリの中で常に表示させておきたいのか、特定の画面だけでログアウトできるようになってればいいのか、要件次第だと思う。
この辺は長くなりそうなので別に書く。
次はHelperクラスを利用してControllerに書かれていたログイン用の処理を切り出してControllerを見通しよくしてみたい。
336px
336px
関連記事
-
-
Ruby,Railsのチートシート
こう …
-
-
Docker Machineのメモ
随時 …
-
-
scrapy実行時のエラー対処
Ma …
-
-
DockerでNginxしたい
Co …
-
-
redux-sagaをざっくり入門したい
Co …
-
-
FlutterでListViewしたい
こん …