おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

Ruby on Rails チュートリアルで30歳までに人生を変える(第14章 完)

こんにちは。opiyoです。

今回は、第14章をやっていきます。

第14章はフォロー、フォロワーする機能を追加します。

なんとなんと最後の章までやってまいりました。

ではでは、早速行ってみましょう。

railsチュートリアル14章の演習解説

14.1.1 演習 データモデルの問題 (および解決策)

14.1.1.1

<問題> 図 14.7のid=1のユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるでしょうか? 想像してみてください。ヒント: 4.3.2で紹介したmap(&:method_name)のパターンを思い出してください。例えばuser.following.map(&:id)の場合、idの配列を返します。

<回答>

[2,7,10,8]

14.1.1.2

<問題>図 14.7を参考にして、id=2のユーザーに対してuser.followingを実行すると、結果はどのようになるでしょうか? また、同じユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるでしょうか? 想像してみてください。

<回答>

[1]

14.1.2 演習 User/Relationshipの関連付け

14.1.2.1

<問題> コンソールを開き、表 14.1のcreateメソッドを使ってActiveRelationshipを作ってみましょう。データベース上に2人以上のユーザーを用意し、最初のユーザーが2人目のユーザーをフォローしている状態を作ってみてください。

<回答>

> user_a.active_relationships.create(followed_id: user_b.id)
   (0.1ms)  begin transaction
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 101], ["LIMIT", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 102], ["LIMIT", 1]]
  SQL (7.1ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 101], ["followed_id", 102], ["created_at", 2017-08-31 01:06:19 UTC], ["updated_at", 2017-08-31 01:06:19 UTC]]
   (1.9ms)  commit transaction
=> #<Relationship:0x007fdb1fbac6b8 id: 88, follower_id: 101, followed_id: 102, created_at: Thu, 31 Aug 2017 01:06:19 UTC +00:00, updated_at: Thu, 31 Aug 2017 01:06:19 UTC +00:00>

14.1.2.2

<問題> 先ほどの演習を終えたら、active_relationship.followedの値とactive_relationship.followerの値を確認し、それぞれの値が正しいことを確認してみましょう。

<回答> active_relationship.followedactive_relationship.followerを実行するとエラーになるので確認できない。

# 14.1.4 フォローしているユーザー で出てくるやり方を使えば、フォローしている、していないの確認ができそう!
> user_a.following?(user_b)
  User Exists (13.0ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 101], ["id", 102], ["LIMIT", 1]]
=> true

14.1.3 演習 Relationshipのバリデーション

14.1.3.1

<問題>リスト 14.5のバリデーションをコメントアウトしても、テストが成功したままになっていることを確認してみましょう。(以前のRailsのバージョンでは、このバリデーションが必須でしたが、Rails 5から必須ではなくなりました。今回はフォロー機能の実装を優先しますが、この手のバリデーションが省略されている可能性があることを頭の片隅で覚えておくと良いでしょう。)

<回答> コメントアウトしてもテストは成功することを確認

14.1.4 演習 フォローしているユーザー

14.1.4.1

<問題>コンソールを開き、リスト 14.9のコードを順々に実行してみましょう。

<回答>

> michael = User.find_by(name: "michael")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "michael"], ["LIMIT", 1]]
=> #<User:0x007fdb1b727628
 id: 103,
 name: "michael",
 email: "michael@co.jp",
 created_at: Thu, 31 Aug 2017 02:00:21 UTC +00:00,
 updated_at: Thu, 31 Aug 2017 02:00:28 UTC +00:00,
 password_digest: "$2a$10$2hC.1.fUg3T8GUKMX.EnTOGDcIEHpHryFQg69YKmljRALWUeKsyYG",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$rglVP8eQVwnwE/diLFGPyuZJjA0lDGOtMwqSmewgeDIEdxU9N7Fk6",
 activated: true,
 activated_at: Thu, 31 Aug 2017 02:00:28 UTC +00:00,
 reset_digest: nil,
 reset_sent_at: nil>

[50] pry(main)> archer = User.find_by(name: "archer")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "archer"], ["LIMIT", 1]]
=> #<User:0x007fdb19dfc1a8
 id: 104,
 name: "archer",
 email: "archer@co.jp",
 created_at: Thu, 31 Aug 2017 02:00:39 UTC +00:00,
 updated_at: Thu, 31 Aug 2017 02:00:41 UTC +00:00,
 password_digest: "$2a$10$QMPgyffkll/ps.Mh798Kd.97jtJa.gW3mjEGmLlAme5tmL//IhXJG",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$PygPxQjeBFgeXIKwPuQcqOIbwLevHEs2QXs5Tyvn6m05T7w7Vsc4C",
 activated: true,
 activated_at: Thu, 31 Aug 2017 02:00:41 UTC +00:00,
 reset_digest: nil,
 reset_sent_at: nil>


[51] pry(main)> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 103], ["id", 104], ["LIMIT", 1]]
=> false

[52] pry(main)> michael.follow(archer)
   (0.1ms)  begin transaction
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 103], ["LIMIT", 1]]
  User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 104], ["LIMIT", 1]]
  SQL (14.0ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 103], ["followed_id", 104], ["created_at", 2017-08-31 02:01:33 UTC], ["updated_at", 2017-08-31 02:01:33 UTC]]
   (1.9ms)  commit transaction
=> #<Relationship:0x007fdb221c9620 id: 89, follower_id: 103, followed_id: 104, created_at: Thu, 31 Aug 2017 02:01:33 UTC +00:00, updated_at: Thu, 31 Aug 2017 02:01:33 UTC +00:00>

[53] pry(main)> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 103], ["id", 104], ["LIMIT", 1]]
=> true

[54] pry(main)> michael.unfollow(archer)
  Relationship Load (0.2ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 103], ["followed_id", 104], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  SQL (0.3ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 89]]
   (1.8ms)  commit transaction
=> #<Relationship:0x007fdb22bae5f0 id: 89, follower_id: 103, followed_id: 104, created_at: Thu, 31 Aug 2017 02:01:33 UTC +00:00, updated_at: Thu, 31 Aug 2017 02:01:33 UTC +00:00>

[55] pry(main)> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 103], ["id", 104], ["LIMIT", 1]]
=> false

14.1.4.2

<問題>先ほどの演習の各コマンド実行時の結果を見返してみて、実際にはどんなSQLが出力されたのか確認してみましょう。

<回答>

> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 103], ["id", 104], ["LIMIT", 1]]

> michael.follow(archer)
   (0.1ms)  begin transaction
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 103], ["LIMIT", 1]]
  User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 104], ["LIMIT", 1]]
  SQL (14.0ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 103], ["followed_id", 104], ["created_at", 2017-08-31 02:01:33 UTC], ["updated_at", 2017-08-31 02:01:33 UTC]]

> michael.unfollow(archer)
  Relationship Load (0.2ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 103], ["followed_id", 104], ["LIMIT", 1]]
  SQL (0.3ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 89]]

14.1.5 演習 フォロワー

14.1.5.1

<問題>コンソールを開き、何人かのユーザーが最初のユーザーをフォローしている状況を作ってみてください。最初のユーザーをuserとすると、user.followers.map(&:id)の値はどのようになっているでしょうか?

<回答>

# ランダムに6人取ってくる
> users = User.order("RANDOM()").limit(6)

# その6人が一番目のユーザーをフォローする
> users.each {|user| user.follow(User.first)}

# 一番目のユーザーのフォロワーを確認
> User.first.followers.map(&:id)
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
  User Load (0.2ms)  SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> [36, 20, 60, 74, 87, 53]

14.1.5.2

<問題>上の演習が終わったら、user.followers.countの実行結果が、先ほどフォローさせたユーザー数と一致していることを確認してみましょう。

<回答>

> User.first.followers.count
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 6

14.1.5.2

<問題>user.followers.countを実行した結果、出力されるSQL文はどのような内容になっているでしょうか? また、user.followers.to_a.countの実行結果と違っている箇所はありますか? ヒント: もしuserに100万人のフォロワーがいた場合、どのような違いがあるでしょうか? 考えてみてください。

<回答>

> User.first.followers.to_a.count
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
  User Load (0.1ms)  SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 6

to_a付けても変化はない。が、多分配列にした数を数える感じになるから遅くなる。

14.2.1 演習 フォローのサンプルデータ

14.2.1.1

<問題>コンソールを開き、User.first.followers.countの結果がリスト 14.14で期待している結果と合致していることを確認してみましょう。

<回答>

> User.first.followers.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 38

> (3..40).size
=> 38

14.2.1.2

<問題>先ほどの演習と同様に、User.first.following.countの結果も合致していることを確認してみましょう。

<回答>

> User.first.following.count
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> 49

> (2..50).count
=> 49

14.2.2 演習 統計と [Follow] フォーム

14.2.2.1

<問題>ブラウザから /users/2 にアクセスし、フォローボタンが表示されていることを確認してみましょう。同様に、/users/5 では [Unfollow] ボタンが表示されているはずです。さて、/users/1 にアクセスすると、どのような結果が表示されるでしょうか?

<回答> - /users/2:フォローボタンが表示 - /users/5:アンフォローボタンが表示 - /users/1:何も表示されない(ログインユーザーだから)

14.2.2.1

<問題>ブラウザからHomeページとプロフィールページを表示してみて、統計情報が正しく表示されているか確認してみましょう。

<回答> 表示されている。

14.2.2.1

<問題>Homeページに表示されている統計情報に対してテストを書いてみましょう。ヒント: リスト 13.28に示したテストを追加してみてください。同様にして、プロフィールページにもテストを追加してみましょう。

<回答>

# test/integration/users_profile_test.rb
require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:michael)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.following.count.to_s, response.body
    assert_match @user.followers.count.to_s, response.body
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination'
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
end

14.2.3 演習 [Following] と [Followers] ページ

14.2.3.1

<問題>ブラウザから /users/1/followers と /users/1/following を開き、それぞれが適切に表示されていることを確認してみましょう。サイドバーにある画像は、リンクとしてうまく機能しているでしょうか?

<回答> 表示されている。また、画像のリンクも機能している。

14.2.3.1

<問題>リスト 14.29のassert_selectに関連するコードをコメントアウトしてみて、テストが正しく red に変わることを確認してみましょう。

<回答>

# app/views/users/show_follow.html.erb
        <div class="user_avatars">
          <% @users.each do |user| %>
            <%= link_to gravatar_for(user, size: 30), user %>
          <% end %>
        </div>

多分ここだと思う。assert_selectで確認しているのはaタグのリンク先とユーザーのパス(users/5)が合致しているかだから。

14.2.4 演習 [Follow] ボタン (基本編)

14.2.4.1

<問題>ブラウザ上から /users/2 を開き、[Follow] と [Unfollow] を実行してみましょう。うまく機能しているでしょうか?

<回答> followersの数字が変わるし、ボタンも変わるので機能している。

14.2.4.1

<問題>先ほどの演習を終えたら、Railsサーバーのログを見てみましょう。フォロー/フォロー解除が実行されると、それぞれどのテンプレートが描画されているでしょうか?

<回答> users/show.html.erbが描画される

14.2.5 演習 [Follow] ボタン (Ajax編)

14.2.5.1

<問題>ブラウザから /users/2 にアクセスし、うまく動いているかどうか確認してみましょう。

<回答> うまく動いていることを確認。

14.2.5.2

<問題>先ほどの演習で確認が終わったら、Railsサーバーのログを閲覧し、フォロー/フォロー解除を実行した直後のテンプレートがどうなっているか確認してみましょう。

<回答> relationships/create.js.erbrelationships/destroy.js.erbが呼ばれているログが見つからない。

14.2.6 演習 フォローをテストする

14.2.6.1

<問題>リスト 14.36のrespond_toブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認してみましょう。実際、どのテストケースが落ちたでしょうか?

<回答> - format.html { redirect_to @user and return}の場合は、htmlを呼び出すテストが失敗する - 28: "should follow a user the standard way" - 40: "should unfollow a user the standard way"

  • format.jsの場合は、jsを呼び出すテストが失敗しない

14.2.5.2といい、ちゃんと呼ばれてないのかな?

14.2.6.2

<問題>リスト 14.40のxhr: trueがある行のうち、片方のみを削除するとどういった結果になるでしょうか? このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのか考えてみてください。

<回答> format.htmlformat.jsのどちらかの行を削除した時ってことかな? その場合は、14.2.6.1の回答と同じになっちゃうな。 とりあえずパス。

14.3.1 演習 動機と計画

14.3.1.1

<問題>マイクロポストのidが正しく並んでいると仮定して (すなわち若いidの投稿ほど古くなる前提で)、図 14.22のデータセットでuser.feed.map(&:id)を実行すると、どのような結果が表示されるでしょうか? 考えてみてください。ヒント: 13.1.4で実装したdefault_scopeを思い出してください。

<回答>

[10,9,7,5,4,2,1]

14.3.2 演習 フィードを初めて実装する

14.3.2.1

<問題>リスト 14.44において、現在のユーザー自身の投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?

<回答>

  def feed
    following_ids = "SELECT followed_id FROM relationships
      WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids})", user_id: id)
  end
# 取得件数が減ったからページネーションされないってことじゃないかな?
 FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.4457496699978947]
 test_micropost_interface#MicropostsInterfaceTest (1.45s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/microposts_interface_test.rb:12:in `block in <class:MicropostsInterfaceTest>'

# こっちが今回で欲しかったエラーだね
 FAIL["test_feed_should_have_the_right_posts", UserTest, 1.505586264996964]
 test_feed_should_have_the_right_posts#UserTest (1.51s)
        Expected false to be truthy.
        test/models/user_test.rb:106:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:105:in `block in <class:UserTest>'

14.3.2.2

<問題>リスト 14.44において、フォローしているユーザーの投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?

<回答>

def feed
  Micropost.where(user_id: id)
end
FAIL["test_feed_should_have_the_right_posts", UserTest, 3.7484980859990173]
 test_feed_should_have_the_right_posts#UserTest (3.75s)
        Expected false to be truthy.
        test/models/user_test.rb:102:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:101:in `block in <class:UserTest>'

14.3.2.3

<問題>リスト 14.44において、フォローしていないユーザーの投稿を含めるためにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか? ヒント: 自分自身とフォローしているユーザー、そしてそれ以外という集合は、いったいどういった集合を表すのか考えてみてください。

