LoginSignup
4
5

More than 5 years have passed since last update.

Ruby on Rails チュートリアル集中講座3日目

Posted at

日本語版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を設定しよう

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_controller.rb
#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-"を前に足してあげないといけないのだが)

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 ログアウト

routes.rb

さっそく、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ができてないので直す。
4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5