日本語版Rails Tutorialhttps://railstutorial.jp/
を運営しているYassLabさんが講師!
たった5日間であの長いRails Tutorialを完走する集中講座に参加しています。
https://coedo-dev.doorkeeper.jp/events/72968
なんかメモの質がどんどん走り書きになってるけど、
メモだからQiita投稿しちゃいます。
第7章 ユーザー登録〜推測可能であることの素晴らしさ〜
RESTful
Railsの設計のひとつ。
APIまわりを綺麗にしよう。リファレンス読まなくても 推測 できるのがRESTの嬉しいところ。
usersまわりの7つのリクエスト。scaffoldお得パック。
リク | URL | 名前付きルート | コントローラアクション | 対応するモデルの動作 |
---|---|---|---|---|
GET | /users | user_path | index | User.all |
GET | /users/new | user_path(user) | new | User.new(...) |
POST | /users | new_user_path | create | User.create(...) |
GET | /users/1 | user_path | show | User.find(1) |
GET | /users/1/edit | edit_user_path(user) | edit | User.find(1) |
PATCH | /users/1 | user_path(user) | update | User.find(1).update_attributes |
DELETE | /users/1 | user_path(user) | destroy | User.find(1).destroy |
/users/new というより/signup の方が簡単だよね。
DELETE専用のページは普通無い(Showとかでいい)
ユーザー退会ページとかは用意してもいい
PUTリクエストとPATCHリクエストの違い
どうも過去のものになっちゃったらしい。
PUTはそもそも一括更新って意味だった。(同じことできるので残ってはいる。
Q.TwitterはRESTじゃない?
/user/kugyu10 だったら分かるんだけど、
/kugyu10 になってる。これはRestなのか?
A.RESTかどうかは本質的ではない。推測できればいいんじゃない?
RESTパターンにあわせると推測しやすいけど、
RESTパターンじゃなくても推測できればいいんじゃない?という話。
QiitaのURLの例(/ユーザー名/items/アイテムID)
https://qiita.com/kugyu10/items/b743dc3d50c145dd85ef
多分、中ではUser.find_by(name:"kugyu10").items.find_by(id:...) みたいなことしてる?
GitHubのURLの例。(/人/リポジトリ って感じ?)
https://github.com/kugyu10/flow_statter
たしかに、推測はできる。
SampleAppでユーザーを表示させよう。
Debuggerの出番
debug(params) if Rails.env.development?
後置if便利。一行ですむ喜び。
Routes.rbを設定しよう
resources :user
routesが増えてきたら、
rails routes
で一覧が確認できる
PUTはPATCHの代わりになる。
params
ハッシュみたいに使える
今回はparams[:id]で GETからきたURL /users/1 の1が取得できる。
インスタンス変数って?
ふつーに user
って書いたらローカル変数。
メソッドの中でしか生きられない。
view側で見るためには、view側で宣言するか、↓で作ってもらう
リクエスト内?
@user
view側でも見れるようになる。
@@user
$user
とかいうクラス変数、グローバル変数もある。
基本、使わない方が幸せ。
(グローバル変数がある、ということは、
あった方が嬉しいという特殊なケースがあるのだろう。
ただ、初心者のうちは忘れた方が良さそうだ)
ブレークポイント
byebugコマンド
理想的には使わない方が基本はデバッグは速いので多様しないこと。
現に、初期のRubyには無かった。
もちろん、こみいっているバグや訳がわからないとき便利。
Gravoterを使う
これはUsersHelperに
gravatar_for(user)
を定義する。
form_for
User.newで、空のUser(雛形)をインスタンス変数にいれて
new.html.erbにわたす
7つのアクション
index, show, new, create, edit, update, destroy
この順番で書くのが慣習。
abc順じゃないし、なんの順番だろう?
form_for
じつはたくさん機能がある。Railsガイド参照。
email_field
スマホで@や.が打ちやすくなる。
password_field
***で隠してくれる。
(あとなんかOnePassとかが反応してくれる。)
Create my accountの作り方
もう、POSTで送るところまでできてる。
POSTを送ったあと、
6章で作ったバリデーションを試して
DBにsaveにした。
CSRFを防ぐトークン
(application.html.erbに入ってる)
form_with って?
Rails5.1から、form_forとform_tagを統合しようとして実装されたもの。
そもそもform_forとform_tagって同じようなことをやってるよね?って話で。
使いやすいなら今後そっちに統一されるかも?
Ajaxで
一応、ソレ用にform作るオプションがある。remote:true
余談
Railsのコントリビューターはたくさんいるが、
Rails開発してるコアチームは世界で12人程度。
日本人もコミットしてる人は3人いる。
Kamipoさんはすごい人
ぐぐったらほんとに凄い人だった件。
Yaginumaさんのブログ。Railsの最新動向がでてる。情報収集の習慣づけとしておすすめ。
amatsudaさん
createアクション
@user = User.new(params[:user])だけでいい理由
params[:user]が入れ子構造になってて
user_idもemailも入っているから。
nilとfalse以外はtrueになると嬉しい理由
if @user.save
# 成功
else
# 失敗
end
みたいな短い書き方できる。
(たとえばJavaだったら、nullかもしれないものは
if (hogehoge == null) {} と書くしかない。
(Nullableがいまいち使えない))
ForbiddenAttributesError
文法的にはソレでいいんだけど、
(form_for経由の場合は脆弱性生むので)禁止していますよ、
ってエラー。
こっそりparam[:user][:admin]=trueとか混ぜて
送信してくるかもしれないので対策してね。って意味。
こういうのを防ぐストロングパラメータにすれば解決。
# user_paramsは以下4つの属性しか許可しませんよ
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
error_massagesパーシャル
sharedに入れたのは、今後、フォームがあるたんびに作るから。
pluralize
べんり
'child'.pluralize # "children"
'men'.singularize # "man"
GET /users/:id
redirect_to user_path(@user.id) #平たく書くとこう
redirect_to user_path(@user) #.idは省略できる
redirect_to @user #さらにここまで省略できる
flash
ハッシュっぽく使える
flash[:success] = 'Welcome to Sample App!'
keyをdangerとかsuccessとかにすると
そのままbootstrapのクラス指定に使えるよね、って小技。
(そのまま、といいつつviewでは "alert-"を前に足してあげないといけないのだが)
http://bootstrap3.cyberlab.info/components/alerts.html
flashのkey | bootstrapで使えるクラス名 | 色 |
---|---|---|
:success | alert alert-success | 緑 |
:info | alert alert-info | 青 |
:warning | alert alert-warning | 黄色 |
:danger | alert alert-danger | 赤 |
SSL,Puma,Procfileの設定
設定ぜんぜんわからん。今回はコピペる。
8章 基本的なログイン機構 〜ここからが本当の地獄だ〜
ログイン・セッション周り。
普通はMVCでやるんだけど、
Sessionはモデル・DBで扱うとかえって面倒くさい。
かわりにcookie(クライアント側のブラウザ)と
Sessionsリソース(サーバーとブラウザ間の情報)を使う
モデルが無いから頑張らないとね、みたいなことも多い。
Sessionのルーティング
GET /login ログインページを表示する
POST /login ログインボタン押した!
DELETE /logout ログアウト
さっそく、Controllerを作ろう
今まで
def new
@user = User.new
end
あれ?今回モデル作ってないぞ。
今回やりたいことは、ログインページで受け取ったものを
POST /login に渡してあげればいい。
Routesで設定すれば、Sessions#createに渡るので。
で、今回の書き方。sessionっていうシンボルを渡す。
form_for(:session, url: login_path)
クラスメソッドの話
def User.all
end
authenticate
has_secure_passwordするとできるメソッド。
nilで呼び出そうとすると死ぬ。当然か。
なのでnilガードが必要。Rubyならnilガードも一行。
トリッキーな書き方
if user && user.authenticate(...)
user.authenticateは見つかったらオブジェクトかfalseを返す。
find_byでもなんでも、見つかったらオブジェクトを返すことがRubyは多い。
なぜならオブジェクトがあればなんでもtrue、nilはfalseと評価できるで。
booleanを返してほしいメソッドなら、
authenticate? みたいに「?」を末尾に書くのが慣習。
OSSでもすごい良い機能だけど、名前がダメだからマージされなかった、
とかめっちゃあるあるらしい。Java以上にRubyでは名前はすごい大事。
redirect_to と renderの違い
flash 次の1回目のリクエストが来るまで保持し続ける。
redirectなら、それもリクエストなので、redirectした瞬間、
(flashを最後読み取って表示してから)消える
renderは描画なので、同じリクエスト内の処理の処理。
なのでまだ、次のリクエスト時にも残ってる
直すのは簡単。flash.now にするだけ。
renderする時はflash.now
redirect_toする時はflashを使う、って覚え方で多分大丈夫。
loginしてるかどうか
SessionsHelperに書く
session
- name
- password
- hoge
session.hogeがnilでないならOK
で、ログインの機能はありとあらゆる操作で欲しいので
ApplicationControllerにincludeする。
これでSampleApp内でどこでも使える。
復元
sessionの状態を見るメソッドとしてcurrent_userメソッド作る。
メモ化
パフォーマンスがよくなる
基本的にfindはDB問い合わせ。
current_userメソッドたたくたびに、毎回
DB問い合わせしたら大変なので、
一度インスタンス変数にメモ化できるとうれしい。
# メモ化を平たく書くとこうなる
if @current_user.nil?
@current_user = User.find_by(id: session[:user_id])
else
@current_user
end
Rubyは、@current_userの真偽は(nil以外なら)常にtrue。
上記のトリッキーな書き方同様、こうも書ける
@current_user = @current_user || User.find_by(...)
さらにこう短く書くのがRails流。
@current_user ||= User.find_by(...)
(未だに見ると一瞬びびるので
呼吸するように書きたい。)
logged_in?
?で終わってるので、booleanで返るのが明らか。
logout
method delete
method patch
統合テスト用のデータを作る、ための下ごしらえ。
DBには生パスワードじゃなくて、ハッシュ化したものいれないといけないから。
fixtureの用意もなかなか大変。
で、ハッシュ化させるクラスメソッドを用意する。
User.digest(string)
中身はざっくりの理解でOK
三項演算子 ?と:
A ? B : C
A がtrueならB実行、そうでないならC実行。
?はメソッド名で良く使うし、:も::とかしたりするから
慣れるまでややこしそう。
ファットコントローラー問題
ありがち
cookpadくらいになるとファットモデル問題もあるらしい。
9章 発展的なログイン機構〜Chromeさんが頭良くなりすぎた件〜
タブを閉じても大丈夫なようにする機能。
Chromeさんはある程度の期間、sessionを保ってくれるので重要度が下がったかも。
9章の機能をいれれば、ブラウザどころかMacOSをリブートしても大丈夫。
ユーザー体験向上!
ただし、セッションハイジャックのリスクと常に背中合わせ。
セッションの寿命
-
銀行の振込とか決済サービスとかなら、
セッションハイジャックのリスクは絶対いや。
数分でセッションを切っていく。 -
機密とかを扱うサービス
タブを閉じたら期限切れに。 -
SNSなどのサービス
セッションを永続化しているサービスが多い。
で、cookieを使う
cookieはクライアントのブラウザの中に保存されるテキストデータ。
cookieの中にRemember meトークンが保存されている。
ハッシュ関数を通してダイジェストでやりとりする。
DBに保存するのもダイジェスト。
記憶トークンはパスワード同様、なるべく触らない、覚えない、
漏らさないように気をつける。
今回の実装概要図を見ながら説明
署名付(暗号化済みの)ユーザーID
signed[:user_id]340394→本物のuser_id:3
トークンはユーザー名と違ってユーザーが書かない選ばない。
Railsがランダムの文字列を作って勝手にブラウザのクッキーに置く。
そして、ハッシュ化したダイジェストだけDB保存する。
いざ実装
Userにremember_digestのカラムを追加しよう
rails generate migration add_remember_digest_to_users remember_digest:string
rails db:migrate
_ばかりなのにちゃんと理解できるRails。
has_secure_passwordがやってくれたことをやる
password(フォーム用)
password_confirmation (フォーム用)
password_digest (これだけDBに保存する)
メソッドの最後がイコールなメソッド
仮想的なカラム(一時的にインスタンス変数にいれるけどDBには保存しない)
としてふるまってくれる。ここまでRuby。
で、アトリビュートアクセッサ(sttr_accessor) というRailsが敷かれている。
よくあるメソッドとして、書ける。
クラス・メソッドの定義
User.digest()
# ここでのselfはUserになる
self.digest()
メタプロするとき、selfじゃないとダメな時がある。
(メタプロしないならUserでいいかな・・・?)
cookiesを永続化する
# これはcookiesのexpires:を 20.years.from_now.utc にするのと同じ。
cookies.permanent
ユーザーIDを署名してcookieにおく
cookies.signed[:user_id] = user.id でおk
つまりメソッドチェーンすれば
cookies.permanent.signed[:user_id] = user.id
user_idの探し方
1.sessionにあればそれ使う
2.cookiesにあればそれ使う
3.どっちも無いならだめ
if (user_id = cookies.signed[:user_id])
Javaだと9割方比較演算子==のたいぽだけど、Rubyならこれもよくある記法。
条件式の中で代入している。nilならfalseになるので。
2つの目立たないバグ
・連続ログアウト問題
再現できればあとは簡単。
・複数種類のブラウザを使った問題。ほんと目立たない(笑)
tokenがあって、digestだけnil、という特殊な状況でエラー発生。
・別のブラウザで同時にログインする(別々にcookie保存)
・片方のブラウザを閉じて開く(nilになっちゃう)
単体テストと結合テストでログインの方法が違うけど、
同じlog_in_asメソッド名で実装して、
似たように使えるようにしてしまう。
log_in(user)
logged_in?
is_logged_in?
log_in_as(user)
log_in_as(user)
さすがにややこしくない?
何がうれしいか?
一番は、エンジニアにとって推測しやすい、ということ。
推測ができて、そのとおりにできるなら、中身は意識しなくていい。
どこまでダックタイピングするかは、
ガイドラインとかスタイルとかチームの合意次第でもある。
疑問点
- @current_userのインスタンス変数の寿命は?
- sessionまわりが全体的にもやっとする
TODO
- 9章まだ完了してないので実装する。
- 分からないの承知で、Railsガイド(コントローラーとかセッションとか)に目を通す。
- なんかBitBucketができてないので直す。