<回答>

ようは全部!ってことだと思うんだが。

def feed
  Micropost.all
end
 FAIL["test_feed_should_have_the_right_posts", UserTest, 6.150512954001897]
 test_feed_should_have_the_right_posts#UserTest (6.15s)
        Expected true to be nil or false
        test/models/user_test.rb:110:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:109:in `block in <class:UserTest>'

14.3.3 演習 サブセレクト

14.3.3.1

<問題>Homeページで表示される1ページ目のフィードに対して、統合テストを書いてみましょう。リスト 14.49はそのテンプレートです。

<回答>

# test/integration/following_test.rb
  test "feed on Home page" do
    get root_path
    @user.feed.paginate(page: 1).each do |micropost|
      assert_match CGI.escapeHTML(micropost.content), response.body
    end
  end

14.3.3.2

<問題>リスト 14.49のコードでは、期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしています (このメソッドは11.2.3で扱ったCGI.escapeと同じ用途です)。このコードでは、なぜHTMLをエスケープさせる必要があったのでしょうか? 考えてみてください。ヒント: 試しにエスケープ処理を外して、得られるHTMLの内容を注意深く調べてください。マイクロポストの内容が何かおかしいはずです。また、ターミナルの検索機能 (Cmd-FもしくはCtrl-F) を使って「sorry」を探すと原因の究明に役立つはずです。

<回答>

(byebug) CGI.escapeHTML(micropost.content)
"I&#39;m sorry. Your words made sense, but your sarcastic tone did not." # 「'」これがエスケープされる、されないの違い
(byebug) micropost.content
"I'm sorry. Your words made sense, but your sarcastic tone did not."

Ruby on Rails チュートリアルで30歳までに人生を変える(第13章)

こんにちは。opiyoです。

今回は、第13章をやっていきます。

第13章はユーザーが短いメッセージを投稿できる「マイクロポスト」機能を追加します。

やっとログイン関係の処理を抜けて、機能拡張ですね!

ではでは、早速行ってみましょう。

railsチュートリアル13章の演習解説

13.1.1 演習 基本的なモデル

13.1.1.1

<問題>RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム (created_atとupdated_at) には何が入っているでしょうか?

<回答>

> micropost = Micropost.new
=> #<Micropost:0x007fbbfc35b870 id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil, picture: nil>
[3] pry(main)> micropost.user_id = User.first.id
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> 1
[4] pry(main)> micropost.content = "Lorem ipsum"
=> "Lorem ipsum"
[5] pry(main)> micropost
=> #<Micropost:0x007fbbfc35b870 id: nil, content: "Lorem ipsum", user_id: 1, created_at: nil, updated_at: nil, picture: nil>

created_atとupdated_atには何も入ってない

13.1.1.2

<問題>先ほど作ったオブジェクトを使って、micropost.userを実行してみましょう。どのような結果が返ってくるでしょうか? また、micropost.user.nameを実行した場合の結果はどうなるでしょうか?

<回答>

> micropost.user
  User Load (0.6ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User:0x007fbc0396b358
 id: 1,
 name: "Example User",
 email: "example@railstutorial.org",
 created_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 updated_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 password_digest: "$2a$10$zjV8yVOjS/lZaq6t2I0Ae.xOrbyTAE/G18QeHKZw/72vRIFOOaSl6",
 remember_digest: nil,
 admin: true,
 activation_digest: "$2a$10$WVoTuhmujpxjL.agLrTGeeZy/56retMcUz2fYMe.8YWfBPf.8ZnRq",
 activated: true,
 activated_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 reset_digest: nil,
 reset_sent_at: nil>

[7] pry(main)> micropost.user.name
=> "Example User"

13.1.1.3

<問題>先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?

<回答>

> micropost.save
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", 2017-08-30 01:19:30 UTC], ["updated_at", 2017-08-30 01:19:30 UTC]]
   (2.5ms)  commit transaction
=> true
[11] pry(main)> micropost
=> #<Micropost:0x007fbbfc35b870 id: 301, content: "Lorem ipsum", user_id: 1, created_at: Wed, 30 Aug 2017 01:19:30 UTC +00:00, updated_at: Wed, 30 Aug 2017 01:19:30 UTC +00:00, picture: nil>

マジックカラムであるcreated_atとupdated_atに値が入った!!

13.1.2 演習 Micropostのバリデーション

13.1.2.1

<問題>Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

<回答>

> micropost = Micropost.new
=> #<Micropost:0x007fbbfcdc41b8 id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil, picture: nil>
[2] pry(main)> micropost.valid?
=> false

> micropost.errors.full_messages
=> ["User must exist", "User can't be blank", "Content can't be blank"]

13.1.2.2

<問題>コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

<回答>

> micropost.content = "a" * 141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
> micropost.content.size
=> 141

> micropost.valid?
=> false
[16] pry(main)> micropost.errors.full_messages
=> ["User must exist", "User can't be blank", "Content is too long (maximum is 140 characters)"]

13.1.3 演習 User/Micropostの関連付け

13.1.3.1

<問題>データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?

<回答>

> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x007fbc06359de0
 id: 1,
 name: "Example User",
 email: "example@railstutorial.org",
 created_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 updated_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 password_digest: "$2a$10$zjV8yVOjS/lZaq6t2I0Ae.xOrbyTAE/G18QeHKZw/72vRIFOOaSl6",
 remember_digest: nil,
 admin: true,
 activation_digest: "$2a$10$WVoTuhmujpxjL.agLrTGeeZy/56retMcUz2fYMe.8YWfBPf.8ZnRq",
 activated: true,
 activated_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 reset_digest: nil,
 reset_sent_at: nil>
[30] pry(main)> micropost = user.microposts.create(content: "Lorem ipsum")
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", 2017-08-30 01:40:35 UTC], ["updated_at", 2017-08-30 01:40:35 UTC]]
   (2.5ms)  commit transaction
=> #<Micropost:0x007fbc06409380 id: 302, content: "Lorem ipsum", user_id: 1, created_at: Wed, 30 Aug 2017 01:40:35 UTC +00:00, updated_at: Wed, 30 Aug 2017 01:40:35 UTC +00:00, picture: nil>

13.1.3.2

<問題>先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?

<回答>

> user.microposts.find(micropost.id)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["id", 302], ["LIMIT", 1]]
=> #<Micropost:0x007fbc05d60240 id: 302, content: "Lorem ipsum", user_id: 1, created_at: Wed, 30 Aug 2017 01:40:35 UTC +00:00, updated_at: Wed, 30 Aug 2017 01:40:35 UTC +00:00, picture: nil>

[43] pry(main)> user.microposts.find(micropost)
DEPRECATION WARNING: You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. (called from <main> at (pry):18)
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["id", 302], ["LIMIT", 1]]
=> #<Micropost:0x007fbc051de5c0 id: 302, content: "Lorem ipsum", user_id: 1, created_at: Wed, 30 Aug 2017 01:40:35 UTC +00:00, updated_at: Wed, 30 Aug 2017 01:40:35 UTC +00:00, picture: nil>

13.1.3.3

<問題>user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。

<回答>

> user == micropost.user
=> true
[47] pry(main)> user.microposts.first == micropost
=> true

13.1.4 演習 マイクロポストを改良する

13.1.4.1

<問題>Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。

<回答>

> Micropost.first.created_at.to_s
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> "2017-08-30 01:40:35 UTC"
[14] pry(main)> Micropost.last.created_at.to_s
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> "2017-08-30 01:05:42 UTC"

あー僕これ勘違いしてた。firstで取得した時はid順で取ってくると思ってた。 default_scope設定してるんだから、そりゃー降順で取ってくるわな。 きちんと発行されたSQL見るのは大事ですね。

13.1.4.2

<問題>Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか? ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。

<回答>

> Micropost.first
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
[19] pry(main)> Micropost.last
  Micropost Load (0.4ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]

firstは、DESC。lastは、ASCで発行されていることが分かる。

13.1.4.3

<問題>データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。

<回答>

# 最初に投稿したマイクロポスト
> user.microposts.last
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> #<Micropost:0x007fbc00dda3b0
 id: 1,
 content: "Cum consequatur enim quibusdam aliquid corrupti reprehenderit et.",
 user_id: 1,
 created_at: Wed, 30 Aug 2017 01:05:42 UTC +00:00,
 updated_at: Wed, 30 Aug 2017 01:05:42 UTC +00:00,
 picture: nil>

# ユーザーをdestroy
> user.destroy
   (0.1ms)  begin transaction
  Micropost Load (0.6ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
  SQL (0.3ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 302]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 301]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 295]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 289]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 283]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 277]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 271]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 265]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 259]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 253]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 247]]
  SQL (0.2ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 241]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 235]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 229]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 223]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 217]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 211]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 205]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 199]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 193]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 187]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 181]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 175]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 169]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 163]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 157]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 151]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 145]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 139]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 133]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 127]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 121]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 115]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 109]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 103]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 97]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 91]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 85]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 79]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 73]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 67]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 61]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 55]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 49]]
  SQL (0.0ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 43]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 37]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 31]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 25]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 19]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 13]]
  SQL (0.2ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 7]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 1]]
  SQL (0.2ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
   (3.1ms)  commit transaction
=> #<User:0x007fbc04851110
 id: 1,
 name: "Example User",
 email: "example@railstutorial.org",
 created_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 updated_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 password_digest: "$2a$10$zjV8yVOjS/lZaq6t2I0Ae.xOrbyTAE/G18QeHKZw/72vRIFOOaSl6",
 remember_digest: nil,
 admin: true,
 activation_digest: "$2a$10$WVoTuhmujpxjL.agLrTGeeZy/56retMcUz2fYMe.8YWfBPf.8ZnRq",
 activated: true,
 activated_at: Wed, 30 Aug 2017 01:05:28 UTC +00:00,
 reset_digest: nil,
 reset_sent_at: nil>

> Micropost.find(1)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["id", 1], ["LIMIT", 1]]
ActiveRecord::RecordNotFound: Couldn't find Micropost with 'id'=1

> user.find(1)
NoMethodError: undefined method `find' for #<User:0x007fbc04851110>

13.2.1 演習 マイクロポストの描画

13.2.1.1

<問題>7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.agoや6.months.agoを実行してみましょう。

<回答>

> helper.time_ago_in_words(3.weeks.ago)
=> "21 days"
[48] pry(main)> helper.time_ago_in_words(6.months.ago)
=> "6 months"

13.2.1.2

<問題>helper.time_ago_in_words(1.year.ago)と実行すると、どういった結果が返ってくるでしょうか?

<回答>

> helper.time_ago_in_words(1.year.ago)
=> "about 1 year"

13.2.1.3

<問題>micropostsオブジェクトのクラスは何でしょうか? ヒント: リスト 13.23内のコードににあるように、まずはpaginateメソッド (引数はpage: nil) でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。

<回答>

> user = User.first
> microposts = user.microposts.paginate(page: 1)
> microposts.class
=> Micropost::ActiveRecord_AssociationRelation

13.2.2 演習 マイクロポストのサンプル

13.2.2.1

<問題>(1..10).to_a.take(6)というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。

<回答>

> (1..10).to_a.take(6)
=> [1, 2, 3, 4, 5, 6]

13.2.2.2

<問題>先ほどの演習にあったto_aメソッドの部分は本当に必要でしょうか? 確かめてみてください。

<回答>

> (1..10).take(6)
=> [1, 2, 3, 4, 5, 6]

> (1..10).class
=> Range
[72] pry(main)> (1..10).to_a.class
=> Array

13.2.2.3

<問題>Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に大学名や電話番号、Hipster IpsumやChuck Norris facts (参考: チャック・ノリスの真実) を画面に出力してみましょう。(訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力するfaker-okinawaもあります。ぜひ遊んでみてください。)

<回答>

> Faker::Cat.name
=> "Shadow"

> Faker::SlackEmoji.people
=> ":stuck_out_tongue_winking_eye:"

> Faker::Music.key
=> "Fb"

13.2.3 演習 プロフィール画面のマイクロポストをテストする

13.2.3.1

<問題>リスト 13.28にある2つの’h1’のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが green から redに変わることを確認してみてください。

<回答>

# app/views/users/show.html.erb
5      <!-- <h1>
6        <%= gravatar_for @user %>
7        <%= @user.name %>
8      </h1> -->

13.2.3.2

<問題>リスト 13.28にあるテストを変更して、will_paginateが1度のみ表示されていることをテストしてみましょう。ヒント: 表 5.2を参考にしてください。

<回答>

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination', count: 1 # ここを修正。
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end

13.3.1 演習 マイクロポストのアクセス制御

13.3.1.1

<問題>なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。

<回答> コードが重複してしまうため

13.3.2 演習 マイクロポストを作成する

13.3.2.1

<問題>Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。

<回答>

# app/views/static_pages/home.html.erb
<% if logged_in? %>
  <%= render 'user_logged_in' %>
<% else %>
  <%= render 'user_logged_in' %>
<% end %>
# app/views/static_pages/_user_logged_in.html.erb
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <%= render 'shared/user_info' %>
    </section>
    <section class="micropost_form">
      <%= render 'shared/micropost_form' %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3>Micropost Feed</h3>
    <%= render 'shared/feed' %>
  </div>
</div>
# app/views/static_pages/_user_not_logged_in.html.erb
<% provide(:title, "Home") %>
<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails logo"), "https://rubyonrails.org/" %>

13.3.3 演習 フィードの原型

13.3.3.1

<問題>新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。

<回答>

INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "hakuの休日"], ["user_id", 101], ["created_at", 2017-08-30 05:01:10 UTC], ["updated_at", 2017-08-30 05:01:10 UTC]]

13.3.3.2

<問題>コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where("user_id = ?", user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。

<回答>

> user.feed
  Micropost Load (0.4ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id=2) ORDER BY "microposts"."created_at" DESC

> Micropost.where("user_id = ?", user.id)
  Micropost Load (0.4ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 2) ORDER BY "microposts"."created_at" DESC

> user.feed == user.microposts
  Micropost Load (0.4ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id=2) ORDER BY "microposts"."created_at" DESC
=> true
[57] pry(main)> Micropost.where("user_id = ?", user.id) == user.microposts
  Micropost Load (0.4ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 2) ORDER BY "microposts"."created_at" DESC
=> true

# なぜか`false`になる。発行されているSQLも一緒なのに。なぜだ?
> Micropost.where("user_id = ?", user.id) == user.feed
=> false

13.3.4 演習 マイクロポストを削除する

13.3.4.1

<問題>マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを見てみて、DELETE文の内容を確認してみてください。

<回答>

DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 303]]

