【149日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストのビューを追加する
今日は「11.2.1 マイクロポストの描画」から。
11.2.1 マイクロポストの描画
この項では、ユーザーのプロフィール画面 (show.html.erb) でそのユーザーのマイクロポストを表示させ、また、これまでに投稿した総数も表示するようにしていきます。
ふむふむ。Twitterのプロフィール画面のイメージかな。
まずは、Micropostのコントローラとビューを作成するために、コントローラを生成しましょう。
生成しましょう。
$ rails generate controller Microposts 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
user.html.erbパーシャルと同じようにmicropost.html.erbパーシャルを定義してマイクロポストを表示するようにする。
パーシャル定義しようとしたらファイルがなかったので作成しておく。
$ touch app/views/microposts/_micropost.html.erb
app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> </li>
今回の場合は、ユーザーコントローラのコンテキストにおいて、マイクロポストをページネーションしたいため、明示的に@microposts変数を will_paginateに渡す必要があります。
will_paginateについての説明が全くピンと来てないけど明示的に変数を渡す必要があるのは分かった。
チュートリアルのコードにはredirect_toの行のコードがないけどとりあえず下に@microposts変数追加しといた。
app/controllers/users_controller.rb
def show @user = User.find(params[:id]) redirect_to root_url and return unless @user.activated? @microposts = @user.microposts.paginate(page: params[:page]) end
これですべての要素が揃ったので、プロフィール画面にマイクロポストを表示させてみましょう (リスト11.23)。
マイクロポストの投稿数はuser.microposts.countで取得できて、それなりに高速らしい。いいね!
アイコンの表示の下にマイクロポストを表示させるようにコードを追加。
app/views/users/show.html.erb
<div class="col-md-8"> <% if @user.microposts.any? %> <h3>Microposts (<%= @user.microposts.count %>)</h3> <ol class="microposts"> <%= render @microposts %> </ol> <%= will_paginate @microposts %> <% end %> </div>
ここで、改良した新しいプロフィール画面をブラウザで見てみましょう (図11.5)。
…何とも寂しいページで、がっかりですね。マイクロポストが1つもないのでは無理もありません。
今までのユーザーページと何も変わっていない…!ので動作確認はマイクロポスト追加してからにしよう。
今日の学習時間は【20分】。
次は「11.2.2 マイクロポストのサンプル」から。
【148日目】【1日20分のRailsチュートリアル】【第11章】ユーザーが破棄された場合、マイクロポストも破棄されるようにする
今日は「11.1.4 マイクロポストを改良する」の「Dependent: destroy」から。
11.1.4 マイクロポストを改良する
Dependent: destroy
サイト管理者はユーザーを破棄する権限を持ちます。ユーザーが破棄された場合、ユーザーのマイクロポストも同様に破棄されるべきです。
マイクロポストとユーザーの関連性を切るのもありだと思ったけど、残しておいても容量圧迫するもんねぇ。。。要らないか。
この振る舞いは、has_manyメソッドにオプションを渡してあげることで実装できます (リスト11.18)。
元々書いてたhas_manyメソッドにdependent: :destroyオプションを追加。
これだけでユーザーを削除したらそのユーザーに紐付いたマイクロポストも削除されるらしい…!楽!シンプル!
app/models/user.rb
class User < ActiveRecord::Base has_many :microposts, dependent: :destroy :
次に、リスト11.18が正しく動くかどうか、テストを使ってUserモデルを検証してみます。
マイクロポストを生成して、次にユーザーを削除してマイクロポストの数が1減っているかを確認するテスト。
test/models/user_test.rb
test "associated microposts should be destroyed" do @user.save @user.microposts.create!(content: "Lorem ipsum") assert_difference 'Micropost.count', -1 do @user.destroy end end
問題なく動きました。
$ bundle exec rake test 54 tests, 223 assertions, 0 failures, 0 errors, 0 skips
11.2 マイクロポストを表示する
Twitterのような独立したマイクロポストのindexページは作らずに、図11.4のモックアップに示したように、ユーザーのshowページで直接マイクロポストを表示させることにします。
まずは特定のユーザーのマイクロポストを表示させるようにユーザーのプロフィールページを修正する。
今日の学習時間は【15分】。
次は「11.2.1 マイクロポストの描画」から。
【147日目】【1日20分のRailsチュートリアル】【第11章】マイクロポストを特定の順序で取得できるようにする
今日は「11.1.4 マイクロポストを改良する」から。
11.1.4 マイクロポストを改良する
具体的には、ユーザーのマイクロポストを特定の順序で取得できるようにしたり、マイクロポストをユーザーに依存させて、ユーザーが削除されたらマイクロポストも自動的に削除されるようにしていきます。
ふむふむ。
デフォルトのスコープ
まずデータベース上の最初のマイクロポストが、fixture内のマイクロポスト (most_recent) と同じであるか検証するテストを書いていきましょう (リスト11.13)。
まずテストを書いてみる。
test/models/micropost_test.rb
test "order should be most recent first" do assert_equal microposts(:most_recent), Micropost.first end
リスト11.13では、マイクロポスト用のfixtureファイルからサンプルデータを読み出しているので、次のfixtureファイルも必要になります (リスト11.14)。
マイクロポスト用のfixtureファイルを作成する。
test/fixtures/microposts.yml
orange: content: "I just ate an orange!" created_at: <%= 10.minutes.ago %> tau_manifesto: content: "Check out the @tauday site by @mhartl: http://tauday.com" created_at: <%= 3.years.ago %> cat_video: content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk" created_at: <%= 2.hours.ago %> most_recent: content: "Writing a short test" created_at: <%= Time.zone.now %>
ほとんどのシステムでは上から順に作成されるので、fixtureファイルでも意図的に順序をいじっています。
(中略)
この振る舞いは恐らくシステムに依存していて崩れやすいので、(本来は) この振る舞いに依存したテストは書くべきでは無いでしょう。
崩れやすいからオススメではないしいつも使えるわけではない?けど、今回はとりあえず大丈夫なのかな…??
この段階ではマイクロポストをソートしてないのでテストは失敗する。
$ bundle exec rake test TEST=test/models/micropost_test.rb TESTOPTS="--name test_order_should_be_most_recent_first" Finished in 0.11341s 1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
残念ながらデフォルトの順序が昇順となっているので、このままでは数の小さい値から大きい値にソートされてしまいます (最も古い投稿が最初に表示されてしまいます)。
順序を逆にしたい場合は、一段階低いレベルの技術ではありますが、次のように生のSQLを引数に与える必要があります。
新しい投稿から古い投稿の順になるようソートする。
app/models/micropost.rb
default_scope -> { order(created_at: :desc) }
これでテストが通るようになりました。
$ bundle exec rake test Finished in 1.94684s 53 tests, 222 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【25分】。
次は「11.1.4 マイクロポストを改良する」の「Dependent: destroy」から。
【146日目】【1日20分のRailsチュートリアル】【第11章】UserモデルとMicropostモデルの関連付け
今日は「11.1.3 User/Micropostの関連付け」から。
11.1.3 User/Micropostの関連付け
それぞれのマイクロポストは1人のユーザーと関連付けられ、それぞれのユーザーは (潜在的に) 複数のマイクロポストと関連付けられます。
ふむふむ。
UserとそのMicropostは1対多の関係、と。
これらのメソッドは使うと、紐付いているユーザーを通してマイクロポストを作成することができます (慣習的に正しい方法です)。
なんか不思議…!でもWebアプリケーションの作りには合ってる感じする。
前回の記事の「慣習的に間違っている」行はこのことらしい。
Micropostモデルの方では、belongs_to :userというコードが必要になるのですが、これは リスト11.1のマイグレーションによって自動的に生成されているはずです (リスト11.9)。 一方、Userモデルの方では、has_many :micropostsと追加する必要があります。ここは自動的に生成されないので、手動で追加してください (リスト11.10)。
Micropostモデルは下記コードが元々生成されていることを確認。
app/models/micropost.rb
class Micropost < ActiveRecord::Base belongs_to :user : end
Userモデルは自動的に生成されないので下記コードを追加。
app/models/user.rb
class User < ActiveRecord::Base has_many :microposts : end
正しく関連付けができたら、リスト11.2のsetupメソッドを修正して、慣習的に正しくマイクロポストを作成してみます (リスト11.11)。
慣習的云々のコードを下記のように修正。
test/models/micropost_test.rb
@micropost = @user.microposts.build(content: "Lorem ipsum")
テストも問題なし。
$ bundle exec rake test 52 tests, 221 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【17分】。
次は「11.1.4 マイクロポストを改良する」から。
【145日目】【1日20分のRailsチュートリアル】【第11章】Micropostモデルのバリデーション
今日は「11.1.2 Micropostのバリデーション」から。
11.1.2 Micropostのバリデーション
Micropostの初期テストはUserモデルの初期テスト (リスト6.7) と似ています。
まずはMicropostモデル単体のテストを作成する。
test/models/micropost_test.rb
require 'test_helper' class MicropostTest < ActiveSupport::TestCase def setup @user = users(:michael) # 次の行は慣習的に間違っている @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id) end test "should be valid" do assert @micropost.valid? end test "user id should be present" do @micropost.user_id = nil assert_not @micropost.valid? end end
慣習的に間違っているコードは後で修正するそう。
また、有効性に対するテストは成功しますが、存在性に対するテストは失敗するはずです。これは、Micropostモデルのバリデーションがまだ何もないことが原因です。
"test_user_id_should_be_present"
のテストで失敗しますね。
$ bundle exec rake test:models FAIL["test_user_id_should_be_present", MicropostTest, 2017-05-26 15:23:17 +0000] test_user_id_should_be_present#MicropostTest (1495812197.48s) Expected true to be nil or false test/models/micropost_test.rb:17:in `block in <class:MicropostTest>' 14 tests, 23 assertions, 1 failures, 0 errors, 0 skips
これを修正するためには、ユーザーidに対するバリデーションを追加する必要があります (リスト11.4)。
Micropostモデルにバリデーションを追加。
app/models/micropost.rb
class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true end
これでテストが通るようになりました。
$ bundle exec rake test:models 14 tests, 23 assertions, 0 failures, 0 errors, 0 skips
user_id属性と同様に、content属性も存在する必要があり、さらにマイクロポストが140文字より長くならないよう制限を加えます。
content属性とマイクロポストの制限に関するテストを追加。
test/models/micropost_test.rb
test "content should be present" do @micropost.content = " " assert_not @micropost.valid? end test "content should be at most 140 characters" do @micropost.content = "a" * 141 assert_not @micropost.valid? end
バリデーション追加してないのでテストは失敗する。
$ bundle exec rake test:models FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 2017-05-26 15:23:17 +0000] test_content_should_be_at_most_140_characters#MicropostTest (1495812197.13s) Expected true to be nil or false test/models/micropost_test.rb:27:in `block in <class:MicropostTest>' FAIL["test_content_should_be_present", MicropostTest, 2017-05-26 15:23:17 +0000] test_content_should_be_present#MicropostTest (1495812197.13s) Expected true to be nil or false test/models/micropost_test.rb:22:in `block in <class:MicropostTest>' 16 tests, 25 assertions, 2 failures, 0 errors, 0 skips
これに対応するアプリケーション側の実装は、Userのname用バリデーション (リスト6.16) と全く同じです。
Userモデルと同じようなバリデーションをMicropostモデルに追加。
app/models/micropost.rb
validates :content, presence: true, length: { maximum: 140 }
これでテストは通るようになりました。
$ bundle exec rake test:models 16 tests, 25 assertions, 0 failures, 0 errors, 0 skips
今日の学習時間は【18分】。
次は「11.1.3 User/Micropostの関連付け」から。
【144日目】【1日20分のRailsチュートリアル】【第11章】Micropostモデルを生成する
今日は「第11章 ユーザーのマイクロポスト」から。ようやく第11章…!
第11章 ユーザーのマイクロポスト
全ての準備が整った今、ユーザーが短いメッセージを投稿できるようにするためのリソース「マイクロポスト」を追加していきます。
Twitterみたいなアプリケーションにするそう。
11.1 Micropostモデル
Git をバージョン管理に使っている場合は、いつものようにトピックブランチを作成しておきましょう。
作成しておきましょう。
$ git checkout master $ git checkout -b user-microposts
11.1.1 基本的なモデル
Micropostモデルは、マイクロポストの内容を保存するcontent属性と、特定のユーザーとマイクロポストを関連付けるuser_id属性の2つの属性だけを持ちます。
基本的な属性はこの2つらしい。
図11.1のモデルでは、マイクロポストの投稿にString型ではなくtext型を使っている点に注目してください。
文字列を扱うにも色んな型があるんだな…。
リスト6.1でUserモデルを生成したときと同様に、Railsのgenerate modelコマンドを使ってMicropostモデルを生成してみます。
Micropostモデルを生成してみました。
$ rails generate model Micropost content:text user:references invoke active_record create db/migrate/20170525023044_create_microposts.rb create app/models/micropost.rb invoke test_unit create test/models/micropost_test.rb create test/fixtures/microposts.yml
このgenerateコマンドはmicropostsテーブルを作成するためのマイグレーションファイルを生成します。
マイグレーションファイル生成されてますね。
user_idとcreated_at両方のカラムを1つの配列に含めることで、Active Recordで両方のキーを同時に使用する複合キーインデックスを作成できます。
うーん、データベースから検索しやすくするため、って感じかな。。。たぶん
下記をマイグレーションファイルに追加することでuser_idとcreated_atカラムにインデックスが付与できる?っぽい。
db/migrate/[timestamp]_create_microposts.rb
add_index :microposts, [:user_id, :created_at]
マイグレーションしてデータベースを更新。
$ bundle exec rake db:migrate
:
-- create_table(:microposts)
-- add_index(:microposts, [:user_id, :created_at])
:
今日の学習時間は【23分】。
次は「11.1.2 Micropostのバリデーション」から。
【143日目】【1日20分のRailsチュートリアル】【第10章】演習の3.と期限切れの比較の証明
今日は「10.5 演習」の3.から。
10.5 演習
3.リスト10.42では、activateメソッドとcreate_reset_digestメソッドの両方でupdate_attributeを呼び出しており、それぞれのアクセスによってデータベーストランザクションが個別に発生してしまう点が残念です。
リスト10.59のテンプレートに記入することで、個別のupdate_attribute呼び出しを単一のupdate_columns呼び出しに統合し、データベースアクセスが1回で済むようにしてください。
変更後にテストを実行し、GREENになることを確認してください。
2行で書いてるupdate_attribute(=2回データベースアクセスが発生する)をupdate_columnsで1回のデータベースアクセスにしよう、ってことか。
まとめるだけだから引数は変えなくて問題ないかな。
app/models/user.rb
class User < ActiveRecord::Base : # アカウントを有効にする def activate update_columns(activated: true, activated_at: Time.zone.now) end : # パスワード再設定の属性を設定する def create_reset_digest self.reset_token = User.new_token update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now) end
テスト実行して問題ないことを確認。
$ bundle exec rake test 48 tests, 217 assertions, 0 failures, 0 errors, 0 skips
演習終わり!
10.6 証明: 期限切れの比較
この説では、10.2.4で用いたパスワード期限切れの期間の比較が正しいことを証明します。
私の中では納得できている話なのでさらっと読むだけ。
数式で書くと順を追って理解できるから分かりやすいね。
今日の学習時間は【14分】。
これにて第10章終わり!
次は「第11章 ユーザーのマイクロポスト」から。