はじめに
こちらの素晴らしいRailsチュートリアルを実施した記録、およびそこで得た学びについて記載していきます。
基本的に追記していく形で記録していく予定です。
最後まで完走したい。
やっていて思ったこと
- 「本章のまとめ」を先に読んだほうがいいんじゃないか説
- 各章ごとにまとめが書かれてあり、具体的に何をやったかを振り返っているリストがある
- それを先に読んでおくことで、「実現したいこと」と「具体的にどうやって実現するのか」を結びつけやすいんじゃないか、と感じた
環境
- Windows Subsystem for Linux(Windows 10)
- ディストリビューションは Ubuntu 18.04
- エディタはVS Code
第1章
環境構築とHerokuにDeployするところまで。
Herokuを数年前に触ったときは英語のみだった気がするけれど、いつの間にか日本語に対応されていた。
少し時間がかかった箇所としては、rails new
のあとにbundle install
一発で完了しなかったことや環境構築周りくらい。
- 開発環境はローカル(= Ubuntu)で構築
- rubyはanyenv + rbenvでインストール
- Bitbucketではなく、GitHubを使用
- Microsoftの買収のおかげでPrivateリポジトリを無料で使えるようになったのはありがたい
-
Heroku CLIは
curl https://cli-assets.heroku.com/install.sh | sh
でインストール- 上記同ページの
Ubuntu / Debian apt-get
にあるcurl https://cli-assets.heroku.com/install-ubuntu.sh | sh
ではダメだった- 何がダメだったのかは調査していない
- この過程で、WSLにもMacOSでお馴染みの
brew
を使うことができる、ということを知った
- 上記同ページの
-
bundle install時に--path vendor/bundleを付ける必要性は本当にあるのか、もう一度よく考えてみよう
- なるほど確かに。脳死で
--path
をつけてたので反省
- なるほど確かに。脳死で
第2章
大量の機能を自動的に生成するscaffoldジェネレータというスクリプトを使ってアプリケーションをすばやく生成し、それを元に高度なRailsプログラミングとWebプログラミングの概要を学びます。
とのこと。
MVCモデルとRESTアーキテクチャについてが該当すると思われる。
-
ProgateのWeb開発パス(Ruby on Rails)で似たようなことは学べていた
- あと、RESTという言葉は出てこなかったと思う
- Routingで
resource
、Modelでhas_many
やbelongs_to
など、Progateでは出てこなかったコードが学べる - Ruby 2.7.0をインストールしていたため、コマンド実行時にWarningがたくさん出てきてしまった
- 第3章では次点の2.6.5にして進めることにする
- railsのバージョンを上げたりすることでも回避可能かも知れないけれど、チュートリアルと挙動が変わったら面倒なのでそれはしない方針で行う
-
rails console
でUser.all
などが見づらい問題-
PP.pp
を付け加えると整形されて見やすくなった - https://himakan.net/program/ror/pretty_print
-
-
rails db:reset
でテーブル内容をすべて削除できる
ハマった箇所
users
とmicroposts
を関連付けしたあと、microposts
で新しいレコードを作成できなくなった。
irb(main):004:0> PP.pp User.all
User Load (0.2ms) SELECT "users".* FROM "users"
[#<User:0x00007f4fdc5ce918
id: 1,
name: "あ",
email: "あ",
created_at: Tue, 17 Mar 2020 14:58:59 UTC +00:00,
updated_at: Tue, 17 Mar 2020 14:58:59 UTC +00:00>]
=> #<IO:/dev/pts/2>
irb(main):015:0> m = Micropost.create!(content: 'aaaaaaaaaa', user_id: 1)
(0.1ms) begin transaction
(0.0ms) rollback transaction
Traceback (most recent call last):
1: from (irb):15
ActiveRecord::RecordInvalid (Validation failed: Users must exist)
どうしたか?
単なる自分のtypoが引き起こしていた問題でした。
belongs_to :users
となっていた箇所をbelongs_to :user
とした。
belongs_toでは・・・
belongs_to関連付けで指定するモデル名は必ず「単数形」にしなければなりません。
一方、has_manyでは・・・
has_many関連付けを宣言する場合、相手のモデル名は「複数形」にする必要があります。
わかってみれば「なんだ、そういうことか」という感じではあるけれど、Validation failed: Users must exist
だと、文法ミスだということに気づきづらいなぁ、とは思いました。
第3章
静的ページを作成し、さらに自動テストも作成する。
TDD(テスト駆動開発)の「Red→Green→Refactor」を体験できる。
- Ruby 2.6.5にした
- Security Issueの対応
- GitHubにpushしたところ、以下のような内容のメールが来た
Known moderate severity security vulnerability detected in puma < 3.12.4 defined in Gemfile.lock.
Known critical severity security vulnerability detected in actionview >= 5.1.0, <= 5.1.6.1 defined in Gemfile.lock.
- 前者は3.12.4に変更、後者はrails 5.1.6のままだとインストールできなかったので、5.1系では最新の5.1.7に変更して対処
- チュートリアルを進める上でなにか不都合が出てきたら、その際に別途対処する
- GitHubにpushしたところ、以下のような内容のメールが来た
-
rails generate
またはrails db:migrate
をしたあと、元に戻す方法- generateした際と同じ引数で
destroy
を行う- 元に戻ったかどうかの検証のために
git diff
を行ってみたところ、相違ないことが確認できた
- 元に戻ったかどうかの検証のために
- コントローラーの場合
-
rails generate controller StaticPages home help
のあとに -
rails destroy controller StaticPages home help
をやる
-
- モデルの場合
-
rails generate model User name:string email:string
のあとに -
rails destroy model User
をやる ※テーブルのカラムに該当する箇所は省略可能
-
- マイグレーションの場合
-
rails db:migrate
のあとに -
rails db:rollback
をやる - あるいは
rails db:migrate VERSION=0
※デフォルトに戻す
-
- generateした際と同じ引数で
- 「結局テストはいつ行えばよいのか」
-
.erb
-
Embedded Ruby
のこと。<% %>
または<%= %>
というブロック内でRubyのコードを書くことが出来る。 - 前者は実行されるだけ。後者は実行結果がテンプレートに反映される。
-
-
provide
とyield
-
provide(:title, "About")
のように記載し、yield(:title)
とするとAbout
の文字列を取得できる - こういった使い方もできるというだけで、厳密には違うかも
-
-
root_url
-
routes.rb
でroot
を定義すると、root_url
というRailsのヘルパーを使用することができるようになる
-
第4章
一部の言語仕様を学んでいく・・・章だと思われる。
別の言語を使っていた人であれば、ruby独自の記法や慣習の部分だけ動作を確認するなどすれば良さそう。
ただ、演習がrails console
で実行することが想定されているようで、やや面倒。
- %記法(パーセントきほう)
- https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#percent
- リテラルの一種
数字の1や文字列"hello world"のようにRubyのプログラムの中に直接記述できる値の事をリテラルといいます。
-
symbol-to-proc
-
%w[A B C].map { |char| char.downcase }
と%w[A B C].map(&:downcase)
は同じ結果になる
-
- ハッシュロケット
-
=>
のこと 次のようにキーと値をハッシュロケットと呼ばれる=> によってリテラル表現するほうが簡単です。
-
- ハッシュ
- キーにはシンボル(
:name
みたいなやつ)をrailsではよく使っているよ、とのこと- .e.g)
{ :name => "Michael Hartl", :email => "michael@example.com" }
- .e.g)
- ruby1.9からはjavascriptなどの言語のようにもかけるようになったらしい(個人的にはこちらのほうが慣れているから嬉しい)
- e.g.)
{ name: "Michael Hartl", email: "michael@example.com" }
- e.g.)
- キーにはシンボル(
- stylesheet_link_tag
- めちゃくちゃ読みづらい・・・
-
https://railstutorial.jp/chapters/rails_flavored_ruby?version=5.1#sec-css_revisited
- これが
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
- こうなる
<link data-turbolinks-track="true" href="/assets/application.css" media="all" rel="stylesheet" />
- これが
第5章
レイアウトを作成。bootstrap
とSass
を使用する。
- Railsの
partial
-
<%= render 'layouts/shim' %>
というふうに指定する。 - ただし、ファイル名は
_shim.html.erb
という具合に、アンダースコアから始める
-
- アセットパイプライン
- https://railstutorial.jp/chapters/filling_in_the_layout?version=5.1#sec-sass_and_the_asset_pipeline
具体的には、Asset Pipelineがすべてのスタイルシートを1つのCSSファイル (application.css) にまとめ、すべてのJavaScriptファイルを1つのJSファイル (javascripts.js) にまとめてくれます。
- インテグレーションテスト
rails generate integration_test hoge
-
ActionDispatch::IntegrationTest
を継承したclassのファイルが生成される
第6章
第6章から第12章を通して、Railsのログインと認証システムをひととおり開発します。
とあるように、ユーザーのログイン機能を作るために、ユーザーのモデルを作成する。
progateのほうでRailsのパスを実施済みの場合は、見覚えるのある内容が多い。
- マイグレーション(ファイル)
- ブロックの最後の行
t.timestamps
は特別なコマンドで、created_atとupdated_atという2つの「マジックカラム (Magic Columns)」を作成します。 - DBのテーブルにユニークになるようにインデックスを張りたい場合
add_index <:table_name>, <:column_name>, unique: true
- これを、マイグレーションファイルの
change
メソッド内で実行されるように記載する
- ブロックの最後の行
-
reload
- モデルオブジェクトを
.reload
すると、変更(save)前の状態に戻る(取り消される)
- モデルオブジェクトを
-
update_attributes
- e.g.)
user.update_attributes(name: "The Dude", email: "dude@abides.org")
- どれか1つでもバリデーションエラーになると失敗する
- e.g.)
-
updated_at
の更新タイミング-
save
した際に更新されるのではなく、オブジェクトの内容を変更した際に更新される。 - そのため、オブジェクトを何も変更せずに、ただ
save
しただけではupdated_at
は更新されない。
-
-
1.year.ago
- 上記は、現在時刻の1年前という意味
- 同様に、
1.month
や1.day
も存在する。末尾にs
をつけて複数形にしても同じ種類のオブジェクトが返却される
- ActiveRecordでよく使用されるValidation
- presence
- 存在性。いわゆる必須項目として扱わせることができる
- length
- format
- confirmation
- presence
-
rails test test/models/user_test.rb
- 特定のtestだけ実行したいとき
-
rails test --help
で使い方を出力できる
- 国際化ドメイン名
- https://www.nic.ad.jp/ja/dom/idn.html
- railsチュートリアルとは直接関係ないけど、こういうのがあるんだ、くらいには覚えておけるとよさそう
-
before_save { self.email = email.downcase }
- 右の式では
self
を省略できるものの、個人的には省略しないほうがわかりやすいな、と感じた -
before_save { self.email.downcase! }
としても良さそう。一般的にはどのパターンが多いのだろうか
- 右の式では
- 多重代入
@user.password = @user.password_confirmation = "a" * 5
- これは、
@user.password
と@user.password_confirmation
に同じ値("a" * 5)を代入している
疑問点
例えば、Foo@ExAMPle.Comとfoo@example.comが別々の文字列だと解釈してしまうデータベースがありますが、私達のアプリケーションではこれらの文字列は同一であると解釈されるべきです。
この問題を避けるために、今回は「データベースに保存される直前にすべての文字列を小文字に変換する」という対策を採ります。
この対策で良いのかどうかは、実際には対象となるシステムで考慮すべき点だと感じた。
これは"チュートリアルだから"という理由で小文字に変換しているけれど、ユーザー目線で見ると入力していないメールアドレスが、例えば自分しか見られないページに表示されてしまうことになる。
downcase
で一律小文字にしてしまうと復元できなくなるため、検索用のカラムと表示用のカラムで分けて保存する、という対策もありだと思われる。
第7章
ユーザー登録機能を追加する。
-
Rails.env.development?
-
Rails.env == 'development'
と等価 - node.jsでいうところの
NODE_ENV === 'development'
みたいなものという理解
-
- console、server、migrateでデフォルト以外の環境にする方法
rails console production
rails server --environment production
rails db:migrate RAILS_ENV=production
- どうしてバラバラなんだろうか?
- Sassのミックスイン機能
-
@mixin
でCSSを定義(名前をhogeとする) - 別のCSSの定義内で、
@include hoge
で呼び出す
-
-
resources :<リソース名>
-
route.rb
に書くだけでRESTfulなアクションがすべて利用可能になる - 一部のアクションだけに絞りたい場合
resources :<リソース名> :only => [:show, :new]
-
-
debugger
メソッド-
rails server
を実行していたターミナルで、rails console
のようなコマンドを実行できるようになる -
Ctrl + D
でプロンプトから抜けられるようになる
-
-
flash
- なんらかのアクションが成功または失敗したときなどに、画面の上部にぴょこっとメッセージを表示させたいときに使う
-
rails db:migrate:reset
- DBを初期化してマイグレーションをやり直す
-
assert_difference
- テストの前後で何らかのカウントが変わっているか(orいないか)を確認するときに使う(という認識)
- たとえばユーザーの追加によってユーザー数が増えていること、削除によって減っていること...etc
-
content_tag
- 以下のコードは等価
<div class="alert alert-<%= message_type %>"><%= message %></div>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
- 以下のコードは等価
- SSLを有効化
-
config/environments/production.rb
にconfig.force_ssl = true
を追記する - または、上記のコードがコメントアウトされていれば、アンコメントする
- この状態のとき、
http
でアクセスしても、307 Internal Redirect
でhttps
のほうへリダイレクトされる
-
第8章
ユーザーのログイン/ログアウト機能を作る
- セッションのコントローラーを作成する
rails generate controller Sessions new
- なぜコントローラーを作らないといけないんだろう?という疑問がある
- なんとなく、Railsで用意されたヘルパーなどが存在していると思っていた
- と思ったら、log_inメソッドの節で、別途
session
というメソッドが出てきた
-
rails routes
- いま現在のルーティングの状態を表示できる
- セッションの種類
- 一時セッション:
session
メソッドで作成 - 永続的(または持続的)セッション:
cookies
メソッドで作成
- 一時セッション:
-
find()
とfind_by()
で対象が見つからなかった場合の返り値の違い- find:
ActiveRecord::RecordNotFound
- find_by:
nil
- 使い分けの一例として、ログインしているかどうかの判別をする際には
find_by
を使ったほうが良さそう- 例外処理を実装した上で
find
を使うことも出来なくはないだろうけど、そこまでする必要はない、という感じ?
- 例外処理を実装した上で
- find:
- Ruby的なコードの短縮形(の1つ)
@current_user = @current_user || User.find_by(id: session[:user_id])
- 上記のコードは下記のように書ける
@current_user ||= User.find_by(id: session[:user_id])
-
x = x + 1
をx += 1
と書けるのと同じようなものとして考えればわかりやすい
疑問点
- integrationテストコードのファイル名は、やりたいテストの内容(目的)にしている(?)
-
rails generate integration_test users_login
というコマンドが出てきた -
app/views
以下のディレクトリおよびファイル名に沿った名前のほうが、プロダクションコードと対応していることが自明になりわかりやすいんじゃないかと思えるのだけど、どうなんだろう? - controllerに対しては、そのままの名前になっている。e.g.)
sessions_controller_test.rb
- 今思えば、ここまででユニットテストと呼ばれているテストがないことに気づいた(ユニットテストの定義に依るけど)
- 強いて言えば
ApplicationHelper
のfull_title
に対してぐらい?
- 強いて言えば
-
第9章
ログイン機能をさらに発展させていく。
ブラウザを閉じてもログインしたままにするかどうかをユーザーが任意で設定できるようにする。
- セッションハイジャックのうち、有名な4つの方法とその対策
- 管理の甘いネットワークを通過するネットワークパケットからパケットスニッファという特殊なソフトウェアで直接cookieを取り出す
- SSLにより、ネットワークデータを暗号化して保護する
- データベースから記憶トークンを取り出す
- 記憶トークンをハッシュ化して保存する
- クロスサイトスクリプティング (XSS) を使う
- railsの場合は自動的に対策(エスケープ)されている。
- ユーザーがログインしているパソコンやスマホを直接操作してアクセスを奪い取る
- 物理的な攻撃はシステム側では防衛できない。
- ただし、被害を最小限に留める方法はある、とのこと
ユーザーが (別端末などで) ログアウトしたときにトークンを必ず変更する
セキュリティ上重要になる可能性のある情報を表示するときはデジタル署名 (digital signature) を行う
- 管理の甘いネットワークを通過するネットワークパケットからパケットスニッファという特殊なソフトウェアで直接cookieを取り出す
- 「Ruby的に正しい」クラスメソッドの定義方法
- 方法は2つ
def self.method_name ... end
-
class << self ... end
のブロック内でdef method_name ... end
- ここまでの解説では
ClassName.method_name
としていた - 実際の業務ではプロジェクトごとにスタイルガイドは決まっているだろうから、基本はそれに従う形で良さそう
- 新規で書く場合、自分であれば
class << self
のパターンにすると思う- 理由は、ブロック内に書く必要があるから、より明示的でわかりやすくなりそうな気がするため
- 方法は2つ
- cookiesメソッド
- 個別のcookiesは、1つのvalue (値) と、オプションのexpires (有効期限) からできている
- 有効期限は省略可能
- e.g.)
cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }
-
permanent
という専用メソッドがあり、下記のコードは上記と同じ cookies.permanent[:remember_token] = remember_token
-
- 複数タブ、複数ブラウザによるバグ
- 2つの目立たないバグでは上記のバグの対策を講じている
- 実際、この手のバグは発生しやすいと思われる。何故なら、仕様を定義する際に見落とされやすいから
- ありがちなバグとして認識しておければ良さそう
- テスト内ではcookiesメソッドにシンボルを使えない
-
cookies[:remember_token]
ではなくcookies['remember_token']
とする - これはcookiesに対してだけなのか?
-
- メソッド名と変数名が同じだと混乱してしまった
-
current_user
と@current_user
みたいな。さらにローカル変数としてcurrent_user
も出てくると・・・(このときは定義できない? 上書き?)
-
- herokuデプロイ時にメンテナンスモードにする
heroku maintenance:on
- 上記のあと、
git push heroku
→heroku run rails db:migrate
する。 - おわったら
heroku maintenance:off
第10章
ユーザーの更新、表示、削除を追加し、RESTアクションを完成させる。
-
WebブラウザはネイティブではPATCHリクエストを送信できない
- という記述がチュートリアルで出てきたが、ぐぐってもそれらしいものが出てこない
-
mozillaのPATCHについてのドキュメント
-
HTML フォームforms での利用 不可
と記載されている。 -
form属性のmethod属性では、
post
とget
とdialog
の3つが指定可能とのこと
-
-
ブラウザからPATCHメソッドのリクエストが飛ばないときは
- ただ、これはプロキシサーバーが怪しいかも?という記事
-
<a target="_blank" rel="noopener">
-
rel="noopener"
を追加して、セキュリティ対策を行う。参考URL
-
-
フレンドリーフォワーディング
- 「ログインしていないユーザーが編集ページにアクセスしようとしていたなら、ユーザーがログインした後にはその編集ページにリダイレクトされるようにするのが望ましい動作」
-
redirect_to(:url)
について- 「 実は、明示的にreturn文やメソッド内の最終行が呼び出されない限り、リダイレクトは発生しません。」
-
redirect_to
は直ちに実行されるわけではない、という仕様
-
ページネーション
- めちゃくちゃ簡単に実装できる・・・。
-
will_paginate
というgemをインストール -
<%= will_paginate %>
を対象のviewに書く - 対象のviewに対応するcontrollerでは
paginate
メソッドで結果を返す- e.g.)
@users = User.paginate(page: params[:page])
- e.g.)
-
- めちゃくちゃ簡単に実装できる・・・。
-
Railsは自動で色々してくれる、系が難しい(覚えることも増える)
-
パーシャルのリファクタリング
- 「ここでは、renderをパーシャル (ファイル名の文字列) に対してではなく、Userクラスのuser変数に対して実行している点に注目してください12。この場合、Railsは自動的に_user.html.erbという名前のパーシャルを探しにいくので、このパーシャルを作成する必要があります」
- 「Railsは@users をUserオブジェクトのリストであると推測します。さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します」
- 上記に限らないが、この手の振る舞いや記法を知っていないと、コードリーディングも難しくなりそうだなと感じた
-
パーシャルのリファクタリング
第11章
アカウントの有効化。利用できる機能を制限。
メール送信機能を作成し、メールによる本人確認を行う。
-
11.3.3 有効化のテストとリファクタリングの演習でのつまづき
- 「ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。」とあったので、
test/integration
のindexとshow(ファイル追加)に対してテストを追加したが、これで正しかったのか?- 懸念としては、想定外のテストを書いた影響で今後のチュートリアルに影響出てしまわないかどうか
- それはそれで、なぜ動かなくなってしまったのかを調査する練習にはなるけれど
- 追加したテスト
- index: activateされていないユーザーは一覧に表示されていないこと。
assert_select ... count: 0
で対応 - show: activate済みおよびされていないユーザーごとに、リダイレクト先のURLが正しいかどうか。
assert_response
またはassert_redirected_to
で対応 - このために、fixtureにactivateされていないユーザーをひとり追加した
- index: activateされていないユーザーは一覧に表示されていないこと。
- 「ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。」とあったので、
-
本番環境でのメール送信
- 今回は読むだけに留める。
第12章
パスワードの再設定。
-
rails generate controller ... --no-test-framework
-
--no-test-framework
を渡すと、テストを自動生成しなくなる
-
- 名前付きルートの
*_path
と*_url
の違い-
(byebug) puts new_password_reset_path
/password_resets/new
-
(byebug) puts new_password_reset_url
http://localhost:3000/password_resets/new
-
(若干だれてきた・・・)
第13章
マイクロポストの実装。
-
rails generate model
におけるreferences
タイプ- e.g.)
rails generate model Micropost content:text user:references
- migrationファイルでは
t.references :user, foreign_key: true
となる - またmodelsでは、
belongs_to :user
が自動的に付与される
- migrationファイルでは
- あるモデルでbelongs_to関連付けを行なうと、他方のモデルとの間に「1対1」のつながりが設定されます
- e.g.)
-
belongs_to
やhas_many
で関連付けされている場合-
Micropost.new
ではなく、user.microposts.build
のようにして呼び出すほうが"慣習的に正しい"らしい
-
- デフォルトスコープ
- 「データベースから要素を取得したときの、デフォルトの順序を指定するメソッド」
-
default_scope -> { order(created_at: :desc) }
- ApplicationRecordで書いておくと、上記の場合は「作成日時が新しい順」で返却されるようになる
- whereとかも使えるとのこと
第14章
最後の章。
ユーザーをフォローする、ユーザーにフォローされる。
フォローしているユーザーのフィード(マイクロポスト)の一覧を表示する。
全体的に複数行のコードが多くなっているので、箇条書きでの説明が少しむずかしい。この章に限ってはチュートリアルへのリンクを多く貼っていく感じにする。
- Relationshipモデル
- 特殊?なルーティング
-
Usersコントローラにfollowingアクションとfollowersアクションを追加する
following_user GET /users/:id/following(.:format) users#following
followers_user GET /users/:id/followers(.:format) users#followers
-
Usersコントローラにfollowingアクションとfollowersアクションを追加する
- Ajax
-
Asynchronous JavaScript + XML
の略 -
<%= form_for ... %>
にremote: true
を追加するform_for ..., remote: true
- このようにするだけで非同期リクエストを送信できるようになる
- とはいえ、きちんと対応させるためにはいろいろやる必要があるので、チュートリアルを参考にすること
-
- SQLのサブセレクト
- サブクエリともいう
- どれくらい変わるのかを試していた方がいた。結果はこちら
機能拡張
サンプルアプリケーションの機能を拡張するにお題がいくつかある。