13.3.4.2

<問題>redirect_to request.referrer || root_urlの行をredirect_back(fallback_location: root_url)と置き換えてもうまく動くことを、ブラウザを使って確認してみましょう (このメソッドはRails 5から新たに導入されました)。

<回答> エラーなく削除できることを確認した。

13.3.5 演習 フィード画面のマイクロポストをテストする

13.3.5.1

<問題>リスト 13.55で示した4つのコメント (「無効な送信」など) のそれぞれに対して、テストが正しく動いているか確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが redになることを確認し、元に戻すと greenになることを確認してみましょう。

<回答>

くそー動かない。画面でも再現する。

ERROR["test_micropost_interface", MicropostsInterfaceTest, 3.208587193999847]
 test_micropost_interface#MicropostsInterfaceTest (3.21s)
ActionView::Template::Error:         ActionView::Template::Error: Missing partial microposts/_user_logged_in, application/_user_logged_in with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
          * "/Users/taku/rails/railstutorial/railstutorial_13/app/views"

            app/views/static_pages/home.html.erb:2:in `_app_views_static_pages_home_html_erb___3536680938325879454_70206177007920'
            app/controllers/microposts_controller.rb:12:in `create'
            test/integration/microposts_interface_test.rb:16:in `block (2 levels) in <class:MicropostsInterfaceTest>'
            test/integration/microposts_interface_test.rb:15:in `block in <class:MicropostsInterfaceTest>'

解決した!

# app/views/static_pages/home.html.erb
<% if logged_in? %>
  <%= render 'static_pages/user_logged_in' %> # `user_logged_in`だけ書いてたのが原因。同じディレクトリでもフォルダ名から書く!
<% else %>
  <%= render 'static_pages/user_not_logged_in' %> # `user_logged_in`だけ書いてたのが原因。同じディレクトリでもフォルダ名から書く!
<% end %>

13.3.5.2

<問題>サイドバーにあるマイクロポストの合計投稿数をテストしてみましょう。このとき、単数形 (micropost) と複数形 (microposts) が正しく表示されているかどうかもテストしてください。ヒント: リスト 13.57を参考にしてみてください。

<回答>

# test/integration/microposts_interface_test.rb
require 'test_helper'

class MicropostInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "micropost sidebar count" do
    log_in_as(@user)
    get root_path
    assert_match "#{@user.microposts.count} microposts", response.body # ここを修正
    # まだマイクロポストを投稿していないユーザー
    other_user = users(:malory)
    log_in_as(other_user)
    get root_path
    assert_match "0 microposts", response.body
    other_user.microposts.create!(content: "A micropost")
    get root_path
    assert_match "1 micropost", response.body # ここを修正
  end
end

13.4.1 演習 基本的な画像アップロード

13.4.1.1

<問題>画像付きのマイクロポストを投稿してみましょう。もしかして、大きすぎる画像が表示されてしまいましたか? (心配しないでください、次の13.4.3でこの問題を直します)。

<回答> 問題なくアップロードできる!

13.4.1.2

<問題>リスト 13.63に示すテンプレートを参考に、13.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはサンプル画像をfixtureディレクトリに追加してください (コマンド例: cp app/assets/images/rails.png test/fixtures/)。リスト 13.63で追加したテストでは、Homeページにあるファイルアップロードと、投稿に成功した時に画像が表示されているかどうかをチェックしています。なお、テスト内にあるfixture_file_uploadというメソッドは、fixtureで定義されたファイルをアップロードする特別なメソッドです18。ヒント: picture属性が有効かどうかを確かめるときは、11.3.3で紹介したassignsメソッドを使ってください。このメソッドを使うと、投稿に成功した後にcreateアクション内のマイクロポストにアクセスするようになります。

<回答>

# test/integration/microposts_interface_test.rb
require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_match "#{@user.microposts.count} microposts", response.body
    assert_select "div.pagination"
    assert_select 'input[type=file]'
    # 無効な送信
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: {micropost: {content: ""}}
    end
    assert_select 'div#error_explanation'
    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: {micropost: {content: content, picture: picture}}
    end
    assert assigns(:micropost).picture?
    assert_redirected_to root_url
    follow_redirect!
    assert_match content, response.body
    assert_match "#{@user.microposts.count} micropost", response.body
    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # 違うユーザーのプロフィールにアクセス(削除リンクが無いこと)
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end
end

エラーになるので、改めて確認する。

13.4.2 演習 画像の検証

13.4.2.1

<問題>5MB以上の画像ファイルを送信しようとした場合、どうなりますか?

<回答> メッセージウィンドウが表示される(Maximum file size is 5MB. Please choose a smaller file.)

13.4.2.1

<問題>無効な拡張子のファイルを送信しようとした場合、どうなりますか?

<回答>

Picture translation missing: en.errors.messages.extension_whitelist_error

13.4.3 演習 画像のリサイズ

13.4.3.1

<問題>解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?

<回答> 問題なし

13.4.3.2

<問題>既にリスト 13.63のテストを追加していた場合、この時点でテストスイートを走らせるとエラーメッセージが表示されるようになるはずです。このエラーを取り除いてみましょう。ヒント: リスト 13.68にある設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズをさせないようにしてみましょう。

<回答> - エラーにならない - config/initializers/skip_image_resizing.rbが存在しない

Ruby on Rails チュートリアルで30歳までに人生を変える(第12章)

こんにちは。opiyoです。

今回は、第12章をやっていきます。

第12章はパスワードを忘れた時の再設定方法です。

どうやら難しそうですが、早速行ってみましょう。

railsチュートリアル12章の演習解説

12.1.1 演習 PasswordResetsコントローラ

12.1.1.1

<問題>この時点で、テストスイートが greenになっていることを確認してみましょう。

<回答> 問題なし

12.1.1.2

<問題>表 12.1の名前付きルートでは、pathではなくurlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習 (11.1.1.1) と同じ理由です。

<回答> メールの本文で使うリンクになるので、絶対パス=_urlを使わないとどのサイトなのか参照できないから

12.1.2 演習 新しいパスワードの設定

12.1.2.1

<問題>リスト 12.4のform_forメソッドでは、なぜ@password_resetではなく:password_resetを使っているのでしょうか? 考えてみてください。

<回答> @password_reset(オブジェクト)を使う必要がない。これを使うとオブジェクトの有り・無しによってRailsがよしなにやってくれる。 今回は、入力されたパスワードを使ってDB検索するだけなので一番シンプルな形にするために:password_rest(シンボル)を使ってる。

12.1.3 演習 createアクションでパスワード再設定

12.1.3.1

<問題>試しに有効なメールアドレスをフォームから送信してみましょう (図 12.6)。どんなエラーメッセージが表示されたでしょうか?

<回答> エラーにならない。

12.1.3.2

<問題>コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの) 該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか?

<回答> エラーにならなかったので、再度確認します。

12.2.1 演習 新しいパスワードの設定

12.2.1.1

<問題>ブラウザから、送信メールのプレビューをしてみましょう。「Date」の欄にはどんな情報が表示されているでしょうか?

<回答> Date:Mon, 28 Aug 2017 07:09:54 +0000

12.2.1.2

<問題>パスワード再設定フォームから有効なメールアドレスを送信してみましょう。また、Railsサーバーのログを見て、生成された送信メールの内容を確認してみてください。

<回答> メッセージが表示されエラーなく登録された(Password has been reset.)

Sent mail to haku@co.jp (18.8ms)
Date: Mon, 28 Aug 2017 16:10:44 +0900
From: noreply@example.com
To: haku@co.jp
Message-ID: <59a3c1f4ad18e_8d93fdb09687134926e4@rh0257.rhizomedom.co.jp.mail>
Subject: Password reset
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_59a3c1f4ac5a4_8d93fdb096871349250";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_59a3c1f4ac5a4_8d93fdb096871349250
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

To reset your password click the link below:

http://localhost:3000/password_resets/6rzf5BbQ9uFRCrekZp3PtA/edit?email=haku%40co.jp

This link will expire in two hours.

If you did not request your password to be reset, please ignore this email and
your password will stay as it is.


----==_mimepart_59a3c1f4ac5a4_8d93fdb096871349250
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
      /* Email styles need to be inline */
    </style>
  </head>

  <body>
    <h1>Password reset</h1>

<p>To reset your password click the link below:</p>

<a href="http://localhost:3000/password_resets/6rzf5BbQ9uFRCrekZp3PtA/edit?email=haku%40co.jp">Reset password</a>

<p>This link will expire in two hours.</p>

<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>

  </body>
</html>

----==_mimepart_59a3c1f4ac5a4_8d93fdb096871349250--

12.2.1.3

<問題>コンソールに移り、先ほどの演習課題でパスワード再設定をしたUserオブジェクトを探してください。オブジェクトを見つけたら、そのオブジェクトが持つreset_digestとreset_sent_atの値を確認してみましょう。

<回答>

> User.last
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x007fc463209b10
 id: 102,
 name: "haku",
 email: "haku@co.jp",
 created_at: Mon, 28 Aug 2017 03:26:44 UTC +00:00,
 updated_at: Mon, 28 Aug 2017 07:11:03 UTC +00:00,
 password_digest: "$2a$10$BGL4JzqH6XKZQIHcaZG1CeAXndlo25Gaj1z.74gDwTTHEwjlzuZ9a",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$kSWicCWAUJWf8ip4/LKhVehAvTG.t56.W2PyXWfVykUkpM6wzTHTa",
 activated: true,
 activated_at: Mon, 28 Aug 2017 03:26:47 UTC +00:00,
 reset_digest: "$2a$10$SjkLM3hWhvqIGxWzmXHftOtINDC89Ti9OZQrJBpMq2jyFIB2.fzoG",
 reset_sent_at: Mon, 28 Aug 2017 07:10:44 UTC +00:00>

12.2.2 演習 送信メールのテスト

12.2.2.1

<問題>メイラーのテストだけを実行してみてください。このテストは greenになっているでしょうか?

<回答> なっている。

12.2.2.2

<問題>リスト 12.12にある2つ目のCGI.escapeを削除すると、テストが redになることを確認してみましょう。

<回答>

FAIL["test_password_reset", UserMailerTest, 2.998306857998614]
 test_password_reset#UserMailerTest (3.00s)
        Expected /michael@example\.com/ to match # encoding: US-ASCII
        "\r\n----==_mimepart_59a3c6f85e58_f443ffb6443fa0465c8\r\nContent-Type: text/plain;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nTo reset your password click the link below:\r\n\r\nhttp://localhost:3000/password_resets/95ZsztFc2ioqiuI92qDcxQ/edit?email=michael%40example.com\r\n\r\nThis link will expire in two hours.\r\n\r\nIf you did not request your password to be reset, please ignore this email and\r\nyour password will stay as it is.\r\n\r\n\r\n----==_mimepart_59a3c6f85e58_f443ffb6443fa0465c8\r\nContent-Type: text/html;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\n<!DOCTYPE html>\r\n<html>\r\n  <head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <style>\r\n      /* Email styles need to be inline */\r\n    </style>\r\n  </head>\r\n\r\n  <body>\r\n    <h1>Password reset</h1>\r\n\r\n<p>To reset your password click the link below:</p>\r\n\r\n<a href=\"http://localhost:3000/password_resets/95ZsztFc2ioqiuI92qDcxQ/edit?email=michael%40example.com\">Reset password</a>\r\n\r\n<p>This link will expire in two hours.</p>\r\n\r\n<p>\r\nIf you did not request your password to be reset, please ignore this email and\r\nyour password will stay as it is.\r\n</p>\r\n\r\n  </body>\r\n</html>\r\n\r\n----==_mimepart_59a3c6f85e58_f443ffb6443fa0465c8--\r\n".
        test/mailers/user_mailer_test.rb:26:in `block in <class:UserMailerTest>'

12.3.1 演習 editアクションで再設

12.3.1.1

<問題>12.2.1.1で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、図 12.11のように表示されるか確かめてみましょう。

<回答> http://localhost:3000/password_resets/-e_N9zdv0kKekwluwQXnzw/edit?email=haku%40co.jp 表示させることを確認。

12.3.1.2

<問題>先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか?

<回答> エラーにならないので、再度確認する

12.3.2 演習 パスワードを更新する

12.3.2.1

<問題>12.2.1.1で得られたリンク (Railsサーバーのログから取得) をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか?

<回答>

The form contains 1 error.
Password confirmation doesn't match Password
["Password confirmation doesn't match Password"]

12.3.3.2

<問題>コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトのpassword_digestの値を取得してみましょう。次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう (図 12.13)。パスワードの再設定は成功したら、再度password_digestの値を取得し、先ほど取得した値と異なっていることを確認してみましょう。ヒント: 新しい値はuser.reloadを通して取得する必要があります。

<回答>

> after = User.last
# パスワード変更を実施
> before = User.last
> before.password_digest == after.password_digest
=> false

12.3.3 演習 パスワードを更新する

12.3.3.1

<問題>リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習 (リスト 11.39) の解答も含まれています。

<回答>

# app/models/user.rb
  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

12.3.3.2

<問題>リスト 12.16のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐 (リスト 12.21) を統合テストで網羅してみましょう (12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます (なお、大文字と小文字は区別されません)。

12.3.3.3

<問題>2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の (または共有された) コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます (しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう5。

12.3.3.4

<問題>リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。

Ruby on Rails チュートリアルで30歳までに人生を変える(第11章)

こんにちは。opiyoです。

今回は、第11章をやっていきます。

第11章はメールを使ってアカウントを有効化する方法です。

どうやら難しそうですが、早速行ってみましょう。

railsチュートリアル11章の演習解説

11.1.1 演習 AccountActivationsコントローラ

11.1.1.1

<問題>現時点でテストスイートを実行すると greenになることを確認してみましょう。

<回答> 問題ないことを確認

11.1.1.1

<問題>表 11.2の名前付きルートでは、pathではなくurlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。

<回答> _pathだと、相対パスでの表記になってしまうため。

11.1.2 演習 AccountActivationのデータモデル

11.1.2.1

<問題>本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。

<回答> 問題ないことを確認

11.1.2.2

<問題>コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると (Privateメソッドなので) NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。

<回答>

> u = User.last
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x007fcb594c0f50
 id: 101,
 name: "haku",
 email: "haku@co.jp",
 created_at: Mon, 07 Aug 2017 03:43:38 UTC +00:00,
 updated_at: Mon, 07 Aug 2017 03:43:38 UTC +00:00,
 password_digest: "$2a$10$A3jl3TpJQRtjO4ENd.vAA.8X.hpGb0v/NpDjcBmvHXw0HdGZGN5h6",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$PY8DTSnaULLELaVLIF/agOae7WOsh/gZFfS0U9cGYBRQnCqQR3vby",
 activated: false,
 activated_at: nil>
[22] pry(main)> u.create_activation_digest
NoMethodError: private method `create_activation_digest' called for #<User:0x007fcb594c0f50>
Did you mean?  created_at_change
               created_at_previous_change
               created_at
               created_at_was
               created_at_changed?
               created_at_will_change!
               restore_activation_digest!
