ぞえの技術めも

Ruby on Rails勉強中

【146日目】【1日20分のRailsチュートリアル】【第11章】UserモデルとMicropostモデルの関連付け

Ruby on Railsチュートリアル(第3版)

今日は「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モデルのバリデーション

Ruby on Railsチュートリアル(第3版)

今日は「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モデルを生成する

Ruby on Railsチュートリアル(第3版)

今日は「第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.と期限切れの比較の証明

Ruby on Railsチュートリアル(第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章 ユーザーのマイクロポスト」から。

【142日目】【1日20分のRailsチュートリアル】【第10章】演習の2.の/users/:idの統合テストを作成する

Ruby on Railsチュートリアル(第3版)

今日は「10.5 演習」の2.の/users/:idの統合テストを作成するところから。

10.5 演習

応用問題: /usersと/users/:id両方の統合テストを作成してください。

昨日の続き。/users/:idの統合テスト作成について考えます。

とりあえずテストが通ったコードはコチラ。

テスト追加するファイルがコレジャナイ感が拭えない。。。

test/integration/users_index_test.rb

  test "user_path as non-activated" do
    log_in_as(@admin)
    # 無効なユーザー
    @admin.toggle!(:activated)
    get user_path(@admin)
    assert_redirected_to root_path
    @admin.toggle!(:activated)
  end
$ bundle exec rake test
48 tests, 217 assertions, 0 failures, 0 errors, 0 skips

あーでもないこーでもないと考えてたら時間切れ。

今日の学習時間は【18分】

次は「10.5 演習」の3.から。

【141日目】【1日20分のRailsチュートリアル】【第10章】演習の2.

Ruby on Railsチュートリアル(第3版)

今日は「10.5 演習」の2.から。

10.5 演習

2.現在は、/usersのユーザーインデックスページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。
しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト10.58のテンプレートに記入して、この動作を変更してください。
なお、ここで使用するActive Recordのwhereメソッドについては、11.3.3でもう少し詳しく説明します。応用問題: /usersと/users/:id両方の統合テストを作成してください。

whereメソッドはよく分からないけど、SQLでSELECT文使うときのWHERE句みたいなものかな。。。また後で。

穴埋めするとこんな感じかなぁ。。。

app/controllers/users_controller.rb

  def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
  end

応用問題: /usersと/users/:id両方の統合テストを作成してください。

なんて難しい。。。。

/usersについて統合テスト作成してみたけどこんなのでいいのだろうか。。。ファイルもこれでいいのかよく分かってない…。
とりあえずテストは通った。

まぁまずは自分で書いてみるのも勉強だよね。答え合わせはまたいつか。

test/integration/users_index_test.rb

  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)
  end
   :
  test "index as non-activated" do
    log_in_as(@admin)
    # 無効なユーザー
    @admin.toggle!(:activated)
    get users_path
    assert_template 'users/index'
    assert_select 'a', text: @admin.name, count: 0
    @admin.toggle!(:activated)
  end
$ bundle exec rake test
47 tests, 215 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【35分】

次は「10.5 演習」の2.の/users/:idの統合テストを作成するところから。

【140日目】【1日20分のRailsチュートリアル】【第10章】10章のまとめと演習の1.

Ruby on Railsチュートリアル(第3版)

前回からだいぶ間が空いてしまったけど復活。
今日は「10.4 最後に」から。

10.4 最後に

アカウント有効化機能とパスワード再設定機能が追加されたことで、ついにサンプルアプリケーションの登録、ログイン、ログアウト機能がすべて本格的に実装完了しました。

アプリケーションの本機能ではないけど、アカウント管理には必須な機能がこれで実装完了したのか…!

10.4.1 本章のまとめ

そういえばGitへのコミットは…!?って思いかけたけど、前回してました。そうだった。

10.5 演習

演習とチュートリアル本編の食い違いを避ける方法については、3.6のトピックブランチの演習に追加したメモをご覧ください。

演習用にブランチ作成しておきます。

$ git checkout account-activation-password-reset
$ git checkout -b account-activation-password-reset-exercises

1.リスト10.57のテンプレートを埋めて、期限切れのパスワード再設定のブランチ (リスト10.52) の統合テストを作成してください (10.57 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。
期限切れのテスト方法はさまざまですが、リスト10.57でおすすめした手法 (大文字小文字は区別されません) を使えば、レスポンスの本文に「expired」という語があるかどうかをチェックできます。

"Password reset has expired."っていうエラーメッセージの全文でなくてもいいのかな。。。まぁ一部でいいか。

リスト10.57をほぼコピペして一部埋めてみた。
assert_match /expired/i, response.bodyIDEのワーニング出てるんだよね…合ってると思うんだけどな。。。

正規表現/iについては下記記事が詳しかった。/iが大文字小文字区別されないための記法か。

www.rubylife.jp

test/integration/password_resets_test.rb

  test "expired token" do
    get new_password_reset_path
    post password_resets_path, password_reset: { email: @user.email }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token),
          email: @user.email,
          user: { password:              "foobar",
                  password_confirmation: "foobar" }
    assert_response :redirect
    follow_redirect!
    assert_match /expired/i, response.body
  end

テストは通ったのでワーニングは無視します。気になるけど…

$ bundle exec rake test
46 tests, 213 assertions, 0 failures, 0 errors, 0 skips

今日の学習時間は【25分】

次は「10.5 演習」の2.から。