Rails Tutorial 5 User microposts
Rails Tutorial 5 User microposts
Twitterのようなマイクロポスト機能を実装していく。
A Micropost model
text 型のcontent
属性, integer 型のuser_id
を持つ Micropost モデルを作成する。
$ rails generate model Micropost content:text user:references Running via Spring preloader in process 2331 invoke active_record create db/migrate/20170514234635_create_microposts.rb create app/models/micropost.rb invoke test_unit create test/models/micropost_test.rb create test/fixtures/microposts.yml
user:references
という引数をつけることで、belongs_to
のコードが自動追加される。
また、user_id
も自動的に追加される。
add_index :microposts, [:user_id, :created_at]
を追加してマイグレーションを実行する。
$ rails db:migrate == 20170514234635 CreateMicroposts: migrating ================================= -- create_table(:microposts) -> 0.0086s -- add_index(:microposts, [:user_id, :created_at]) -> 0.0018s == 20170514234635 CreateMicroposts: migrated (0.0108s) ========================
List 13.2 では ApplicationRecord
だったものが、List 13.5 では ActiveRecord::Base
になっている。
意図したものか、ただの誤植か。
- モデルのバリデーションのテストを実装
- バリデーションの追加
- user_id の存在性確認
- micropost の存在性確認
- micropost が 140 文字を超えないように
- テストが通ることを確認
- 正しい慣習
belongs_to
,has_many
の関係にあるモデル間ではuser.microposts.create
のように作成できるのが慣習として正しいbuild
メソッドはオブジェクトを返すが DB には反映されない- User モデル側に
has_many :microposts
を追加する。- これで(
belongs_to
はgenerateで追加済みなので) 慣習的に正しい実装が可能になる
- これで(
- マイクロポストを作成日時の逆順で取り出す
- テスト駆動で進める
- fixture の追加
- created_at はマジックカラムで手動更新できないが、fixture 内では可能
- ラムダ式を使った降順取り出し
- ユーザー削除時にそのユーザーのマイクロポストを同時に削除
dependent: :destroy
オプションを使って自動的に実行されるようにする- 上記のテストを実装する
などを学びながら進める。
Showing microposts
マイクロポストを表示する処理を実装する。
まずはサンプルデータを再生成しておく。
$ rails db:migrate:reset Dropped database 'db/development.sqlite3' Dropped database 'db/test.sqlite3' Created database 'db/development.sqlite3' Created database 'db/test.sqlite3' == 20161023011602 CreateUsers: migrating ====================================== -- create_table(:users) -> 0.0019s == 20161023011602 CreateUsers: migrated (0.0020s) ============================= == 20161106170056 AddIndexToUsersEmail: migrating ============================= -- add_index(:users, :email, {:unique=>true}) -> 0.0013s == 20161106170056 AddIndexToUsersEmail: migrated (0.0016s) ==================== == 20161109220735 AddPasswordToUsers: migrating =============================== -- add_column(:users, :password_digest, :string) -> 0.0007s == 20161109220735 AddPasswordToUsers: migrated (0.0009s) ====================== == 20170206234706 AddRememberDigestToUsers: migrating ========================= -- add_column(:users, :remember_digest, :string) -> 0.0008s == 20170206234706 AddRememberDigestToUsers: migrated (0.0009s) ================ == 20170425032634 AddAdminToUsers: migrating ================================== -- add_column(:users, :admin, :boolean, {:default=>false}) -> 0.0011s == 20170425032634 AddAdminToUsers: migrated (0.0012s) ========================= == 20170426235417 AddActivationToUsers: migrating ============================= -- add_column(:users, :activation_digest, :string) -> 0.0009s -- add_column(:users, :activated, :boolean, {:default=>false}) -> 0.0006s -- add_column(:users, :activated_at, :datetime) -> 0.0004s == 20170426235417 AddActivationToUsers: migrated (0.0022s) ==================== == 20170508234917 AddResetToUsers: migrating ================================== -- add_column(:users, :reset_digest, :string) -> 0.0008s -- add_column(:users, :reset_sent_at, :datetime) -> 0.0004s == 20170508234917 AddResetToUsers: migrated (0.0013s) ========================= == 20170514234635 CreateMicroposts: migrating ================================= -- create_table(:microposts) -> 0.0020s -- add_index(:microposts, [:user_id, :created_at]) -> 0.0011s == 20170514234635 CreateMicroposts: migrated (0.0034s) ======================== $ rails db:seed
次にコントローラとビューを生成する。
$ rails generate controller Microposts Running via Spring preloader in process 2240 create app/controllers/microposts_controller.rb invoke erb create app/views/microposts invoke test_unit create test/controllers/microposts_controller_test.rb invoke helper create app/helpers/microposts_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/microposts.coffee invoke scss create app/assets/stylesheets/microposts.scss
- 一つのマイクロポストを表示するのには _micropost.html.erb パーシャルを使う
- User モデルのコンテキストから Micropost のページネーションを行う
- Userコントローラで show アクションに @microposts 変数を追加する
- will_paginate の引数に上記 @microposts を指定する
- 動作確認用にマイクロポストを追加する(seeds.rb)
User.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true, activated: true, activated_at: Time.zone.now) 99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password, activated: true, activated_at: Time.zone.now) end users = User.order(:created_at).take(6) 50.times do content = Faker::Lorem.sentence(5) users.each { |user| user.microposts.create!(content: content) } end
- マイクロポスト用のCSSを追加する
- プロフィール画面で表示するマイクロポストの統合テストを作成する
- マイクロポストの fixture ファイルを追加する
Manipulating microposts
Web経由でマイクロポストを作成するためのインターフェースを作成していく。
- Micropostsコントローラへのアクセス制御でログイン済みか確かめるテストを作成
- 上記テストが通るように実装を進める
logged_in_user
メソッドを Users コントローラから Application コントローラに移す- Microposts コントローラからも使用できるように
- Microposts コントローラの before フィルターでアクセス制限をかける
- テストが通ることを確認する
- ホーム画面(ルートパス)にフォームを置く
- ログインしていないユーザーにはサインアップ画面を表示
- ログイン済みユーザーにはマイクロポスト投稿用フォームを表示
- ユーザー情報表示はパーシャルで実装
- pluralize でマイクロポスト数を表示(単数系複数形を正しく表示)
- マイクロポスト作成フォームもパーシャルで実装
- homeアクションにマイクロポストのインスタンス変数を追加してパーシャルから参照できるようにする
- error_messagesパーシャルを修正してUserオブジェクト以外でも使えるようにする
- 値がオブジェクトで、キーがパーシャルでの変数名と同じハッシュを使うとパーシャルにオブジェクトを渡せる
- 今までerror_messagesパーシャルを使用していた箇所も上記に合わせて修正する
と進めてマイクロポスト投稿フォームを実装する。
次にHomeにマイクロポストを表示するフィードを実装する。
- feed は全てのユーザーが持つので User モデルに実装するのが自然
- 現段階では、現在ログインしているユーザーのマイクロポストを全て取得する
?
を使って適切にエスケープさせることで SQLインジェクション を防ぐ
- ステータスフィードのパーシャルを作成する
- @feed_itemsの中身はMicropostクラスなので対応する名前のパーシャルを、渡されたリソースのディレクトリ内から探してくれる(?)
- Homeページにフィードのパーシャルを表示する
- マイクロポスト投稿失敗時にエラーを起こさないように空の配列を渡しておく
と進める。
次にマイクロポストの削除を実装する。
ログイン済みユーザーが自分のマイクロポストを削除できるようにする。
- マイクロポスト削除リンクをパーシャルに追加する
- Microposts コントローラに destroy アクションを追加する
- before フィルタで find メソッドを使って確認
- 確認結果NGなら Home ページにリダイレクト
- OKなら成功メッセージをflashしてリダイレクト
- リダイレクト先には
request.referrer
を使って一つ前のURLを指定- Homeページ、プロフィールページどちらから削除された場合にも元のページに戻れる
||
root_urlをデフォルトに設定することで戻るページが見つからなくてもHomeページに戻す
- リダイレクト先には
- before フィルタで find メソッドを使って確認
これで削除が動作するようになる。
続いてテストを書いていく。
- 別々のユーザーに紐付けられたマイクロポストをfixtureに追加
- 別のユーザーのマイクロポストを削除しようとしてHomeページにリダイレクトされることを確認
- 統合テストの作成
- ログインしてマイクロポストのページネーションがされていること
- 無効(空)なマイクロポストを投稿してエラー表示されること
- 有効なマイクロポストの投稿
- 自分のマイクロポストの削除
- 他人のマイクロポストの削除
以上でマイクロポストの操作については実装完了。
Micropost images
画像付きマイクロポストを投稿できるよう機能追加する。
画像アップローダーには CarrierWave を使用するそうな。
carrierwaveuploader/carrierwave/ Classier solution for file uploads for Rails, Sinatra and other Rub
Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks
Sinatra でも使えるのか。画像アップロードのデファクト?
Gemfile に追記して bundle install
すると rails generate
でアップローダーが作れるようになる。
$ rails generate uploader Picture Running via Spring preloader in process 3920 create app/uploaders/picture_uploader.rb
続いてマイクロポストのデータモデルに picture 属性を追加するためのマイグレーションを行う。
$ rails generate migration add_picture_to_microposts picture:string Running via Spring preloader in process 2324 invoke active_record create db/migrate/20170611235521_add_picture_to_microposts.rb $ rails db:migrate == 20170611235521 AddPictureToMicroposts: migrating =========================== -- add_column(:microposts, :picture, :string) -> 0.0075s == 20170611235521 AddPictureToMicroposts: migrated (0.0076s) ==================
- Micropostモデルへの画像の追加
- フォームへの画像アップローダーの追加
- 許可された属性のリストへの picture の追加
- ビューへの画像表示の追加
と進める。
こちら にある通り、form_for
を使う場合には multipart form-data encoding type
は Rails によって自動的に付与されるとのこと。
これで画像はアップロードできるようになる。
巨大なファイルや無効なファイルをハンドリングできるようにバリデーションを実装する。
- ファイル拡張子のホワイトリストの追加
- CarrierWaveで自動生成したアップローダーに追記
- ファイルサイズのバリデーションの追加
- Micropostモデルに実装
- 送信フォームへの制限を追加
- jQuery を用いる
- 警告を無視されたり、POST コマンドを直接実行されたりすると意味がない
- よってサーバー側でのバリデーションが重要になる
続いて画像のリサイズを実装する。
ファイルサイズは制限できても、画像のサイズが制限できていないのでそこに対応していく。
こちらの注釈に以下の記載がある。
It’s possible to constrain the display size with CSS, but this doesn’t change the image size. In particular, large images would still take a while to load. (You’ve probably visited websites where “small” images seemingly take forever to load. This is why.)
CSS で表示サイズを固定するという方法も考えられるが、これでは実際に読み込む画像のサイズは変わらないため無駄に時間がかかることになるそうな。気をつけよう。
以下の ImageMagick というプログラムを使うそうな。
Convert, Edit, Or Compose Bitmap Images @ ImageMagick
そういえば Redmine でも使ってたのでデファクトっぽい?
ImageMagick と Ruby の繋ぎこみに CarrierWave の MiniMagick という gem を使うと。
- ImageMagick で画像サイズを変更する
- Ruby から使えるように MiniMagick を使用する
resize_to_limit
で指定した大きさ以上の画像は指定サイズに縮小する
と進める。
開発環境はこれでいいが、本番環境でアップロードされた画像は Heroku ストレージ上に置かずクラウドストレージ上に保存する。
Heroku のローカルストレージはデプロイする度にクリアされてしまうんだそうな。
- fog gem を使う
- クラウドストレージには S3 を使用
ということでこれもお決まりみたいですね。
クレデンシャルはソースコードに入れずに環境変数で設定、という定石に従って設定。
ここまできたら master にマージして Heroku にデプロイする。
デプロイしたら Heroku の DB をリセットする。
$ heroku pg:reset DATABASE ! This version of the API has been Sunset. ! Please see https://devcenter.heroku.com/changelog-items/1147 for more information.
。。ん?
Legacy Platform API Brownouts start May 24th _ Heroku Dev Center
あー、そうですか。
$ heroku --version heroku-toolbelt/3.43.3 (x86_64-linux) ruby/2.3.0 heroku-cli/5.2.20-9d094b0 (linux-amd64) go1.6.2 You have no installed plugins.
古いんですね。アップデート
$ sudo apt-get update; sudo apt-get install heroku-toolbelt heroku
$ heroku --version heroku-cli/6.11.19-a460a01 (linux-x64) node-v7.10.0
気を取り直して。
$ heroku pg:reset DATABASE ▸ WARNING: Destructive action ▸ postgresql-adjacent-40581 will lose all of its data ▸ ▸ To proceed, type fierce-wave-40771 or re-run this command with --confirm ▸ fierce-wave-40771 > fierce-wave-40771 Resetting postgresql-adjacent-40581... done
よしよし。
次はいよいよ最終章!
その他
最後にやや焦ったけど通せた通せた。
Rails 5.1 からクレデンシャルをどう扱うか、みたいな考えが変わってたはず。
はよキャッチアップしよ。
<li id="micropost-<%= micropost.id %>">
This is a generally good practice, as it opens up the possibility of manipulating individual microposts at a future date (using JavaScript, for example).
だったのが Listing 13.51 で
<li id="<%= micropost.id %>">
になってるのは間違いかな。質問してみようかな。
こちらの注釈 がなかなか。
To learn how to do things like this, you can do what I did: Google around for things like “javascript maximum file size” until you find something on Stack Overflow.
ま、まぁそうね。WWDC のラボでも同じようなこと言われたしもうこれ定着してるのかな。