from /Users/taku/.rbenv/versions/2.3.4/lib/ruby/gems/2.3.0/gems/activemodel-5.0.0.1/lib/active_model/attribute_methods.rb:430:in `method_missing'
[23] pry(main)> u.activation_digest
=> "$2a$10$PY8DTSnaULLELaVLIF/agOae7WOsh/gZFfS0U9cGYBRQnCqQR3vby"

11.1.2.3

<問題>リスト 6.34で、メールアドレスの小文字化にはemail.downcase!という (代入せずに済む) メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。

<回答>

class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save { email.downcase! }

  private

  # def downcase_email
  #   self.email = email.downcase
  # end

end

11.2.1 演習 送信メールのテンプレート

11.2.1.1

<問題>コンソールを開き、CGIモジュールのescapeメソッド (リスト 11.15) でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don’t panic!"をエスケープすると、どんな結果になりますか?

<回答>

> CGI.escape('foo@example.com')
=> "foo%40example.com"
[5] pry(main)> CGI.escape("Don’t panic!")
=> "Don%E2%80%99t+panic%21"
> CGI.escape("!#$%&'()0=~|`{+*}<>?_")
=> "%21%23%24%25%26%27%28%290%3D%7E%7C%60%7B%2B%2A%7D%3C%3E%3F_"

11.2.2 演習 送信メールのプレビュー

11.2.2.1

<問題>Railsプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?

<回答>

Date: Fri, 25 Aug 2017 08:35:47 +0000

http://localhost:3000/rails/mailers/user_mailer/account_activationをブラウザで表示した時間が表示される。

11.2.3 演習 送信メールのテスト

11.2.3.1

<問題>この時点で、テストスイートが greenになっていることを確認してみましょう。

<回答> 問題ないことを確認した。

<問題>リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが redに変わることを確認してみましょう。

<回答>

 FAIL["test_account_activation", UserMailerTest, 5.321151973999804]
 test_account_activation#UserMailerTest (5.32s)
        Expected /michael@example\.com/ to match # encoding: US-ASCII
        "\r\n----==_mimepart_599fe73745950_18423fe6edc3fa14170d5\r\nContent-Type: text/plain;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHi Michael Example,\r\n\r\nWelcome to the Sample App! Click on the link below to activate your account:\r\n\r\nhttp://localhost:3000/account_activations/8LjBfLrHo_srJ_DuuIXlRw/edit?email=michael%40example.com\r\n\r\n\r\n----==_mimepart_599fe73745950_18423fe6edc3fa14170d5\r\nContent-Type: text/html;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\n<!DOCTYPE html>\r\n<html>\r\n  <head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <style>\r\n      /* Email styles need to be inline */\r\n    </style>\r\n  </head>\r\n\r\n  <body>\r\n    <h1>Sample App</h1>\r\n\r\n<p>Hi Michael Example,</p>\r\n\r\n<p>\r\nWelcome to the Sample App! Click on the link below to activate your account:\r\n</p>\r\n\r\n<a href=\"http://localhost:3000/account_activations/8LjBfLrHo_srJ_DuuIXlRw/edit?email=michael%40example.com\">Activate</a>\r\n\r\n  </body>\r\n</html>\r\n\r\n----==_mimepart_599fe73745950_18423fe6edc3fa14170d5--\r\n".
        test/mailers/user_mailer_test.rb:15:in `block in <class:UserMailerTest>'
(byebug) user.email
"michael@example.com" # `@`がそのまま表示される!
(byebug) CGI.escape(user.email)
"michael%40example.com"

11.2.4 演習 ユーザーのcreateアクションを更新

11.2.4.1

<問題>新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?

<回答> - http://localhost:3000/にリダイレクトされることを確認。 - <a href="http://localhost:3000/account_activations/GworqOheQT8myuVYkpf3hw/edit?email=taku%40co.jp">Activate</a>

<問題>コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。

<回答>

> User.last
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x007fcb535b1cd0
 id: 104,
 name: "taku",
 email: "taku@co.jp",
 created_at: Fri, 25 Aug 2017 09:22:39 UTC +00:00,
 updated_at: Fri, 25 Aug 2017 09:22:39 UTC +00:00,
 password_digest: "$2a$10$9Txlyza5j1zT5lmL1ldaJ.5ukrmPGHDYsuF6xCSDB6UJ0AdTPbqn.",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$k8NN5m1heGxd9K5t4lsd8OI2UHeTMSVhZcyllq7K9sIDpQQJYDgDu", # トークンは設定されている
 activated: false, # falseになってる
 activated_at: nil>

11.3.1 演習 authenticated?メソッドの抽象化

11.3.1.1

<問題>コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?

<回答>

> User.create!(name:  "13-1",
[2] pry(main)*   email: "13-1@railstutorial.org",
[2] pry(main)*   password:              "foobar",
[2] pry(main)*   password_confirmation: "foobar",
[2] pry(main)*   admin: true,
[2] pry(main)*   activated: true,
[2] pry(main)* activated_at: Time.zone.now)
   (0.1ms)  begin transaction
  User Exists (0.3ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "13-1@railstutorial.org"], ["LIMIT", 1]]
  SQL (0.5ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "admin", "activation_digest", "activated", "activated_at") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)  [["name", "13-1"], ["email", "13-1@railstutorial.org"], ["created_at", 2017-08-25 09:39:28 UTC], ["updated_at", 2017-08-25 09:39:28 UTC], ["password_digest", "$2a$10$60JFHnETt90jy9lIclanOuEwrBLJcJUd71B.iWjMYHrXE/v5VylLy"], ["admin", true], ["activation_digest", "$2a$10$BQV6wUIYXaaVlZkuIcC8HOYD0qllFBohGUBM9Ib3jmAhfVMRMVqMe"], ["activated", true], ["activated_at", 2017-08-25 09:39:27 UTC]]
   (2.7ms)  commit transaction
=> #<User:0x007fcb546f9ce0
 id: 105,
 name: "13-1",
 email: "13-1@railstutorial.org",
 created_at: Fri, 25 Aug 2017 09:39:28 UTC +00:00,
 updated_at: Fri, 25 Aug 2017 09:39:28 UTC +00:00,
 password_digest: "$2a$10$60JFHnETt90jy9lIclanOuEwrBLJcJUd71B.iWjMYHrXE/v5VylLy",
 remember_digest: nil,
 admin: true,
 activation_digest: "$2a$10$BQV6wUIYXaaVlZkuIcC8HOYD0qllFBohGUBM9Ib3jmAhfVMRMVqMe",
 activated: true,
 activated_at: Fri, 25 Aug 2017 09:39:27 UTC +00:00>

<問題>リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。

<回答> 問題ないことを確認した

11.3.2 演習 editアクションで有効化

11.3.2.1

<問題>コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?

<回答>

http://localhost:3000/account_activations/466u-4hqImyNdeVsrhPbKw/edit?email=haku%40co.jp

466u-4hqImyNdeVsrhPbKwこの部分が有効化トーク

11.3.2.2

<問題>先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。

<回答>

# 有効化前
> User.last
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x007fa9f1ec3e78
 id: 102,
 name: "toma",
 email: "toma@co.jp",
 created_at: Sat, 26 Aug 2017 13:50:08 UTC +00:00,
 updated_at: Sat, 26 Aug 2017 13:50:08 UTC +00:00,
 password_digest: "$2a$10$A6P5nYGWenFVG6b23WZbi.G8GFHj7HZi7YfsDxoT0C/FMi/86QoZ6",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$cIGjCz5OVXBxeJ3QUC56ouStDhT6InDg087d6BaKh2A0/zGlh6sLK",
 activated: false,
# 有効化後
[14] pry(main)> User.last
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x007fa9f1d462a8
 id: 102,
 name: "toma",
 email: "toma@co.jp",
 created_at: Sat, 26 Aug 2017 13:50:08 UTC +00:00,
 updated_at: Sat, 26 Aug 2017 13:50:18 UTC +00:00,
 password_digest: "$2a$10$A6P5nYGWenFVG6b23WZbi.G8GFHj7HZi7YfsDxoT0C/FMi/86QoZ6",
 remember_digest: nil,
 admin: false,
 activation_digest: "$2a$10$cIGjCz5OVXBxeJ3QUC56ouStDhT6InDg087d6BaKh2A0/zGlh6sLK",
 activated: true,
 activated_at: Sat, 26 Aug 2017 13:50:18 UTC +00:00>

11.3.3 演習 有効化のテストとリファクタリング

11.3.3.1

<問題>リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。

<回答>

# app/models/user.rb
  def activate
    update_attributes(activated: true, activated_at: Time.zone.now)
  end

テストが成功することも確認した

11.3.3.2

<問題>現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。

<回答>

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  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
  .
end

11.3.3.3

<問題>ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。

<回答>

# test/integration/users_index_test.rb
class UsersIndexTest < ActionDispatch::IntegrationTest

  test "index as admin including pagination and delete links" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.where(activated: true).paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
  end
end

11.3.3.2の修正を行うと、テストがエラーになるので有効化されたユーザーのみテスト対象となるように修正する

Ruby on Rails チュートリアルで30歳までに人生を変える(第10章)

こんにちは。opiyoです。

今回は、第10章をやっていきます。

第10章はユーザー登録以外の「表示」「編集」「削除」の方法です。

railsチュートリアル10章の学んだこと

  • target="_blank"が使われていますが、これを使うとリンク先を新しいタブ (またはウィンドウ) で開くようになる
<a href="http://gravatar.com/emails" target="_blank">change</a>
  • form_forの引数にインスタンス変数を使うと中身がある場合はそれをRailsが勝手に表示する
  • :allow_nil → trueならば、nilの検証はスキップ。つまり空文字(' ')はキャッチする!
  • beforeフィルターはコントローラ内のすべてのアクションに適用されるので、ここでは適切な:onlyオプション (ハッシュ) を渡すことで、:editと:updateアクションだけにこのフィルタが適用される
  • フレンドリーフォワーディング ... リダイレクト先は、ユーザーが開こうとしていたページにしてあげること
  • will_paginatebootstrap-will_paginate gemを使えばページネーションが作れる
  • will_paginateメソッドをviewに追加して、paginateメソッドでデータを取ってくるだけですと?
# view
<%= will_paginate %>

# controller
  def index
    @users = User.paginate(page: params[:page])
  end

-統合テスト = integration_test - $ rails generate integration_test users_index - toggleメソッド使うと反転できる?

$ rails console --sandbox
>> user = User.first
>> user.admin?
=> false
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true

railsチュートリアル10章の演習解説

10.1.1 演習 編集フォーム

10.1.1.1

<問題>先ほど触れたように、target="_blank"で新しいページを開くときには、セキュリティ上の小さな問題があります。それは、リンク先のサイトがHTMLドキュメントのwindowオブジェクトを扱えてしまう、という点です。具体的には、フィッシング (Phising) サイトのような、悪意のあるコンテンツを導入させられてしまう可能性があります。Gravatarのような著名なサイトではこのような事態は起こらないと思いますが、念のため、このセキュリティ上のリスクも排除しておきましょう。対処方法は、リンク用のaタグのrel (relationship) 属性に、"noopener"と設定するだけです。早速、リスト 10.2で使ったGravatarの編集ページへのリンクにこの設定をしてみましょう。

<回答>

# app/views/users/edit.html.erb
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>
    </div>

10.1.1.2

<問題>リスト 10.5のパーシャルを使って、new.html.erbビュー (リスト 10.6) とedit.html.erbビュー (リスト 10.7) をリファクタリングしてみましょう (コードの重複を取り除いてみましょう)。ヒント: 3.4.3で使ったprovideメソッドを使うと、重複を取り除けます3。(関連するリスト 7.27の演習課題を既に解いている場合、この演習課題をうまく解けない可能性があります。うまく解けない場合は、既存のコードのどこに差異があるのか考えながらこの課題に取り組んでみましょう。例えば筆者であれば、リスト 10.5のテクニックをリスト 10.6に適用してみたり、リスト 10.7のテクニックをリスト 10.5に適用してみたりするでしょう。)

<回答>

# app/views/users/_form.html.erb
<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages', object: @user %>

  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :email %>
  <%= f.text_field :email, class: 'form-control'  %>

  <%= f.label :passwore %>
  <%= f.password_field :password, class: 'form-control'  %>

  <%= f.label :password_confirmation, "Confirmation" %>
  <%= f.password_field :password_confirmation, class: 'form-control'  %>

  <%= f.submit yield(:btn_text), class: "btn btn-primary" %>
<% end %>
# app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:btn_text, 'Create my account') %>
<h1><h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
  </div>
</div>
# app/views/users/edit.html.erb
<% provide(:title, 'Edit user') %>
<% provide(:btn_text, 'Save changes') %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= render 'form' %>
    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank" rel="noopener">change</a>
    </div>
  </div>
</div>

10.1.2 演習 編集の失敗

10.1.2.1

<問題>編集フォームから有効でないユーザー名やメールアドレス、パスワードを使って送信した場合、編集に失敗することを確認してみましょう。

<回答>

The form contains 2 errors.
Email is invalid
Password is too short (minimum is 6 characters)
["Email is invalid", "Password is too short (minimum is 6 characters)"]

エラーメッセージが表示されることを確認。

10.1.3 演習 編集失敗時のテスト

10.1.3.1

<問題>リスト 10.9のテストに1行追加し、正しい数のエラーメッセージが表示されているかテストしてみてましょう。ヒント: 表 5.2で紹介したassert_selectを使ってalertクラスのdivタグを探しだし、「The form contains 4 errors.」というテキストを精査してみましょう。

<回答>

# test/integration/users_edit_test.rb
  test "unsuccessful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
    assert_select "div", "The form contains 4 errors." # この行を追加した!
  end

各パラメータがvalidateに引っかかるので、4 errorsのメッセージが表示されることをチェック!

10.1.4 演習 TDDで編集を成功させる

10.1.4.1

<問題>実際に編集が成功するかどうか、有効な情報を送信して確かめてみましょう。

<回答> - 問題なく更新できる。 - パスワード欄が空白で更新できる。

10.1.4.2

<問題>もしGravatarと紐付いていない適当なメールアドレス (foobar@example.comなど) に変更した場合、プロフィール画像はどのように表示されるでしょうか? 実際に編集フォームからメールアドレスを変更して、確認してみてましょう。

<回答>

10.2.1 演習 ユーザーにログインを要求する

10.2.1.1

<問題>デフォルトのbeforeフィルターは、すべてのアクションに対して制限を加えます。今回のケースだと、ログインページやユーザー登録ページにも制限の範囲が及んでしまうはずです (結果としてテストも失敗するはずです)。リスト 10.15のonly:オプションをコメントアウトしてみて、テストスイートがそのエラーを検知できるかどうか (テストが失敗するかどうか) 確かめてみましょう。

<回答> 問題なし

10.2.2 演習 正しいユーザーを要求する

10.2.2.1

<問題>何故editアクションとupdateアクションを両方とも保護する必要があるのでしょうか? 考えてみてください。

<回答> 自身データが修正されてしまうことを保護するため。

10.2.2.2

<問題>上記のアクションのうち、どちらがブラウザで簡単にテストできるアクションでしょうか?

<回答>

editアクション。

10.2.3 演習 フレンドリーフォワーディング

10.2.3.1

<問題>フレンドリーフォワーディングで、最初に渡されたURLにのみ確実に転送されていることを確認するテストを作成してみましょう。続けて、ログインを行った後、転送先のURLはデフォルト (プロフィール画面) に戻る必要もありますので、これもテストで確認してみてください。ヒント: リスト 10.29のsession[:forwarding_url]が正しい値かどうかを確認するテストを追加してみましょう。

<回答>

# test/integration/users_edit_test.rb
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    assert session[:forwarding_url] # sessionに値が入っていることをチェック
    assert_equal edit_user_url(@user), session[forwarding_url] # 正しい値かチェックする    
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user) # 今まではログイン後はログインユーザーのプロフィール画面に遷移してたけど、記憶したURLに遷移するようになったので、ログイン前にアクセスしたユーザー編集画面に遷移してる。
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end

10.2.3.2

<問題>7.1.3で紹介したdebuggerメソッドをSessionsコントローラのnewアクションに置いてみましょう。その後、ログアウトして /users/1/edit にアクセスしてみてください (デバッガーが途中で処理を止めるはずです)。ここでコンソールに移り、session[:forwarding_url]の値が正しいかどうか確認してみましょう。また、newアクションにアクセスしたときのrequest.get?の値も確認してみましょう (デバッガーを使っていると、ときどき予期せぬ箇所でターミナルが止まったり、おかしい挙動を見せたりします。熟練の開発者になった気になって (コラム 1.1)、落ち着いて対処してみましょう)。

<回答>

[1, 10] in /Users/taku/rails/railstutorial/railstutorial_10/app/controllers/sessions_controller.rb
    1: class SessionsController < ApplicationController
    2:   def new
    3:     debugger
=>  4:   end
    5:
    6:   def create
    7:     user = User.find_by(email: params[:session][:email].downcase)
    8:     if user && user.authenticate(params[:session][:password])
    9:       # ユーザーログイン後にユーザー情報のページにリダイレクトする
   10:       log_in user
(byebug) session[:forwarding_url]
"http://localhost:3000/users/1/edit"
(byebug) request.get?
true

10.3.1 演習 すべてのユーザーを表示する

10.3.1.1

<問題>レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。ヒント: log_in_asヘルパーを使ってリスト 5.32にテストを追加してみましょう。

<回答>

# test/integration/site_layout_test.rb
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:michael)
  end

  test "layout links" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    assert_select "a[href=?]", "http://news.railstutorial.org/"
    # ログインする
    log_in_as(@user)
    get root_path
    assert_select "a[href=?]", root_path
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", users_path
    assert_select "a[href=?]", user_path(@user)
    assert_select "a[href=?]", edit_user_path(@user)
    assert_select "a[href=?]", logout_path
  end
end

10.3.2 演習 サンプルのユーザー

10.3.2.1

<問題>試しに他人の編集ページにアクセスしてみて、10.2.2で実装したようにリダイレクトされるかどうかを確かめてみましょう。

<回答>

/users/XXXX/editにアクセスすると、トップページにリダイレクトされる(XXXXはユーザーID)

10.3.3 演習 ページネーション

10.3.3.1

<問題>Railsコンソールを開き、pageオプションにnilをセットして実行すると、1ページ目のユーザーが取得できることを確認してみましょう。

<回答>

> User.paginate(page: nil)
  User Load (4.4ms)  SELECT  "users".* FROM "users" LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
=> [#<User:0x007fd36897ceb0
  id: 1,
  name: "Example User",
  email: "example@railstutorial.org",
  created_at: Wed, 23 Aug 2017 00:25:16 UTC +00:00,
  updated_at: Wed, 23 Aug 2017 00:25:16 UTC +00:00,
  password_digest: "$2a$10$mjXbcC/DgUxXF.uhaGsZDeueTiLUbdsXsQVso79iYlmi1Nq7hMu0q",
  remember_digest: nil,
  admin: true>,
 #<User:0x007fd36894fcf8

10.3.3.2

<問題>先ほどの演習課題で取得したpaginationオブジェクトは、何クラスでしょうか? また、User.allのクラスとどこが違うでしょうか? 比較してみてください。

<回答>

> paginate_user = User.paginate(page: nil)
> paginate_user.class
=> User::ActiveRecord_Relation
> all_user = User.all
> all_user.class
=> User::ActiveRecord_Relation

クラスは一緒だよ!

10.3.4 演習 ユーザー一覧のテスト

10.3.4.1

<問題>試しにリスト 10.45にあるページネーションのリンク (will_paginateの部分) を2つともコメントアウトしてみて、リスト 10.48のテストが redに変わるかどうか確かめてみましょう。

<回答>

 FAIL["test_index_as_admin_including_pagination_and_delete_links", UsersIndexTest, 4.353926331999901]
 test_index_as_admin_including_pagination_and_delete_links#UsersIndexTest (4.35s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/users_index_test.rb:14:in `block in <class:UsersIndexTest>'

divタグのpaginationクラスが無いからエラーになる

10.3.4.2

<問題>先ほどは2つともコメントアウトしましたが、1つだけコメントアウトした場合、テストが greenのままであることを確認してみましょう。will_paginateのリンクが2つとも存在していることをテストしたい場合は、どのようなテストを追加すれば良いでしょうか? ヒント: 表 5.2を参考にして、数をカウントするテストを追加してみましょう。

<回答>

  • 1つだけコメントアウトした場合、テストが greenのままであることを確認
  • 2つとも存在していることをテストする
# test/integration/users_index_test.rb
    assert_select 'div.pagination', count: 2 # count: 2を追加

10.3.5 演習 パーシャルのリファクタリング

10.3.5.1

<問題>リスト 10.52にあるrenderの行をコメントアウトし、テストの結果が redに変わることを確認してみましょう。

<回答>

 FAIL["test_index_as_admin_including_pagination_and_delete_links", UsersIndexTest, 1.8776025120005215]
 test_index_as_admin_including_pagination_and_delete_links#UsersIndexTest (1.88s)
        Expected at least 1 element matching "a[href="/users/14035331"]", found 0..
        Expected 0 to be >= 1.
        test/integration/users_index_test.rb:17:in `block (2 levels) in <class:UsersIndexTest>'
        test/integration/users_index_test.rb:16:in `block in <class:UsersIndexTest>'

一覧に表示されるはずのユーザーへのリンクパスが見つから無いのでエラーになります。

10.4.1 演習 管理ユーザー

10.4.1.1

<問題>Web経由でadmin属性を変更できないことを確認してみましょう。具体的には、リスト 10.56に示したように、PATCHを直接ユーザーのURL (/users/:id) に送信するテストを作成してみてください。テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストの結果は redになるはずです。

<回答>

# test/controllers/users_controller_test.rb
  test "should not allow the admin attribute to be edited via the web" do
    log_in_as(@other_user)
    assert_not @other_user.admin?
    patch user_path(@other_user), params: {
      user: {
        password: @other_user.password,
        password_confirmation: @other_user.password_confirmation,
        admin: true
      }
    }
    assert_not @other_user.reload.admin?
  end

10.4.2 演習 destroyアクション

10.4.2.1

<問題>管理者ユーザーとしてログインし、試しにサンプルユーザを2〜3人削除してみましょう。ユーザーを削除すると、Railsサーバーのログにはどのような情報が表示されるでしょうか?

<回答>

10.4.3 演習 ユーザー削除のテスト

10.4.3.1

<問題>試しにリスト 10.59にある管理者ユーザーのbeforeフィルターをコメントアウトしてみて、テストの結果が redに変わることを確認してみましょう。

<回答>

 FAIL["test_should_redirect_destroy_when_logged_in_as_a_non-admin", UsersControllerTest, 4.160433043001831]
 test_should_redirect_destroy_when_logged_in_as_a_non-admin#UsersControllerTest (4.16s)
        "User.count" didn't change by 0.
        Expected: 34
          Actual: 33
        test/controllers/users_controller_test.rb:56:in `block in <class:UsersControllerTest>'

画面からだとdeleteリンクがでないから、多分これありえないけど攻撃される時はきっとテストみたいな感じで直接やられるのだろう。 それをテストでしっかりブロックできてることを確認できるのは良いね。 で、patchとかdeleteメソッドってどうやってやるんだろうか?

railsチュートリアル10章でつまずいた

$ rails test
Running via Spring preloader in process 13479
Started with run options --seed 32097

ERROR["test_index_as_non-admin", UsersIndexTest, 1.2183937259997037]
 test_index_as_non-admin#UsersIndexTest (1.22s)
NoMethodError:         NoMethodError: undefined method `paginate' for #<Class:0x007fc03bee53b0>
            app/controllers/users_controller.rb:7:in `index'
            test/integration/users_index_test.rb:29:in `block in <class:UsersIndexTest>'

ERROR["test_index_as_admin_including_pagination_and_delete_links", UsersIndexTest, 1.2559454040001583]
 test_index_as_admin_including_pagination_and_delete_links#UsersIndexTest (1.26s)
NoMethodError:         NoMethodError: undefined method `paginate' for #<Class:0x007fc03bee53b0>
            app/controllers/users_controller.rb:7:in `index'
            test/integration/users_index_test.rb:12:in `block in <class:UsersIndexTest>'

  37/37: [==========================================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.95285s
37 tests, 93 assertions, 0 failures, 2 errors, 0 skips

rh0257:railstutorial_10 taku$ spring stop # これやったら治った!
Spring stopped.
rh0257:railstutorial_10 taku$ rails test
Running via Spring preloader in process 13604
Started with run options --seed 8584

  37/37: [==========================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.85845s
37 tests, 156 assertions, 0 failures, 0 errors, 0 skips

くそー動かしてみて問題無いのなら、一先ずほっとくのもありだね。 忘れた頃に治ってるかも。

Ruby on Rails チュートリアルで30歳までに人生を変える(第9章)

こんにちは。opiyoです。

今回は、第9章をやっていきます。

第9章は画面からユーザーを登録する方法です。

railsチュートリアル9章で学んだこと

  • 記憶トークン (remember token) を生成し、cookiesメソッドによる永続的cookiesの作成
  • 安全性の高い記憶ダイジェスト (remember digest) によるトークン認証にこの記憶トークンを活用

railsチュートリアル9章の演習解説

9.1.1 演習 記憶トークンと暗号化

9.1.1.1

<問題>コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。

<回答>

* user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-03 07:19:07", updated_at: "2017-07-03 07:40:01", password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5...", remember_digest: nil>
irb(main):012:0> user.remember
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", 2017-08-18 01:28:49 UTC], ["remember_digest", "$2a$10$G7Gzgfj9Ypt7BnwJVDAhfuzbxhxMXv4f67/04hjZbIT.O/BoNA.BO"], ["id", 1]]
   (1.5ms)  commit transaction
=> true
irb(main):013:0> user
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-03 07:19:07", updated_at: "2017-08-18 01:28:49", password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5...", remember_digest: "$2a$10$G7Gzgfj9Ypt7BnwJVDAhfuzbxhxMXv4f67/04hjZbIT...">
irb(main):014:0> user.remember_token
=> "r_pj-i3RnMV0jGD7U9VdEg"
irb(main):015:0> user.remember_digest
=> "$2a$10$G7Gzgfj9Ypt7BnwJVDAhfuzbxhxMXv4f67/04hjZbIT.O/BoNA.BO"

9.1.1.2

<問題>リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、リスト 9.4 (ややわかりにくい) や、リスト 9.5 (非常に混乱する) の実装でも、正しく動くことを確認してみてください。ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。わかりにくさの原因の一部はこの点にあります。

<回答>

  # 渡された文字列のハッシュ値を返す
  def self.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
   
  # ランダムなトークンを返す
  def self.new_token
    SecureRandom.urlsafe_base64
  end

問題なし

  class << self
    # 渡された文字列のハッシュ値を返す
    def digest(string)
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                    BCrypt::Engine.cost
      BCrypt::Password.create(string, cost: cost)
    end
      
    # ランダムなトークンを返す
    def new_token
      SecureRandom.urlsafe_base64
    end
  end

問題なし

9.1.2 演習 ログイン状態の保持

9.1.2.1

<問題>ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。

<回答>

Chromeディベロッパーツールを使って確認。

remember_token:_T_91tSHmmZtIf6GLa3Pzg
user_id:5d0aa7d03c456ef4864c32703d61611e49364eaa

9.1.2.2

<問題>コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。

<回答>

* u = User.last
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-18 01:58:33", updated_at: "2017-08-18 02:02:05", password_digest: "$2a$10$e9Rq/VoCQN8j6.7G0CYYG.dOlVAMhhdGOidroqcUgtc...", remember_digest: "$2a$10$HXE4Ts.phrwUZUZXMJJh4ud2MsVMpEN32GT7fK/ZvjD...">
irb(main):010:0> token = "_T_91tSHmmZtIf6GLa3Pzg"
=> "_T_91tSHmmZtIf6GLa3Pzg"
irb(main):011:0> u.authenticated?(token)
=> true

9.1.3 演習 ユーザーを忘れる

9.1.3.1

<問題>ログアウトした後に、ブラウザの対応するcookiesが削除されていることを確認してみましょう。

<回答> Chromeディベロッパーツールを使ってcookiesが削除されていることを確認。

9.1.4 演習 2つの目立たないバグ

9.1.4.1

<問題>リスト 9.16で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。

<回答>

エラーになることを確認!

NoMethodError in SessionsController#destroy
undefined method `forget' for nil:NilClass

9.1.4.2

<問題>リスト 9.19で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。

<回答> もう一方のブラウザを再起動してサンプルアプリケーションにアクセスできちゃうことを確認!

9.1.4.3

<問題>上のコードでコメントアウトした部分を元に戻し、テストスイートが red から greenになることを確認しましょう。

<回答> テストが通ることを確認!

9.2 演習 [Remember me] チェックボックス

9.2.1

<問題>ブラウザでcookies情報を調べ、[remember me] をチェックしたときに意図した結果になっているかどうかを確認してみましょう。

<回答>

ログインが保持されていることを確認しました。 - [remember me] へのチェックあり:「remember_token」、「user_id」が保存される - [remember me] へのチェックなし:「remember_token」、「user_id」が保存されない

9.2.2

<問題>コンソールを開き、三項演算子を使った実例を考えてみてください (コラム 9.2)。

<回答>

> result = 90
=> 90
irb(main):018:0> result > 80 ? "合格":"不合格"
=> "合格"
irb(main):019:0> result = 60
=> 60
irb(main):020:0> result > 80 ? "合格":"不合格"
=> "不合格"

※僕の環境(Ruby 2.3.3)だと、日本語入力すると化ける。もし同じ現象になる場合はRubyのバージョンを変更して見てください。

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin15]
rh0257:railstutorial_9 taku$ rbenv versions
  system
  2.1.5
  2.1.6
* 2.3.3 (set by /Users/taku/rails/railstutorial/railstutorial_9/.ruby-version)
  2.3.4
  2.4.1
rh0257:railstutorial_9 taku$ rbenv local 2.3.4
rh0257:railstutorial_9 taku$ ruby -v
ruby 2.3.4p301 (2017-03-30 revision 58214) [x86_64-darwin15]

# インストールする場合
$ rbenv install --list
$ rbenv install 2.3.4

9.3.1 演習 [Remember me] ボックスをテストする

9.3.1.1

<問題>リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。

<回答>

class SessionsController < ApplicationController
  def new
  end

  def create
    @user = User.find_by(email: params[:session][:email].downcase) # ローカル変数`user`に@を付けてインスタンス変数へ
    if @user && @user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      # エラーメッセージを作成する
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

(略)

  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token # インスタンス変数に設定された`remember_token`で比較する
  end

end

9.3.2 演習 [Remember me] をテストする

9.3.2.1

<問題>リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。

<回答>

module SessionsHelper

(略)

  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
#      if user && user.authenticated?(cookies[:remember_token]) 
      if user
        log_in user
        @current_user = user
      end
    end
  end

この状態でテストすると失敗することを確認。

Ruby on Rails チュートリアルで30歳までに人生を変える(第8章)

こんにちは。opiyoです。

今回は、第8章をやっていきます。

第8章は画面からユーザーを登録する方法です。

railsチュートリアル8章の学んだこと

  • httpはステートレスなプロトコル。つまり状態がない
  • リクエストのひとつひとつは、その前のリクエスト情報を知らない
  • リクエストが終わると何もかも忘れて次回最初からやり直す健忘症的なプロトコル
  • ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内「には」まったくあり
  • セッション (Session) と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザRailsサーバーなど) に別途設定
  • セッションを実装する方法として最も一般的なのは、cookies
  • sessionメソッドで作成した一時cookiesは自動的に暗号化され
  • user.authenticate(password)は、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較する
  • 「!!」(「バンバン (bang bang)」と読みます) という演算子を使うと、そのオブジェクトを2回否定することになるので、どんなオブジェクトも強制的に論理値に変換できる
> 0
=> 0
irb(main):017:0> !0
=> false
irb(main):019:0* !!0
=> true

> user && user.authenticate("hakuhaku")
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-17 02:41:54", updated_at: "2017-08-17 02:41:54", password_digest: "$2a$10$zcrCZj1hnh3cp.zZDmYD6OV/73uzwUzbWTqyceJZWEz...">
irb(main):023:0> !!(user && user.authenticate("hakuhaku"))
=> true
  • flashのメッセージとは異なり、flash.nowのメッセージはその後リクエストが発生したときに消滅します
  • sessionメソッドで作成した一時cookiesは自動的に暗号化される
  • Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリを読み込むよう、アセットパイプラインに指示する
  • Rails 5.1 からjQueryもデフォルトで読み込まれなくなったので、jQueryも追加する必要があります

railsチュートリアル8章の演習解説

8.1.1 演習 Sessionsコントローラ

8.1.1.1

<問題>GET login_pathとPOST login_pathとの違いを説明できますか? 少し考えてみましょう。

<回答>

       login GET    /login(.:format)          sessions#new
             POST   /login(.:format)          sessions#create
  • GET login_path:'/login'にアクセスした時 → sessionコントローラー、newアクション
  • POST login_path:'/login'で、入力した値を送信した時 → sessionコントローラー、createアクション

8.1.1.2

<問題>ターミナルのパイプ機能を使ってrails routesの実行結果とgrepコマンドを繋ぐことで、Usersリソースに関するルーティングだけを表示させることができます。同様にして、Sessionsリソースに関する結果だけを表示させてみましょう。現在、いくつのSessionsリソースがあるでしょうか? ヒント: パイプやgrepの使い方が分からない場合は Learn Enough Command Line to Be Dangerousの Section on Grep (英語) を参考にしてみてください。

<回答>

$ rails routes | grep users
      signup GET    /signup(.:format)         users#new
       users GET    /users(.:format)          users#index
             POST   /users(.:format)          users#create
    new_user GET    /users/new(.:format)      users#new
   edit_user GET    /users/:id/edit(.:format) users#edit
        user GET    /users/:id(.:format)      users#show
             PATCH  /users/:id(.:format)      users#update
             PUT    /users/:id(.:format)      users#update
             DELETE /users/:id(.:format)      users#destroy
$ rails routes | grep sessions
sessions_new GET    /sessions/new(.:format)   sessions#new
       login GET    /login(.:format)          sessions#new
             POST   /login(.:format)          sessions#create
      logout DELETE /logout(.:format)         sessions#destroy

8.1.2 演習 ログインフォーム

8.1.2.1

<問題>リスト 8.4で定義したフォームで送信すると、Sessionsコントローラのcreateアクションに到達します。Railsはこれをどうやって実現しているでしょうか? 考えてみてください。ヒント:表 8.1とリスト 8.5の1行目に注目してください。

<回答>

       login GET    /login(.:format)          sessions#new
             POST   /login(.:format)          sessions#create

form_for(:session, url: login_path)のurlに指定されたプレフィックスを見て判断している。 httpメソッドが、GET or POSTかはmethodオプションで変えれるはずだけどデフォルトはpostになる。 よってSessionsコントローラのcreateアクションに到達する!

8.1.3 演習 ユーザーの検索と認証

8.1.3.1

<問題>Railsコンソールを使って、表 8.2のそれぞれの式が合っているか確かめてみましょう. まずはuser = nilの場合を、次にuser = User.firstとした場合を確かめてみてください。ヒント: 必ず論理値オブジェクトとなるように、4.2.3で紹介した!!のテクニックを使ってみましょう。例: !!(user && user.authenticate(’foobar’))

<回答>

■User:存在しない && Password:合致しない → false

> user = nil
=> nil
irb(main):002:0> user && user.authenticate("hoge")
=> nil

■User:存在する && Password:合致しない → false

> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-17 02:41:54", updated_at: "2017-08-17 02:41:54", password_digest: "$2a$10$zcrCZj1hnh3cp.zZDmYD6OV/73uzwUzbWTqyceJZWEz...">
> !!(user && user.authenticate("hoge"))
=> false

■User:存在する && Password:合致する → true

> !!(user && user.authenticate("hakuhaku"))
=> true

8.1.5 演習 フラッシュのテスト

8.1.5.1

<問題>8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。

<回答>

  1. "/login"を開く
  2. EmailとPasswordに適当な値を入力しログインボタンを押下する
  3. flashメッセージが表示されることを確認
  4. "/"を開いた時にflashメッセージが表示されないことを確認

8.2.1 log_inメソッド

8.2.1.1

<問題>有効なユーザーで実際にログインし、ブラウザからcookiesの情報を調べてみてください。このとき、sessionの値はどうなっているでしょうか? ヒント: ブラウザでcookiesを調べる方法が分からない? 今こそググってみるときです! (コラム 1.1)

<回答> Chromeの場合はディベロッパーツールのApplicationタブから確認できます。(右クリックして「検証」すれば出てくる!) f:id:opiyotan:20170817122354p:plain

8.2.1.2

<問題>先ほどの演習課題と同様に、Expiresの値について調べてみてください。

<回答> 有効期限の意味。 上の画像で「Session」ってなってるやつは、ブラウザの再起動で無くなる。 年月日があるものは、それが有効期限になる。

8.2.2 演習 現在のユーザー

8.2.2.1

<問題>Railsコンソールを使って、User.find_by(id: ...)で対応するユーザーが検索に引っかからなかったとき、nilを返すことを確認してみましょう。

<回答>

> User.find_by(id: 99)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 99], ["LIMIT", 1]]
=> nil

<問題>先ほどと同様に、今度は:user_idキーを持つsessionハッシュを作成してみましょう。リスト 8.17に記したステップにしたがって、||=演算子がうまく動くことも確認してみましょう。

<回答>

> session = {}
=> {}
irb(main):002:0> session[:user_id] = nil
=> nil
irb(main):003:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (2.9ms)  SELECT  "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT ?  [["LIMIT", 1]]
=> nil
irb(main):004:0> session[:user_id]= User.first.id
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> 1
irb(main):005:0> @current_user ||= User.find_by(id: session[:user_id])
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "haku", email: "haku@co.jp", created_at: "2017-08-17 02:41:54", updated_at: "2017-08-17 02:41:54", password_digest: "$2a$10$zcrCZj1hnh3cp.zZDmYD6OV/73uzwUzbWTqyceJZWEz...">

8.2.3 演習 レイアウトリンクを変更する

8.2.3.1

<問題>ブラウザのcookieインスペクタ機能を使って (8.2.1.1)、セッション用のcookieを削除してみてください。ヘッダー部分にあるリンクは非ログイン状態のものになっているでしょうか? 確認してみましょう。

<回答> 非ログイン状態になる。

8.2.3.2

<問題>もう一度ログインしてみて、ヘッダーのレイアウトが変わったことを確認してみましょう。その後、ブラウザを再起動させ、再び非ログイン状態に戻ったことも確認してみてください。注意: もしブラウザの [閉じたときの状態に戻す] 機能をオンにしていると、セッション情報も復元される可能性があります。もしその機能をオンにしている場合、忘れずにオフにしておきましょう (コラム 1.1)。

<回答> 非ログイン状態になる。

8.2.4 演習 レイアウトの変更をテストする

8.2.4.1

<問題>試しにSessionヘルパーのlogged_in?メソッドから!を削除してみて、リスト 8.23が redになることを確認してみましょう。

<回答>

$ rails test
FAIL["test_login_with_valid_information_followed_by_logout", UsersLoginTest, 0.940030867999667]
 test_login_with_valid_information_followed_by_logout#UsersLoginTest (0.94s)
        Expected exactly 0 elements matching "a[href="/login"]", found 1..
        Expected: 0
          Actual: 1
        test/integration/users_login_test.rb:27:in `block in <class:UsersLoginTest>'

8.2.4.2

<問題>先ほど削除した部分 (!) を元に戻して、テストが greenに戻ることを確認してみましょう。

<回答>

$ rails test
Running via Spring preloader in process 4789
Started with run options --seed 35483

  21/21: [==========================================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.37352s
21 tests, 54 assertions, 0 failures, 0 errors, 0 skips

8.2.5 演習 レイアウトの変更をテストする

8.2.5.1

<問題>リスト 8.25のlog_inの行をコメントアウトすると、テストスイートは red になるでしょうか? それとも green になるでしょうか? 確認してみましょう。

<回答> テストスイートは redになる。

 FAIL["test_valid_signup_information", UsersSignupTest, 2.6715680599991174]
 test_valid_signup_information#UsersSignupTest (2.67s)
        Expected false to be truthy.
        test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'

8.2.5.2

<問題>現在使っているテキストエディタの機能を使って、リスト 8.25をまとめてコメントアウトできないか調べてみましょう。また、コメントアウトの前後でテストスイートを実行し、コメントアウトすると red に、コメントアウトを元に戻すと green になることを確認してみましょう。ヒント: コメントアウト後にファイルを保存することを忘れないようにしましょう。また、テキストエディタコメントアウト機能については Test Editor Tutorial の Commenting Out (英語) などを参照してみてください。

<回答> コメントアウトは、コメントアウトしたい行を全て選択して「Command + /」(エディタ:Atom

8.3 演習 ログアウト

8.3.1

<問題>ブラウザから [Log out] リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.31で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。

<回答> - トップページが表示される - dropdownメニューがLog inになる - ProfileSettingsのメニューが非表示になっている

8.3.2

<問題>cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。

<回答>

   18:   def destroy
   19:     log_out if logged_in?
   20:     byebug
=> 21:     redirect_to root_url
   22:   end

(byebug) session[:user_id]
nil

Ruby on Rails チュートリアルで30歳までに人生を変える(第7章)

こんにちは。opiyoです。

今回は、第7章をやっていきます。

第7章は画面からユーザーを登録する方法です。

railsチュートリアル7章の学んだこと

  • Railsにはテスト環境 (test)、開発環境 (development)、そして本番環境 (production) の3つの環境がデフォルトで装備されている
  $ rails console
  Loading development environment
  >> Rails.env
  => "development"
  >> Rails.env.development?
  => true
  >> Rails.env.test?
  => false

# rails db:migrateを本番環境で実行する
$ rails db:migrate RAILS_ENV=production
  • Sassのミックスイン機能を使うことで、CSSルールのグループをパッケージ化できる
  • resources :usersの1行を追加すると、ユーザーのURLを生成するための多数の名前付きルート (5.3.3) と共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる
  • Digestライブラリのhexdigestメソッドを使うと、MD5のハッシュ化が実現できま
>> email = "MHARTL@example.COM"
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968"
  • formの構造について
<%= f.label :name %>
<%= f.text_field :name %>
↓ #変換されると?
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />
  • 空の判定
>> user.errors.empty?
=> false # 値が何かしらある。空ならtrue。
>> user.errors.any?
=> true # 値が何かしらある。空ならfalse
  • 引数に整数が与えられると、それに基づいて2番目の引数の英単語を複数形に変更したものを返す
>> helper.pluralize(2, "woman")
=> "2 women"
>> helper.pluralize(3, "erratum")
=> "3 errata"
  • redirect_toの仕組み
redirect_to @user
↓
redirect_to user_url(@user)
 # user_urlはプレフィックスなので、具体的に言うと`users#show`

railsチュートリアル7章ではまった

key must be 32 bytes

ArgumentError (key must be 32 bytes):
Application Trace | Framework Trace | Full Trace
activesupport (5.0.0.1) lib/active_support/message_encryptor.rb:72:in `key='
activesupport (5.0.0.1) lib/active_support/message_encryptor.rb:72:in `_encrypt'
activesupport (5.0.0.1) lib/active_support/message_encryptor.rb:58:in `encrypt_and_sign'
actionpack (5.0.0.1) lib/action_dispatch/middleware/cookies.rb:592:in `commit'
actionpack (5.0.0.1) lib/action_dispatch/middleware/cookies.rb:465:in `[]='
actionpack (5.0.0.1) lib/action_dispatch/middleware/session/cookie_store.rb:117:in `set_cookie'
rack (2.0.3) lib/rack/session/abstract/id.rb:363:in `commit_session'
rack (2.0.3) lib/rack/session/abstract/id.rb:234:in `context'
rack (2.0.3) lib/rack/session/abstract/id.rb:226:in `call'

解決方法は、rubyのバージョンを変えると上手くいった。 変更方法はこちらから。 今回、私の場合は2.4.1から2.3.4に変更し上手くいったがバージョン下げるってのは何だか嫌だな。

railsチュートリアル7章の演習解説

7.1.1 演習 デバッグRails環境

7.1.1.1

<問題> ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか? paramsの内容から確認してみましょう。

<回答>

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: static_pages
  action: about
permitted: false

7.1.1.2

<問題>Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。

<回答>

> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-03 07:19:07", updated_at: "2017-07-03 07:40:01", password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5...">
> user.attributes.class
=> Hash
> user.attributes
=> {"id"=>1, "name"=>"Example User", "email"=>"example@railstutorial.org", "created_at"=>Mon, 03 Jul 2017 07:19:07 UTC +00:00, "updated_at"=>Mon, 03 Jul 2017 07:40:01 UTC +00:00, "password_digest"=>"$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5oPa9JV.taW"}

irb(main):002:0> puts user.attributes.to_yaml # user.attributeでHashが返ってくるので、それをyml化させてる
---
id: 1
name: Example User
email: example@railstutorial.org
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2017-07-03 07:19:07.610288000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2017-07-03 07:40:01.853612000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5oPa9JV.taW"
=> nil

irb(main):004:0* y user.attributes # yメソッドはyml形式でオブジェクトの中身を表示させる。pメソッドだと<>が表示されてエスケープする必要があるがそれがいらないから利用するなんてことがあるらしい
---
id: 1
name: Example User
email: example@railstutorial.org
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2017-07-03 07:19:07.610288000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2017-07-03 07:40:01.853612000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$pQp3dmB7GUGAw/0gkC8KKuH7iS2R5afnYNlTmqg/mT5oPa9JV.taW"
=> nil

出力される結果は同じになる。

7.1.2 演習 Usersリソース

7.1.2.1

<問題>埋め込みRubyを使って、マジックカラム (created_atとupdated_at) の値をshowページに表示してみましょう (リスト 7.4)。

<回答>

<%= @user.created_at %>
<%= @user.updated_at %>

7.1.2.2

<問題>埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。

<回答>

<%= Time.now %>
<%= "現在の時刻は#{Time.now.hour}#{Time.now.min}#{Time.now.sec}秒です。" %> # 時分秒だけを表示することもできる

f:id:opiyotan:20170816103859p:plain

7.1.3 演習 debuggerメソッド

7.1.3.1

<問題>showアクションの中にdebuggerを差し込み (リスト 7.6)、ブラウザから /users/1 にアクセスしてみましょう。その後コンソールに移り、putsメソッドを使ってparamsハッシュの中身をYAML形式で表示してみましょう。ヒント: 7.1.1.1の演習を参考にしてください。その演習ではdebugメソッドで表示したデバッグ情報を、どのようにしてYAML形式で表示していたでしょうか? <回答>

(byebug) puts params.to_yaml
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '2'
permitted: false

7.1.3.2

<問題>newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。

<回答>

(byebug) @user
#<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil>

7.1.4 演習 Gravatar画像とサイドバー

7.1.4.1

<問題>(任意) Gravatar上にアカウントを作成し、あなたのメールアドレスと適当な画像を紐付けてみてください。メールアドレスをMD5ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。

<回答>

f:id:opiyotan:20170816110346p:plain

7.1.4.2

<問題>7.1.4で定義したgravatar_forヘルパーをリスト 7.12のように変更して、sizeをオプション引数として受け取れるようにしてみましょう。うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。重要: この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう。

<回答>

module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user, options = { size: 80 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

7.1.4.3

<問題>オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。

<回答>

module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user, size: 80) # ここを修正! シンプルにかける。
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

7.2.1 演習 ユーザー登録フォーム

7.2.1.1

<問題>試しに、リスト 7.15にある:nameを:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?

<回答>

Unknown action
The action 'index' could not be found for UsersController

7.2.1.2

<問題>試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。

<回答> 結果は変わらないが、なぜダメなのか? - 変数名が長くなり見通しが悪い - formfだからこそ、意味が通じるのがfoobarだと違う意味があるように思えてしまう。混乱する。

7.2.2 演習 フォームHTML

7.2.2.1

<問題> ごめんなさい。分かりません。 教えてください!!

7.3.2 演習 Strong Parameters

7.3.2.1

<問題>/users/new?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。

<回答>

--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  admin: '1'
  controller: users
  action: new
permitted: false

7.3.3 演習 エラーメッセージ

7.3.3.1

<問題>最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。

<回答>

f:id:opiyotan:20170816124511p:plain

7.3.3.2

<問題>未送信のユーザー登録フォーム (図 7.12) のURLと、送信済みのユーザー登録フォーム (図 7.18) のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。

<回答> - 未送信:/signup - 送信済:/users 正しく保存できなかった場合は、render 'new'する処理となっているため。

7.3.4 演習 失敗時のテスト

7.3.4.1

<問題>リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。

<回答>

# test/integration/users_signup_test.rb
  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
  end

7.3.4.2

<問題>未送信のユーザー登録フォームと送信直後のURLは、それぞれ /signup と /users になり、URLが異なっています。これは、リスト 5.43で追加した名前付きルートと、デフォルトのRESTfulなルーティング (リスト 7.3) を設定したことによって生じた差異です。リスト 7.26とリスト 7.27の内容を追加し、この問題を解決してみてください。うまくいけば、いずれのURLも /signup となるはずです。あれ、でもテストは greenのままになっていますね...、なぜでしょうか? (考えてみてください)

<回答>

Rails.application.routes.draw do
  root 'static_pages#home'
(略)
  get  '/signup',  to: 'users#new'
  post '/signup',  to: 'users#create' # ここを追加
  resources :users
end
# app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url: signup_path) do |f| %> # ここを修正・・・明示的にurlを指定する(users#create)
(略)
      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
# rails routes
   signup GET    /signup(.:format)         users#new
          POST   /signup(.:format)         users#create
    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create

見て分かるように、変更前はuserspostアクションを利用してusers#createの処理となっていた。 変更後は、signuppostアクションをしたusers#createの処理となっている。 アクション先がどちらの書き方でも変わらないため、テストを修正しなくても成功する。

7.3.4.3

<問題>リスト 7.25のpost部分を変更して、先ほどの演習課題で作った新しいURL (/signup) に合わせてみましょう。また、テストが greenのままになっている点も確認してください。

<回答>

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post signup_path, params: { user: { name:  "", # ここを修正
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
  end

7.3.4.4

<問題>リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、’form[action="/signup"]’という部分が存在するかどうかに着目してテストしてみましょう。

<回答>

# test/integration/users_signup_test.rb
  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post signup_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select "form[action=?]", "/signup" # ここを追加
    assert_select 'div#error_explanation'
    assert_select 'div.alert-danger'
  end

7.4.1 演習 登録フォームの完成

7.4.1.1

<問題>有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。

<回答>

> User.last
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 5, name: "toma", email: "toma@co.jp", created_at: "2017-08-16 04:51:43", updated_at: "2017-08-16 04:51:43", password_digest: "$2a$10$3NSYCM0YE340GHAaaEJXlOChlsFABFemcj6CiT4yd9V...">

<問題>リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。

<回答>

# redirect_to @userの時
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '5'
permitted: false
# redirect_to user_url(@user)の時
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
  controller: users
  action: show
  id: '6'
permitted: false

コントローラー、アクションが変わらない事を確認しました。

7.4.2 演習 flash

7.4.2.1

<問題>コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}"といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。

<回答>

> "#{:success}"
=> "success"

<問題>先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。

<回答>

> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
> "#{flash[:success]}"
=> "It worked!"
irb(main):012:0> "#{flash[:danger]}"
=> "It failed."

7.4.3 演習 実際のユーザー登録

7.4.3.1

<問題>Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。

<回答>

> User.last
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 3, name: "toma", email: "toma@co.jp", created_at: "2017-08-16 08:19:23", updated_at: "2017-08-16 08:19:23", password_digest: "$2a$10$sYvSxyb8klPNf2D6XMhxDOu1SGev2X0lH58lZVrgLij...">
irb(ma

問題なし

<問題>自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。

<回答> 確認できた!

7.4.4 演習 実際のユーザー登録

7.4.4.1

<問題>7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。

<回答>

  test "valid signup information" do
    get signup_path
(略)
    assert_template 'users/show'
    assert_not flash.empty?
  end

7.4.4.2

<問題>本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。

<回答>

<!DOCTYPE html>
<html>
      .
      <% flash.each do |message_type, message| %>
        <%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
      <% end %>
      .
</html>

7.4.4.3

<問題>リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。

<回答>

 rails test
Running via Spring preloader in process 7332
Started with run options --seed 36041

ERROR["test_valid_signup_information", UsersSignupTest, 2.10871023800064]
 test_valid_signup_information#UsersSignupTest (2.11s)
RuntimeError:         RuntimeError: not a redirect! 204 No Content
            test/integration/users_signup_test.rb:27:in `block in <class:UsersSignupTest>'

  19/19: [==========================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.45401s
19 tests, 40 assertions, 0 failures, 1 errors, 0 skips

7.4.4.4

<問題>リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。

<回答>

$ rails test
Running via Spring preloader in process 7468
Started with run options --seed 62890

 FAIL["test_valid_signup_information", UsersSignupTest, 1.01124269200227]
 test_valid_signup_information#UsersSignupTest (1.01s)
        "User.count" didn't change by 1.
        Expected: 1
          Actual: 0
        test/integration/users_signup_test.rb:21:in `block in <class:UsersSignupTest>'

 FAIL["test_invalid_signup_information", UsersSignupTest, 1.0781876540022495]
 test_invalid_signup_information#UsersSignupTest (1.08s)
        Expected at least 1 element matching "div#error_explanation", found 0..
        Expected 0 to be >= 1.
        test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>'

  19/19: [==========================================================================================================================================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.28741s
19 tests, 39 assertions, 2 failures, 0 errors, 0 skips
  • 登録されたユーザーの件数が増えないことで、assert_differenceの結果がNGになる。

7.5.3 演習 本番環境へのデプロイ

7.5.3.1

<問題>ブラウザから本番環境 (Heroku) にアクセスし、SSLの鍵マークがかかっているか、URLがhttpsになっているかどうかを確認してみましょう。

<回答> f:id:opiyotan:20170816180959p:plain

<問題>本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか?

<回答>

f:id:opiyotan:20170816181057p:plain

Ruby on Rails チュートリアルで30歳までに人生を変える(第6章)

こんにちは。opiyoです。

今回は、第6章をやっていきます。

第6章はユーザーのモデルを作っていきます。

railsチュートリアル6章の学んだこと

$ rails db:rollback
$ rails c --sandbox
Running via Spring preloader in process 3062
Loading development environment in sandbox (Rails 5.0.0.1)
Any modifications you make will be rolled back on exit
  • モデルだけのテスト実行
$ rails test:models
  1/1: [============================================================================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.03454s
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
  • オブジェクトが有効かどうかはvalid?メソッドを使う
  • ハッシュ化とは元に戻せない不可逆なデータに処理する

  • !!でそのオブジェクトが対応する論理値オブジェクトに変換できる

  • モデルの作成方法

$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/20160523010738_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
  • コントローラー名は複数形
  • モデル名は単数形
  • マイグレーションファイル
    • データベースに与える変更を定義したもの
    • rails db:migrateで反映する
    • SQLiteの場合はdevelopment.sqlite3 というファイルが出来上がり、これが実体になる
    • 元に戻す場合は rails db:rollback
  • update_attributesメソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行います (保存に成功した場合はtrueを返します)。 ただし、検証に1つでも失敗すると、 update_attributesの呼び出しは失敗します。
  • 特定の属性のみを更新したい場合は、次のようにupdate_attributeを使います。このupdate_attributeには、検証を回避するといった効果もあります。
> u.update_attribute(name: "El Duderino") # この書き方ではダメ
ArgumentError: wrong number of arguments (given 1, expected 2)

>> user.update_attribute(:name, "El Duderino") # カンマが区切ってキーと値で渡す
=> true
>> user.name
=> "El Duderino"

railsチュートリアル6章の演習解説

6.1.1 演習 データベースの移行

6.1.1.1

<問題>Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造 (スキーマ (schema) と呼びます) を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル (リスト 6.2) の内容を比べてみてください。

<回答>

#db/schema.rb
ActiveRecord::Schema.define(version: 20170630074441) do

  create_table "users", force: :cascade do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at",      null: false
    t.datetime "updated_at",      null: false
  end
end
# db/migrate/20170629005430_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

6.1.1.2

<問題>ほぼすべてのマイグレーションは、元に戻すことが可能です (少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。 $ rails db:rollback 上のコマンドを実行後、db/schema.rbの内容を調べてみて、ロールバックが成功したかどうか確認してみてください (コラム 3.1ではマイグレーションに関する他のテクニックもまとめているので、参考にしてみてください)。 上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。 これがうまくいくのは、drop_tableとcreate_tableがそれぞれ対応していることをchangeメソッドが知っているからです。この対応関係を知っているため、ロールバック用の逆方向のマイグレーションを簡単に実現することができるのです。なお、あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、upとdownのメソッドを別々に定義する必要があります。 詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。

<回答>

ActiveRecord::Schema.define(version: 0) do

end

6.1.1.3

<問題>もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。

<回答>

ActiveRecord::Schema.define(version: 20170630074441) do

  create_table "users", force: :cascade do |t|
    t.string   "name"
    t.string   "email"
    t.datetime "created_at",      null: false
    t.datetime "updated_at",      null: false
  end
end

6.1.2 演習 modelファイル

6.1.2.1

<問題>Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。

<回答>

> u = User.new
> u.class.superclass
=> ApplicationRecord(abstract)

6.1.2.2

<問題>同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。

<回答>

> u.class.superclass.superclass
=> ActiveRecord::Base

6.1.3 演習 ユーザーオブジェクトを作成する

6.1.3.1

<問題>user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。

<回答>

* user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil, password_digest: nil>
irb(main):019:0> user.name.class
=> String
irb(main):020:0> user.email.class
=> String

6.1.3.2

<問題>created_atとupdated_atは、どのクラスのインスタンスでしょうか?

<回答>

* user.created_at.class
=> NilClass
> user.updated_at.class
=> NilClass

6.1.4 演習 ユーザーオブジェクトを検索する

6.1.4.1

<問題>nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。

<回答>

* User.find_by(name: "Michael Hartl")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04", password_digest: "$2a$10$vp5fiCtIdDwU/AdskPGeKeZ35UdjtkZbPWnV3i7g0Rq...">
irb(main):030:0> User.find_by_name("Michael Hartl")
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "Michael Hartl"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04", password_digest: "$2a$10$vp5fiCtIdDwU/AdskPGeKeZ35UdjtkZbPWnV3i7g0Rq...">

6.1.4.2

<問題>実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。 User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。

<回答>

* User.all.class
=> User::ActiveRecord_Relation

6.1.4.3

<問題>User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください (4.2.3)。Rubyの性質として、そのクラスを詳しく知らなくてもなんとなくオブジェクトをどう扱えば良いかわかる、という性質があります。これをダックタイピング (duck typing) と呼び、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」。(訳注: そういえばRubyKaigi 2016の基調講演で、Ruby作者のMatzがダックタイピングについて説明していました。2〜3分の短くて分かりやすい説明なので、ぜひ視聴してみてください!)

<回答>

 User.all.length
  User Load (0.5ms)  SELECT "users".* FROM "users"
=> 2

6.1.5 演習 ユーザーオブジェクトを更新する

6.1.5.1

<問題>userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。

<回答>

> User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04">
irb(main):002:0> u = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-14 00:50:04", updated_at: "2017-08-14 00:50:04">
irb(main):003:0> u.name
=> "Michael Hartl"
irb(main):004:0> u.name = "Haku"
=> "Haku"
irb(main):005:0> u.save
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "mhartl@example.com"], ["id", 1], ["LIMIT", 1]]
  SQL (0.3ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Haku"], ["updated_at", 2017-08-14 01:06:07 UTC], ["id", 1]]
   (1.3ms)  commit transaction
=> true

6.1.5.2

<問題>今度はupdate_attributesを使って、email属性を更新および保存してみてください。

<回答>

> u.update_attributes(email: "haku@example.com")
   (0.1ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "haku@example.com"], ["id", 1], ["LIMIT", 1]]
  SQL (0.3ms)  UPDATE "users" SET "email" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["email", "haku@example.com"], ["updated_at", 2017-08-14 01:10:15 UTC], ["id", 1]]
   (1.6ms)  commit transaction
=> true

6.1.5.3

<問題>同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。

<回答>

* u.update_attribute(:created_at, 1.year.ago)
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["created_at", 2016-08-14 01:14:57 UTC], ["updated_at", 2017-08-14 01:14:57 UTC], ["id", 1]]
   (1.6ms)  commit transaction
=> true

6.2.1 演習 有効性を検証する

6.2.1.1

<問題>コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。

<回答>

* user = User.new(name: "Example User", email: "user@example.com")
=> #<User id: nil, name: "Example User", email: "user@example.com", created_at: nil, updated_at: nil>
irb(main):009:0> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "user@example.com"], ["LIMIT", 1]]
=> true

6.2.1.2

<問題>6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。

<回答>

* user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil>
irb(main):013:0> user.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> true

6.2.2 演習 存在性を検証する

6.2.2.1

<問題>新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):008:0> u.invalid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
=> true
irb(main):009:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT ?  [["LIMIT", 1]]
=> false
irb(main):010:0> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank", "Email is invalid"]

6.2.2.2

<問題>u.errors.messagesを実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?

<回答>

> errors = u.errors.messages # `errors.messages`でハッシュになる
=> {:name=>["can't be blank"], :email=>["can't be blank", "is invalid"]}
irb(main):023:0* errors[:email]
=> ["can't be blank", "is invalid"]

6.2.3 演習 長さを検証する

6.2.3.1

<問題>長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
irb(main):046:0> u.name = "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
irb(main):047:0> u.name.size
=> 51
irb(main):048:0> u.email = "a" * 255 + "@co.jp"
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@co.jp"
irb(main):049:0> u.email.size
=> 261
irb(main):050:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@co.jp"], ["LIMIT", 1]]
=> false
irb(main):051:0> u.error
u.error_on_ignored_order_or_limit u.errors

irb(main):051:0> u.error
u.error_on_ignored_order_or_limit u.errors

6.2.3.2

<問題>長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。

<回答>

> u.errors.full_messages
=> ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]

6.2.4 演習 フォーマットを検証する

6.2.4.1

<問題>リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<回答> f:id:opiyotan:20170815131537p:plain

6.2.4.2

<問題>先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。

<回答>

# app/models/user.rb
 class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence:   true, length: { maximum: 255 },
                    format:     { with: VALID_EMAIL_REGEX }
end
# test/models/user_test.rb
  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com foo@bar..com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end

6.2.4.3

<問題>foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。

<回答> f:id:opiyotan:20170815132436p:plain

6.2.5 演習 一意性を検証する

6.2.5.1

<問題> リスト 6.33を参考に、メールアドレスを小文字にするテストをリスト 6.32に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。

<回答> 問題なし

# before_saveをコメントアウトすると大文字、小文字が合致せずエラーになる
$ rails test
Running via Spring preloader in process 5880
Started with run options --seed 39690

 FAIL["test_email_addresses_should_be_saved_as_lower-case", UserTest, 1.0127649949990882]
 test_email_addresses_should_be_saved_as_lower-case#UserTest (1.01s)
        Expected: "foo@example.com"
          Actual: "Foo@ExAMPle.CoM"

6.2.5.2

<問題>テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。

<回答>

before_save { self.email = email.downcase }
↓
before_save { email.downcase! }
  • 気になること - self.email.downcase! じゃなくていいの? emailをはどう参照されるのだろうか。 selfがあれば、そのオブジェクト自身ってわかる気がするけどそれを勝手にやってくれるってことなのかな...

6.3.2 演習 ユーザーがセキュアなパスワードを持っている

6.3.2.1

<問題>この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil>
irb(main):016:0> u.name = "Haku"
=> "Haku"
irb(main):017:0> u.email = "haku@co.jp"
=> "haku@co.jp"
irb(main):018:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "haku@co.jp"], ["LIMIT", 1]]
=> false

6.3.2.2

<問題> なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。

<回答>

irb(main):019:0> u.errors.full_messages
=> ["Password can't be blank"]

6.3.3 演習 パスワードの最小文字数

6.3.3.1

<問題>有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。

<回答>

* u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil>
irb(main):007:0> u.name = "Haku"
=> "Haku"
irb(main):008:0> u.email = "haku@co.jp"
=> "haku@co.jp"
irb(main):009:0> u.password = u.password_confirmation = "haku"
=> "haku"
irb(main):010:0> u.valid?
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "haku@co.jp"], ["LIMIT", 1]]
=> false

6.3.3.2

<問題> 上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。

<回答>

> u.errors.full_messages
=> ["Password is too short (minimum is 6 characters)"]

6.3.4 演習 ユーザーの作成と認証

6.3.4.1

<問題>コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。

<回答>

$ rails c
Running via Spring preloader in process 6789
Loading development environment (Rails 5.0.0.1)
No entry for terminal type "ake/version";
using dumb terminal settings.
irb(main):001:0> User.find_by(email: "mhartl@example.com")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-15 06:49:16", updated_at: "2017-08-15 06:49:16", password_digest: "$2a$10$ZDicArmstR/XhkV.IKJVu.hMWDn5oh.P3pilUSBlJOi...">

6.3.4.2

<問題>オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?

<回答>

> user = User.find_by(email: "mhartl@example.com")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "mhartl@example.com"], ["LIMIT", 1]]
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2017-08-15 06:49:16", updated_at: "2017-08-15 06:49:16", password_digest: "$2a$10$ZDicArmstR/XhkV.IKJVu.hMWDn5oh.P3pilUSBlJOi...">
irb(main):003:0> user.name = "Haku"
=> "Haku"
irb(main):004:0> user.save
   (0.2ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND ("users"."id" != ?) LIMIT ?  [["email", "mhartl@example.com"], ["id", 1], ["LIMIT", 1]]
   (0.1ms)  rollback transaction
=> false
irb(main):005:0> user.errors.full_messages
=> ["Password can't be blank", "Password is too short (minimum is 6 characters)"] # 

nameだけ変更してsaveしているが、passwordも保存することを要求されるので「password」が無いよってエラーになる。 nameだけ明示的に変更する場合は、6.3.4.3の方法を利用する。

6.3.4.3

<問題>今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。

<回答>

* user.update_attribute(:name, "Haku")
   (0.1ms)  begin transaction
  SQL (0.4ms)  UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ?  [["name", "Haku"], ["updated_at", 2017-08-15 06:59:37 UTC], ["id", 1]]
   (1.9ms)  commit transaction
=> true
irb(main):010:0> user.name
=> "Haku"

会社での目標じゃなくて自分の目標を立てた方が人生は変わる

8月も1/3が終わり8末になると期が変わるって会社も多いのではないだろうか。

期が変わるってなると面談があったり色々と変化があるだろう。

私の会社も8末で期が変わるので色々面談したり目標に対する成果をまとめた資料作ったりが発生するのだが非常に面倒だ。

この目標とそれの達成について今日は思ったことを書こうと思う。


今期の振り返りとして

やったー万歳! 目標達成だー!

ってなったのだけど本当に凄いことだし良い事だと思う。

来期の目標を立てるときは、今期の目標と結果に対して会社全体の目標がまず決まる。

それを達成するためにチームの目標があり、個人の目標だある。

会社で立てる目標ってのは会社の目標を叶えるための目標であって実は自分の目標に直結しないこともあると思う。

この会社で生きるための力を付けることがゴールだから当たり前っちゃ当たり前なのかな。



評価制度が変わるよって話もあったのだけどどうやら組織が大きくなってきたから昔と同じように評価することは難しいって事らしい。

ここ数年人数の増減は無いし大きくなった感覚は無いのだが今までの評価については何だったのだろうか。まーそれはおいておく。ここは変わらないけど、ここが変わる。それによってこんな風になるのよ。素敵でしょ。って感じなのだが皆が知りたいのは結局「どんだけ上がるんだ」って話。これだけ。


今まではどうなったらどんだけ上がるってのが無かったから、それが明確になるなら良いなとは思う。

僕が会社から期待されている役割は具体的にこうだ!ってのがふんわりしてたからそれが具体的になるのはよいことだ。

だけどこれも結局、会社のための力であって外に出たときに通用するのかってのは全く別問題だ。



ここ数記事の中でも同じこと書いているが大事な事だから言うけど40年続く会社は3社らしい。

つまり一生同じ会社で働くってのは今の時代、もー不可能なのだ。

だからこそ何処行っても通用する力を若いうちにつけておく必要が絶対にある。

だから会社がどうなって欲しいかという視点だけじゃなく自分がどうなりたいのか。世の中はどんな人が必要とされているのかをしっかり確認することは絶対に大事だ!

最後に

目標の達成は大事。

会社と自分が重なっている人は幸せだ。

だけどそんな人ほとんどいないはず。

だからこそ自分はどうしたいのかっていう目標を自分のために立てよう。

そうすればきっと人生変わるはずだ。