見出し画像

railsチュートリアル挑戦記 第13章 ユーザーのマイクロポスト

railsチュートリアルをやりながらメモしたことをそのまま記述しています。

第13章ユーザーのマイクロポスト

これまでにユーザー、セッション、アカウント有効化、パスワードリセットという
4つのリソースについてみてきた

全ての準備が整った今、ユーザーが短いメッセージを投稿できるようにするためのリソース
「マイクロポスト」を追加していく

has_manyとbelongs_toメソッドを使って関連付けを行い、結果を処理して表示するために
必要なフォームとその部品を作成していく

Twitterのミニクローンを完成させる

・13 1 Micropostモデル

Micropostリソースの最も本質的な部分を表現するMicropostモデルを作成する
今回のマイクロポストモデルは完全にテストされ、デフォルトの順序を持ち、
親であるユーザーが破棄された場合は自動的に破棄されるようにする

いつものようにトピックブランチを作成
git checkout -b user-microposts

・13 1 1 基本的なモデル

micropostsのモデルは以下の通り
id:数字
content:文字列
user_id:数字
created_at:時間
updated_at:時間

contentはstringではなくtextを使う
stringでも255文字まで格納できるが、text型の方が表現豊かなマイクロポストを実現できる

さらにtextは将来言語に応じて投稿の長さを調節できる

まずはMicropostモデルを作成

リスト 13.1: Micropostモデルを生成する
----------------------------
$ rails generate model Micropost content:text user:references

ログ
----------------------------
s) $ rails generate model Micropost content:text user:references
Running via Spring preloader in process 4129
     invoke  active_record
     create    db/migrate/20200105143129_create_microposts.rb
     create    app/models/micropost.rb
     invoke    test_unit
     create      test/models/micropost_test.rb
     create      test/fixtures/microposts.yml
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

ApplicationRecordを継承したモデルが作られる
そして、belongs_toのコードも自動的に追加されている。
理由は、user:referencesという引数も含めていたため。
以下のようになる。

リスト 13.2: 自動生成されたMicropostモデル
app/models/micropost.rb
----------------------------
class Micropost < ApplicationRecord
  belongs_to :user
end

micropostsテーブルを作成するためのマイグレーションファイルが生成されている
usersモデルとの最大の違いは、references型を利用している点
これを利用すると、自動的にインデックスと外部キー参照つきのuser_idカラムが追加され、
UserとMicropostを関連付けする下準備をしてくれる

リスト 13.3: インデックスが付与されたMicropostのマイグレーション
db/migrate/[timestamp]_create_microposts.rb
----------------------------
class CreateMicroposts < ActiveRecord::Migration[5.0]
 def change
   create_table :microposts do |t|
     t.text :content
     t.references :user, foreign_key: true
     t.timestamps
   end
   add_index :microposts, [:user_id, :created_at]
 end
end

add_indexによって、作成時刻の逆順で取り出しやすくなる

いつものようにデータベースを更新
rails db:migrate

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

ログ
----------------------------
>> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> micropost.use_id
Traceback (most recent call last):
       1: from (irb):2
NoMethodError (undefined method `use_id' for #<Micropost:0x00000000041d59f8>)
Did you mean?  user_id
              user_id?
              user_id=
>> micropost.user_id
=> nil
>> micropost.user_id=1
=> 1
>> micropost.content="Lorem ipsum"
=> "Lorem ipsum"
>> micropost.created_at
=> nil
>> micropost.updated_at
=> nil
----------------------------


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

ログ
----------------------------
>> micropost.user
 User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-01-03 15:40:07", updated_at: "2020-01-03 15:40:07", password_digest: "$2a$10$Wmf7YiJYLhKCt/3b.PU6PeaEuEqYVMDWyTm5xKY/X16...", remember_digest: nil, admin: true, activation_digest: "$2a$10$cXGIiNCaIgMPQ70X/n5BWes/L7sRmhK2KewfMDlT08F...", activated: true, activated_at: "2020-01-03 15:40:07", reset_digest: nil, reset_sent_at: nil>
>> 
----------------------------

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

ログ
----------------------------
>> micropost.save
  (0.1ms)  begin transaction
 SQL (1.5ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2020-01-05 14:35:43.422978"], ["updated_at", "2020-01-05 14:35:43.422978"]]
  (5.3ms)  commit transaction
=> true
>> micropost.created_at
=> Sun, 05 Jan 2020 14:35:43 UTC +00:00
>> micropost.updated_at
=> Sun, 05 Jan 2020 14:35:43 UTC +00:00
>> 
----------------------------

・13 1 2 Micropostのバリデーション

バリデーションを追加していく。

その前に、Micropostモデル単体のテストを作成する
setupでは、fixtureのサンプルユーザーと紐づけた新しいマイクロポストを作成している

次に、マイクロポストが有効であるかどうかをチェックする

最後に、user_idの存在性のバリデーションに対するテストも追加する


リスト 13.4: 新しいMicropostの有効性に対するテスト green
test/models/micropost_test.rb
----------------------------
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
 def setup
   @user = users(:michael)
   # このコードは慣習的に正しくない
   @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
 end
 test "should be valid" do
   assert @micropost.valid?
 end
 test "user id should be present" do
   @micropost.user_id = nil
   assert_not @micropost.valid?
 end
end

setupの中は実は慣習的には正しくない。あとで修正する

1つ目は正常な状態かどうかテスト
2つ目はuser_idが存在しているかどうかテスト

2つ目のテストをパスさせるために、存在性のバリデーションを追加する。


リスト 13.5: マイクロポストのuser_idに対する検証 green
app/models/micropost.rb
----------------------------
class Micropost < ActiveRecord::Base
 belongs_to :user
 validates :user_id, presence: true
end


ちなみに13.5を追加しなくてもテストは成功してしまう
これはsetupが慣習的な意味で正しくないというコードを書いた場合でのみ発生する
あとで慣習的に正しいコードで実装する


リスト 13.6: green
----------------------------
rails test:models

このテストは成功するはず

成功!


次に、content属性に対するバリデーションを追加する。
140文字より長くないように制限を加える

そのためのテストを作成

リスト 13.7: Micropostモデルのバリデーションに対するテスト red
test/models/micropost_test.rb
----------------------------
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
 def setup
   @user = users(:michael)
   @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
 end
 test "should be valid" do
   assert @micropost.valid?
 end
 test "user id should be present" do
   @micropost.user_id = nil
   assert_not @micropost.valid?
 end
 test "content should be present" do
   @micropost.content = "   "
   assert_not @micropost.valid?
 end
 test "content should be at most 140 characters" do
   @micropost.content = "a" * 141
   assert_not @micropost.valid?
 end
end

そしてバリデーションを追加

リスト 13.8: Micropostモデルのバリデーション green
app/models/micropost.rb
----------------------------
class Micropost < ApplicationRecord
 belongs_to :user
 validates :user_id, presence: true
 validates :content, presence: true, length: { maximum: 140 }
end
リスト 13.9: green
----------------------------
rails test

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

ログ
----------------------------
>> micropost.save
  (0.1ms)  begin transaction
 SQL (1.5ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2020-01-05 14:35:43.422978"], ["updated_at", "2020-01-05 14:35:43.422978"]]
  (5.3ms)  commit transaction
=> true
>> micropost.created_at
=> Sun, 05 Jan 2020 14:35:43 UTC +00:00
>> micropost.updated_at
=> Sun, 05 Jan 2020 14:35:43 UTC +00:00
>> m = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> m
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> m.valid?
=> false
>> m.errors.messages
=> {:user=>["must exist"]}
>> 
----------------------------


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

ログ
----------------------------
>> 
ec2-user:~/environment/sample_app (user-microposts) $ rails console
Running via Spring preloader in process 4814
Loading development environment (Rails 5.1.6)
>> m = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> m.content="a"*141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> m.errors.messages
=> {}
>> m.valid?
=> false
>> m.errors.messages
=> {:user=>["must exist"], :user_id=>["can't be blank"], :content=>["is too long (maximum is 140 characters)"]}
>> 
----------------------------

・13 1 3 User/Micropostの関連付け

1つのツイートは1人のユーザーのみ持ってる
1人のユーザーは複数のツイートを持ってる

次のメソッドではなく
Micropost.create
Micropost.create!
Micropost.new

次のメソッドを使うことに注意
user.microposts.create
user.microposts.create!
user.microposts.build

慣習的に以下は正しくない
@user = users(:michael)
# このコードは慣習的に正しくない
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)

慣習的に以下が正しい
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")

以下まとめ
メソッド 用途
micropost.user Micropostに紐付いたUserオブジェクトを返す
user.microposts Userのマイクロポストの集合をかえす
user.microposts.create(arg) userに紐付いたマイクロポストを作成する
user.microposts.create!(arg) userに紐付いたマイクロポストを作成する (失敗時に例外を発生)
user.microposts.build(arg) userに紐付いた新しいMicropostオブジェクトを返す
user.microposts.find_by(id: 1) userに紐付いていて、idが1であるマイクロポストを検索する

@user.microposts.buildのようなコードを使うには、UserモデルとMicropostモデルをそれぞれ更新して関連付ける必要がある。
Micropostモデルのほうはマイグレーションによって自動的に追加された。
Userモデルのほうはhas_manyを追加する必要がある

リスト 13.10: マイクロポストがユーザーに所属する (belongs_to) 関連付け green
app/models/micropost.rb
----------------------------
class Micropost < ApplicationRecord
 belongs_to :user
 validates :user_id, presence: true
 validates :content, presence: true, length: { maximum: 140 }
end
リスト 13.11: ユーザーがマイクロポストを複数所有する (has_many) 関連付け green
app/models/user.rb
----------------------------
class User < ApplicationRecord
 has_many :microposts
 .
 .
 .
end

慣習的に正しいマイクロポストを作成してみよう

リスト 13.12: 慣習的に正しくマイクロポストを作成する green
test/models/micropost_test.rb
----------------------------
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
 def setup
   @user = users(:michael)
   @micropost = @user.microposts.build(content: "Lorem ipsum")
 end
 test "should be valid" do
   assert @micropost.valid?
 end
 test "user id should be present" do
   @micropost.user_id = nil
   assert_not @micropost.valid?
 end
 .
 .
 .
end
リスト 13.13: green
----------------------------
rails test

これでテストが成功するはず!

成功!イイーネっ!

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

ログ
----------------------------
>> u = 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: "2020-01-03 15:40:07", updated_at: "2020-01-03 15:40:07", password_digest: "$2a$10$Wmf7YiJYLhKCt/3b.PU6PeaEuEqYVMDWyTm5xKY/X16...", remember_digest: nil, admin: true, activation_digest: "$2a$10$cXGIiNCaIgMPQ70X/n5BWes/L7sRmhK2KewfMDlT08F...", activated: true, activated_at: "2020-01-03 15:40:07", reset_digest: nil, reset_sent_at: nil>
>> m = u.microposts.create(content: "Lorem ipsum")
  (0.1ms)  begin transaction
 SQL (1.7ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2020-01-05 14:45:04.563360"], ["updated_at", "2020-01-05 14:45:04.563360"]]
  (7.1ms)  commit transaction
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">
>> 
----------------------------


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

ログ
----------------------------
>> u.microposts.find(m.id)
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">
>> 
----------------------------
ログ
----------------------------
>> u.microposts.find(m.id)
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">
>> u.microposts.find(m)
Traceback (most recent call last):
       1: from (irb):6
ArgumentError (You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`.)
>> m
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">
>> u.microposts.find(micropost)
Traceback (most recent call last):
       1: from (irb):8
NameError (undefined local variable or method `micropost' for main:Object)
>> 
----------------------------

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

ログ
----------------------------
>> u == m.user
=> true
>> 
----------------------------
ログ
----------------------------
>> u.microposts.first == m
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> false
>> m
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">
>> u.microposts.first
 Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:35:43", updated_at: "2020-01-05 14:35:43">
>> ----------------------------

・13 1 4 マイクロポストを改良する

以下のように改良する
ユーザーのマイクロポストを特定の順序で取得できるようにする
ユーザーが削除されたらマイクロポストも自動的に削除されるようにする

user.micropostsメソッドはデフォルトでは読み出しの順序に対して何も保証されない
これを、最も新しいマイクロポストを最初に表示できるようにする。

それをするにはdefault scopeというテクニックを使う


まずはテストを作成する

リスト 13.14: マイクロポストの順序付けをテストする red
test/models/micropost_test.rb
----------------------------
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
 .
 .
 .
 test "order should be most recent first" do
   assert_equal microposts(:most_recent), Micropost.first
 end
end

fixtureファイルも作成しておく

リスト 13.15: マイクロポスト用のfixture
test/fixtures/microposts.yml
----------------------------
orange:
 content: "I just ate an orange!"
 created_at: <%= 10.minutes.ago %>
tau_manifesto:
 content: "Check out the @tauday site by @mhartl: http://tauday.com"
 created_at: <%= 3.years.ago %>
cat_video:
 content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
 created_at: <%= 2.hours.ago %>
most_recent:
 content: "Writing a short test"
 created_at: <%= Time.zone.now %>

ためしにテストしてみるとredになるはず!

リスト 13.16: red
----------------------------
rails test test/models/micropost_test.rb
ログ
----------------------------
s) $ rails test test/models/micropost_test.rb
Running via Spring preloader in process 5112
Started with run options --seed 39767
                                                
FAIL["test_order_should_be_most_recent_first", MicropostTest, 0.20437961699985863]
test_order_should_be_most_recent_first#MicropostTest (0.20s)
       --- expected
       +++ actual
       @@ -1 +1 @@
       -#<Micropost id: 941832919, content: "Writing a short test", user_id: nil, created_at: "2020-01-05 14:50:37", updated_at: "2020-01-05 14:50:37">
       +#<Micropost id: 12348100, content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk", user_id: nil, created_at: "2020-01-05 12:50:37", updated_at: "2020-01-05 14:50:37">
       test/models/micropost_test.rb:30:in `block in <class:MicropostTest>'
 5/5: [====] 100% Time: 00:00:00, Time: 00:00:00
Finished in 0.53059s
5 tests, 5 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

古いrailsだと生のSQL文を使う必要があった

今ならrailsでも書ける


デフォルトスコープを変更する書き方がい以下の通り

これによって新規作成順に並ぶ

リスト 13.17: default_scopeでマイクロポストを順序付ける green
app/models/micropost.rb
----------------------------
class Micropost < ApplicationRecord
 belongs_to :user
 default_scope -> { order(created_at: :desc) }
 validates :user_id, presence: true
 validates :content, presence: true, length: { maximum: 140 }
end

これでテストが通る

リスト 13.18: green
----------------------------
rails test

通った!!

ユーザーが削除されたらマイクロポストも削除されるようにする方法は、実はめっちゃ簡単

以下のように1行追加するのみ
dependentは、依存という意味

リスト 13.19: マイクロポストは、その所有者 (ユーザー) と一緒に破棄されることを保証する
app/models/user.rb
----------------------------
class User < ApplicationRecord
 has_many :microposts, dependent: :destroy
 .
 .
 .
end

持ち主のいないMicropostがデータベース上に残り続けるという問題を解決することができる

そのテストを追加する

リスト 13.20: dependent: :destroyのテスト green
test/models/user_test.rb
----------------------------
require 'test_helper'
class UserTest < ActiveSupport::TestCase
 def setup
   @user = User.new(name: "Example User", email: "user@example.com",
                    password: "foobar", password_confirmation: "foobar")
 end
 .
 .
 .
 test "associated microposts should be destroyed" do
   @user.save
   @user.microposts.create!(content: "Lorem ipsum")
   assert_difference 'Micropost.count', -1 do
     @user.destroy
   end
 end
end
リスト 13.21: green
----------------------------
rails test

通った!

演習
Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。

ログ
----------------------------
>> Micropost.first.created_at
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> Sun, 05 Jan 2020 14:35:43 UTC +00:00
>> Micropost.last.created_at
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> Sun, 05 Jan 2020 14:45:04 UTC +00:00
>> 
----------------------------


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

ログ
----------------------------
>> Micropost.first
 Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:35:43", updated_at: "2020-01-05 14:35:43">
>> 
----------------------------
ログ
----------------------------
>> Micropost.last
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">
>> 
----------------------------

ASCとDESCで違うね
それのLIMIT1で先頭を取り出している

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

ログ
----------------------------
>> 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: "2020-01-03 15:40:07", updated_at: "2020-01-03 15:40:07", password_digest: "$2a$10$Wmf7YiJYLhKCt/3b.PU6PeaEuEqYVMDWyTm5xKY/X16...", remember_digest: nil, admin: true, activation_digest: "$2a$10$cXGIiNCaIgMPQ70X/n5BWes/L7sRmhK2KewfMDlT08F...", activated: true, activated_at: "2020-01-03 15:40:07", reset_digest: nil, reset_sent_at: nil>
>> user.microposts.first.id
 Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> 1
>> 
----------------------------
ログ
----------------------------
>> 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: "2020-01-03 15:40:07", updated_at: "2020-01-03 15:40:07", password_digest: "$2a$10$Wmf7YiJYLhKCt/3b.PU6PeaEuEqYVMDWyTm5xKY/X16...", remember_digest: nil, admin: true, activation_digest: "$2a$10$cXGIiNCaIgMPQ70X/n5BWes/L7sRmhK2KewfMDlT08F...", activated: true, activated_at: "2020-01-03 15:40:07", reset_digest: nil, reset_sent_at: nil>
>> user.microposts
 Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 2, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:45:04", updated_at: "2020-01-05 14:45:04">, #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2020-01-05 14:35:43", updated_at: "2020-01-05 14:35:43">]>
>> user.destroy
  (0.1ms)  SAVEPOINT active_record_1
 Micropost Load (0.1ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
 SQL (6.8ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 2]]
 SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 1]]
 SQL (0.1ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
  (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-01-03 15:40:07", updated_at: "2020-01-03 15:40:07", password_digest: "$2a$10$Wmf7YiJYLhKCt/3b.PU6PeaEuEqYVMDWyTm5xKY/X16...", remember_digest: nil, admin: true, activation_digest: "$2a$10$cXGIiNCaIgMPQ70X/n5BWes/L7sRmhK2KewfMDlT08F...", activated: true, activated_at: "2020-01-03 15:40:07", 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]]
Traceback (most recent call last):
       1: from (irb):4
ActiveRecord::RecordNotFound (Couldn't find Micropost with 'id'=1)
>> 
----------------------------


・13 2 マイクロポストを表示する

マイクロポストの表示とテスト部分を作る

twitterのような独立したマイクロポストのindexページは作らずに、ユーザーのshowページで直接マイクロポストを表示してみる

・13 2 1 マイクロポストの描画

一度データベースをリセットしてサンプルデータを再生成しておく

rails db:migrate:reset
rails db:seed

コントローラを作っておく

rails generate controller Microposts

ログ
----------------------------
s) $ 
ec2-user:~/environment/sample_app (user-microposts) $ rails generate controller Microposts
Running via Spring preloader in process 5552
     create  app/controllers/microposts_controller.rb
     invoke  erb
     create    app/views/microposts
     invoke  test_unit
     create    test/controllers/microposts_controller_test.rb
     invoke  helper
     create    app/helpers/microposts_helper.rb
     invoke    test_unit
     invoke  assets
     invoke    coffee
     create      app/assets/javascripts/microposts.coffee
     invoke    scss
     create      app/assets/stylesheets/microposts.scss
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

パーシャルを作る

リスト 13.22: 1つのマイクロポストを表示するパーシャル
app/views/microposts/_micropost.html.erb
----------------------------
<li id="micropost-<%= micropost.id %>">
 <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
 <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
 <span class="content"><%= micropost.content %></span>
 <span class="timestamp">
   Posted <%= time_ago_in_words(micropost.created_at) %> ago.
 </span>
</li>


ページネーション用にshowで@micropostsを作成する

リスト 13.23: @micropostsインスタンス変数をshowアクションに追加する
app/controllers/users_controller.rb
----------------------------
class UsersController < ApplicationController
 .
 .
 .
 def show
   @user = User.find(params[:id])
   @microposts = @user.microposts.paginate(page: params[:page])
 end
 .
 .
 .
end


プロフィール画面にマイクロポストを表示するようにする


リスト 13.24: マイクロポストをユーザーのshowページ (プロフィール画面) に追加する
app/views/users/show.html.erb
----------------------------
<% provide(:title, @user.name) %>
<div class="row">
 <aside class="col-md-4">
   <section class="user_info">
     <h1>
       <%= gravatar_for @user %>
       <%= @user.name %>
     </h1>
   </section>
 </aside>
 <div class="col-md-8">
   <% if @user.microposts.any? %>
     <h3>Microposts (<%= @user.microposts.count %>)</h3>
     <ol class="microposts">
       <%= render @microposts %>
     </ol>
     <%= will_paginate @microposts %>
   <% end %>
 </div>
</div>

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

ログ
----------------------------
>> 3.weeks.ago
=> Sun, 15 Dec 2019 15:03:49 UTC +00:00
>> 6.months.ago
=> Fri, 05 Jul 2019 15:03:54 UTC +00:00
>> 
----------------------------


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

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


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

ログ
----------------------------
>> 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: "2020-01-05 15:00:58", updated_at: "2020-01-05 15:00:58", password_digest: "$2a$10$lBNMZqss5tiNHBhCWOJJ3O4spqd42gjVKZK7LqybUHA...", remember_digest: nil, admin: true, activation_digest: "$2a$10$Jsqr3KrSkmpEusqxNhdsJe/T3C4yFkMCHZyHy59g5FG...", activated: true, activated_at: "2020-01-05 15:00:58", reset_digest: nil, reset_sent_at: nil>
>> microposts = user.microposts.paginate(page: nil)
 Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["user_id", 1], ["LIMIT", 11], ["OFFSET", 0]]
=> #<ActiveRecord::AssociationRelation []>
>> microposts.class
=> Micropost::ActiveRecord_AssociationRelation
>> ----------------------------


・13 2 2 マイクロポストのサンプル

データベースに6人分のマイクロポストを作る


リスト 13.25: サンプルデータにマイクロポストを追加する
db/seeds.rb
----------------------------
.
.
.
users = User.order(:created_at).take(6)
50.times do
 content = Faker::Lorem.sentence(5)
 users.each { |user| user.microposts.create!(content: content) }
end

ここでいつものようにサンプルデータを生成する

rails db:migrate:reset
rails db:seed

railsサーバーを再起動する

見た目を整える

リスト 13.26: マイクロポスト用のCSS (本章で利用するCSSのすべて)
app/assets/stylesheets/custom.scss
----------------------------
.
.
.
/* microposts */
.microposts {
 list-style: none;
 padding: 0;
 li {
   padding: 10px 0;
   border-top: 1px solid #e8e8e8;
 }
 .user {
   margin-top: 5em;
   padding-top: 0;
 }
 .content {
   display: block;
   margin-left: 60px;
   img {
     display: block;
     padding: 5px 0;
   }
 }
 .timestamp {
   color: $gray-light;
   display: block;
   margin-left: 60px;
 }
 .gravatar {
   float: left;
   margin-right: 10px;
   margin-top: 5px;
 }
}
aside {
 textarea {
   height: 100px;
   margin-bottom: 5px;
 }
}
span.picture {
 margin-top: 10px;
 input {
   border: 0;
 }
}

time_ago_in_wordsメソッドによって時間も表示される

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

1から6までの数字の配列

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

合ってた!

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

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

いらなさそう

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

ログ
----------------------------
>> Faker::University.name
=> "Lesch College"
>> Faker::PhoneNumber.phone_number
=> "924.489.7263"
>> Faker::Hipster.sentence
=> "Jean shorts echo pug yr."
>> Faker::ChuckNorris.fact
=> "Chuck Norris can divide by zero."
>> 
----------------------------

faker-okinawaはまた今度


・13 2 3 プロフィール画面のマイクロポストをテストする

プロフィール画面で表示されるマイクロポストに対して、統合テストを書いていく
統合テストを作成する

rails generate integration_test users_profile

ログ
----------------------------
s) $ rails generate integration_test users_profile
Running via Spring preloader in process 5954
     invoke  test_unit
     create    test/integration/users_profile_test.rb
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------


fixtureを書いていく

リスト 13.27: ユーザーと関連付けされたマイクロポストのfixture
test/fixtures/microposts.yml
----------------------------
orange:
 content: "I just ate an orange!"
 created_at: <%= 10.minutes.ago %>
 user: michael
tau_manifesto:
 content: "Check out the @tauday site by @mhartl: http://tauday.com"
 created_at: <%= 3.years.ago %>
 user: michael
cat_video:
 content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
 created_at: <%= 2.hours.ago %>
 user: michael
most_recent:
 content: "Writing a short test"
 created_at: <%= Time.zone.now %>
 user: michael
<% 30.times do |n| %>
micropost_<%= n %>:
 content: <%= Faker::Lorem.sentence(5) %>
 created_at: <%= 42.days.ago %>
 user: michael
<% end %>

テストを書いていく

プロフィール画面にアクセスした後に、ページタイトルとユーザー名、Gravatar、マイクロポストの投稿数、そしてページ分割されたマイクロポスト、といった順でテストしていく

Applicationヘルパーを読み込んだことによって、full_titleヘルパーが利用できる

リスト 13.28: Userプロフィール画面に対するテストgreen
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.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

h1>img.gravatarは、h1タグの内側にgravatarクラス付きのimgタグがあるかどうかをチェックできる

リスト 13.29: green
----------------------------
rails test

うまく行った!!

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

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 6106
Started with run options --seed 50579
FAIL["test_profile_display", UsersProfileTest, 1.6625605170002018]
test_profile_display#UsersProfileTest (1.66s)
       Expected at least 1 element matching "h1>img.gravatar", found 0..
       Expected 0 to be >= 1.
       test/integration/users_profile_test.rb:15:in `block in <class:UsersProfileTest>'
 54/54: [=====================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.81035s
54 tests, 223 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------


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

count:1にしてテストOK
count:2にするとエラー

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 6203
Started with run options --seed 26181
FAIL["test_profile_display", UsersProfileTest, 1.677996511999936]
test_profile_display#UsersProfileTest (1.68s)
       Expected exactly 2 elements matching "div.pagination", found 1..
       Expected: 2
         Actual: 1
       test/integration/users_profile_test.rb:17:in `block in <class:UsersProfileTest>'
 54/54: [=====================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.98115s
54 tests, 226 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

・13 3 マイクロポストを操作する

データモデリングとマイクロポスト表示テンプレートの両方が完成
次はWeb経由でそれらを作成するためのインターフェイスに取り掛かる
最後に、ユーザーがマイクロポストをWeb経由で破棄できるようにする

リスト 13.30: マイクロポストリソースのルーティング
config/routes.rb
----------------------------
Rails.application.routes.draw do
 root   'static_pages#home'
 get    '/help',    to: 'static_pages#help'
 get    '/about',   to: 'static_pages#about'
 get    '/contact', to: 'static_pages#contact'
 get    '/signup',  to: 'users#new'
 get    '/login',   to: 'sessions#new'
 post   '/login',   to: 'sessions#create'
 delete '/logout',  to: 'sessions#destroy'
 resources :users
 resources :account_activations, only: [:edit]
 resources :password_resets,     only: [:new, :create, :edit, :update]
 resources :microposts,          only: [:create, :destroy]
end

HTTPリクエスト	URL	アクション	名前付きルート
POST	/microposts	create	microposts_path
DELETE	/microposts/1	destroy	micropost_path(micropost)
ルーティングは下記の通りになる
HTTPリクエスト	URL	アクション	名前付きルート
POST	/microposts	create	microposts_path
DELETE	/microposts/1	destroy	micropost_path(micropost)

・13 3 1 マイクロポストのアクセス制御

関連付けられたユーザーを通してマイクロポストにアクセスするので、
createアクションやdestroyアクションをするユーザーはログイン済みでなければならない

まずテストを書いていく

リスト 13.31: Micropostsコントローラの認可テスト red
test/controllers/microposts_controller_test.rb
----------------------------
require 'test_helper'
class MicropostsControllerTest < ActionDispatch::IntegrationTest
 def setup
   @micropost = microposts(:orange)
 end
 test "should redirect create when not logged in" do
   assert_no_difference 'Micropost.count' do
     post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
   end
   assert_redirected_to login_url
 end
 test "should redirect destroy when not logged in" do
   assert_no_difference 'Micropost.count' do
     delete micropost_path(@micropost)
   end
   assert_redirected_to login_url
 end
end

このテストをパスするために、少しアプリケーション側のコードをリファクタリングする必要がある

各コントローラが継承するApplicationコントローラに、logged_in_userを移す

リスト 13.32: logged_in_userメソッドをApplicationコントローラに移す red
app/controllers/application_controller.rb
----------------------------
class ApplicationController < ActionController::Base
 protect_from_forgery with: :exception
 include SessionsHelper
 private
   # ユーザーのログインを確認する
   def logged_in_user
     unless logged_in?
       store_location
       flash[:danger] = "Please log in."
       redirect_to login_url
     end
   end
end

usersコントローラからlogged_in_userを削除する

リスト 13.33: Usersコントローラ内のlogged_in_userフィルターを削除する red
app/controllers/users_controller.rb
----------------------------
class UsersController < ApplicationController
 before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
 .
 .
 .
 private
   def user_params
     params.require(:user).permit(:name, :email, :password,
                                  :password_confirmation)
   end
   # beforeフィルター
   # 正しいユーザーかどうかを確認
   def correct_user
     @user = User.find(params[:id])
     redirect_to(root_url) unless current_user?(@user)
   end
   # 管理者かどうかを確認
   def admin_user
     redirect_to(root_url) unless current_user.admin?
   end
end

beforeフィルターで、Micropostsコントローラでcreateアクションやdestroyアクションに対するアクセス制限が簡単に実装可能になる

リスト 13.34: Micropostsコントローラの各アクションに認可を追加する green
app/controllers/microposts_controller.rb
----------------------------
class MicropostsController < ApplicationController
 before_action :logged_in_user, only: [:create, :destroy]
 def create
 end
 def destroy
 end
end

これでテストが通る

リスト 13.35: green
----------------------------
rails test

通った!!!


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

オーバーライドしっぱなしになるから

・13 3 2 マイクロポストを作成する

マイクロポスト作成画面を作っていく

ユーザーのログイン状況に応じて、ホーム画面の表示を変更することを目標にする

Micropostsコントローラのcreateアクションを作っていく

リスト 13.36: Micropostsコントローラのcreateアクション
app/controllers/microposts_controller.rb
----------------------------
class MicropostsController < ApplicationController
 before_action :logged_in_user, only: [:create, :destroy]
 def create
   @micropost = current_user.microposts.build(micropost_params)
   if @micropost.save
     flash[:success] = "Micropost created!"
     redirect_to root_url
   else
     render 'static_pages/home'
   end
 end
 def destroy
 end
 private
   def micropost_params
     params.require(:micropost).permit(:content)
   end
end

サイト訪問者がログインしているかどうかに応じて異なるHTMLを提供するコードを使う

リスト 13.37: Homeページ (/) にマイクロポストの投稿フォームを追加する
app/views/static_pages/home.html.erb
----------------------------
<% if logged_in? %>
 <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>
<% else %>
 <div class="center jumbotron">
   <h1>Welcome to the Sample App</h1>
   <h2>
     This is the home page for the
     <a href="https://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"),
             'http://rubyonrails.org/' %>
<% end %>

このコードのリファクタリングは演習に回す

いくつかパーシャルを作る必要がある。
Homeの新しいサイドバーから作成する


リスト 13.38: サイドバーで表示するユーザー情報のパーシャル
app/views/shared/_user_info.html.erb
----------------------------
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>

次はマイクロポスト作成フォームを定義

リスト 13.39: マイクロポスト投稿フォームのパーシャル
app/views/shared/_micropost_form.html.erb
----------------------------
<%= form_for(@micropost) do |f| %>
 <%= render 'shared/error_messages', object: f.object %>
 <div class="field">
   <%= f.text_area :content, placeholder: "Compose new micropost..." %>
 </div>
 <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>


homeにmicropostのインスタンス変数を追加
current_userメソッドはユーザーがログインしているときしか使えないため、logged_in?でチェック

リスト 13.40: homeアクションにマイクロポストのインスタンス変数を追加する
app/controllers/static_pages_controller.rb
----------------------------
class StaticPagesController < ApplicationController
 def home
   @micropost = current_user.microposts.build if logged_in?
 end
 def help
 end
 def about
 end
 def contact
 end
end

error_messagesパーシャルを更新する

リスト 13.41: Userオブジェクト以外でも動作するようにerror_messagesパーシャルを更新する red
app/views/shared/_error_messages.html.erb
----------------------------
<% if object.errors.any? %>
 <div id="error_explanation">
   <div class="alert alert-danger">
     The form contains <%= pluralize(object.errors.count, "error") %>.
   </div>
   <ul>
   <% object.errors.full_messages.each do |msg| %>
     <li><%= msg %></li>
   <% end %>
   </ul>
 </div>
<% end %>

これでテストするとredになるはず

リスト 13.42: red
----------------------------
rails test
ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 6546
Started with run options --seed 50670
ERROR["test_password_resets", PasswordResetsTest, 1.7875776509999923]
test_password_resets#PasswordResetsTest (1.79s)
ActionView::Template::Error:         ActionView::Template::Error: undefined local variable or method `object' for #<#<Class:0x00000000055aa398>:0x0000000005ed5328>
       Did you mean?  object_id
           app/views/shared/_error_messages.html.erb:1:in `_app_views_shared__error_messages_html_erb___3778079107382904613_49779520'
           app/views/password_resets/edit.html.erb:7:in `block in _app_views_password_resets_edit_html_erb___3280837508107140968_49720280'
           app/views/password_resets/edit.html.erb:6:in `_app_views_password_resets_edit_html_erb___3280837508107140968_49720280'
           test/integration/password_resets_test.rb:37:in `block in <class:PasswordResetsTest>'
 56/56: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.09454s
56 tests, 281 assertions, 0 failures, 1 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

エラーが出る理由
ユーザー登録
パスワード指し設定
ユーザー編集

でもerror_messagesが使われているため。

それぞれを修正する

リスト 13.43: ユーザー登録時のエラー表示を更新する
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) do |f| %>
     <%= render 'shared/error_messages', object: f.object %>
     <%= f.label :name %>
     <%= f.text_field :name, class: 'form-control' %>
     <%= f.label :email %>
     <%= f.email_field :email, class: 'form-control' %>
     <%= f.label :password %>
     <%= f.password_field :password, class: 'form-control' %>
     <%= f.label :password_confirmation, "Confirmation" %>
     <%= f.password_field :password_confirmation, class: 'form-control' %>
     <%= f.submit "Create my account", class: "btn btn-primary" %>
   <% end %>
 </div>
</div>
リスト 13.44: ユーザー編集時のエラー表示を更新する
app/views/users/edit.html.erb
----------------------------
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
 <div class="col-md-6 col-md-offset-3">
   <%= form_for(@user) do |f| %>
     <%= render 'shared/error_messages', object: f.object %>
     <%= f.label :name %>
     <%= f.text_field :name, class: 'form-control' %>
     <%= f.label :email %>
     <%= f.email_field :email, class: 'form-control' %>
     <%= f.label :password %>
     <%= f.password_field :password, class: 'form-control' %>
     <%= f.label :password_confirmation, "Confirmation" %>
     <%= f.password_field :password_confirmation, class: 'form-control' %>
     <%= f.submit "Save changes", class: "btn btn-primary" %>
   <% end %>
   <div class="gravatar_edit">
     <%= gravatar_for @user %>
     <a href="http://gravatar.com/emails">change</a>
   </div>
 </div>
</div>
リスト 13.45: パスワード再設定時のエラー表示を更新する
app/views/password_resets/edit.html.erb
----------------------------
<% provide(:title, 'Reset password') %>
<h1>Password reset</h1>
<div class="row">
 <div class="col-md-6 col-md-offset-3">
   <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
     <%= render 'shared/error_messages', object: f.object %>
     <%= hidden_field_tag :email, @user.email %>
     <%= f.label :password %>
     <%= f.password_field :password, class: 'form-control' %>
     <%= f.label :password_confirmation, "Confirmation" %>
     <%= f.password_field :password_confirmation, class: 'form-control' %>
     <%= f.submit "Update password", class: "btn btn-primary" %>
   <% end %>
 </div>
</div>


これでテストが通るはず
rails test

通った!!

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

app/views/static_pages/home.html.erb
----------------------------
<% if logged_in? %>
 <%= render 'static_pages/home_logged_in' %>
<% else %>
 <%= render 'static_pages/home_not_logged_in' %>
<% end %>
----------------------------
app/views/static_pages/_home_lodgged_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>
----------------------------
app/views/static_pages/_home_not_logged_in.html.erb
----------------------------
<div class="center jumbotron">
 <h1>Welcom to the  Sample App</h1>
 <h2>
   This is the home page for the
   <a href="https://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"), 'http://rubyonrails.org/' %>
----------------------------

リファクタリング後、rails testしてエラーが出ないことを確認!

・13 3 3 フィードの原型


フィードの原型を作る

リスト 13.46: マイクロポストのステータスフィードを実装するための準備
app/models/user.rb
----------------------------
class User < ApplicationRecord
 .
 .
 .
 # 試作feedの定義
 # 完全な実装は次章の「ユーザーをフォローする」を参照
 def feed
   Micropost.where("user_id = ?", id)
 end
   private
   .
   .
   .
end

?をつけるのはSQLインジェクションを避けるため

homeアクションにフィードのインスタンス変数を追加しておく

リスト 13.47: homeアクションにフィードのインスタンス変数を追加する
app/controllers/static_pages_controller.rb
----------------------------
class StaticPagesController < ApplicationController
 def home
   if logged_in?
     @micropost  = current_user.microposts.build
     @feed_items = current_user.feed.paginate(page: params[:page])
   end
 end
 def help
 end
 def about
 end
 def contact
 end
end

リスト 13.48: ステータスフィードのパーシャル
app/views/shared/_feed.html.erb
----------------------------
<% if @feed_items.any? %>
 <ol class="microposts">
   <%= render @feed_items %>
 </ol>
 <%= will_paginate @feed_items %>
<% end %>
リスト 13.49: Homeページにステータスフィードを追加する
app/views/static_pages/_home_logged_in.html.erb
----------------------------
<% if logged_in? %>
 <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>
<% else %>
 .
 .
 .
<% end %>

リスト 13.50: createアクションに空の@feed_itemsインスタンス変数を追加する
app/controllers/microposts_controller.rb
----------------------------
class MicropostsController < ApplicationController
 before_action :logged_in_user, only: [:create, :destroy]
 def create
   @micropost = current_user.microposts.build(micropost_params)
   if @micropost.save
     flash[:success] = "Micropost created!"
     redirect_to root_url
   else
     @feed_items = []
     render 'static_pages/home'
   end
 end
 def destroy
 end
 private
   def micropost_params
     params.require(:micropost).permit(:content)
   end
end


演習

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

ログ
----------------------------
Started POST "/microposts" for 113.158.199.15 at 2020-01-05 15:49:15 +0000
Cannot render console from 113.158.199.15! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by MicropostsController#create as HTML
 Parameters: {"utf8"=>"", "authenticity_token"=>"tKK6AS1epAz0dgcGey8VWQZ7wrbWl3Dn6fJSLomcN8/ymYrHPddjnNYsuBvPPlmVkGB4iYvN0zV5Ew3Y0feV9A==", "micropost"=>{"content"=>"test"}, "commit"=>"Post"}
 User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  (0.0ms)  begin transaction
 SQL (6.5ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "test"], ["user_id", 1], ["created_at", "2020-01-05 15:49:15.548161"], ["updated_at", "2020-01-05 15:49:15.548161"]]
  (10.1ms)  commit transaction
Redirected to https://3af3c1f1ae1947eb8b6ab78acd1007b4.vfs.cloud9.ap-northeast-1.amazonaws.com/
Completed 302 Found in 24ms (ActiveRecord: 16.9ms)

Started GET "/" for 113.158.199.15 at 2020-01-05 15:49:15 +0000
Cannot render console from 113.158.199.15! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by StaticPagesController#home as HTML
 User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 Rendering static_pages/home.html.erb within layouts/application
  (0.1ms)  SELECT COUNT(*) FROM "microposts" WHERE "microposts"."user_id" = ?  [["user_id", 1]]
 Rendered shared/_user_info.html.erb (1.8ms)
 Rendered shared/_error_messages.html.erb (0.3ms)
 Rendered shared/_micropost_form.html.erb (1.8ms)
  (0.1ms)  SELECT COUNT(*) FROM "microposts" WHERE (user_id = 1)
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 Rendered collection of microposts/_micropost.html.erb [30 times] (40.7ms)
 CACHE  (0.0ms)  SELECT COUNT(*) FROM "microposts" WHERE (user_id = 1)
 Rendered shared/_feed.html.erb (45.0ms)
 Rendered static_pages/home.html.erb within layouts/application (51.0ms)
 Rendered layouts/_rails_default.html.erb (42.1ms)
 Rendered layouts/_shim.html.erb (0.3ms)
 Rendered layouts/_header.html.erb (0.8ms)
 Rendered layouts/_footer.html.erb (0.3ms)
Completed 200 OK in 105ms (Views: 100.7ms | ActiveRecord: 0.9ms)
----------------------------


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

ログ
----------------------------
NameError (uninitialized constant Faker::Okinawa)
>> Faker::faker-okinawa
Traceback (most recent call last):
       2: from (irb):13
       1: from (irb):13:in `rescue in irb_binding'
NoMethodError (undefined method `faker' for Faker:Module)
>> Faker::University.name
=> "Lesch College"
>> Faker::PhoneNumber.phone_number
=> "924.489.7263"
>> Faker::Hipster.sentence
=> "Jean shorts echo pug yr."
>> Faker::ChuckNorris.fact
=> "Chuck Norris can divide by zero."
>> ^C
>> 
  (0.1ms)  rollback transaction
ec2-user:~/environment/sample_app (user-microposts) $ rails console --sandbox
Running via Spring preloader in process 7416
Loading development environment in sandbox (Rails 5.1.6)
Any modifications you make will be rolled back on exit
>> 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: "2020-01-05 15:07:39", updated_at: "2020-01-05 15:07:39", password_digest: "$2a$10$EuOhQK0prS5o.tTAWSLgo.mzDj4S8GXpPqBoQBsrXuA...", remember_digest: nil, admin: true, activation_digest: "$2a$10$QHL/IOwleMY/SDCJay1youe1VFRxZvaU5XQD9NzBlG5...", activated: true, activated_at: "2020-01-05 15:07:39", reset_digest: nil, reset_sent_at: nil>
>> Micropost.where("user_id = ?", user.id)
 Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Micropost id: 301, content: "test", user_id: 1, created_at: "2020-01-05 15:49:15", updated_at: "2020-01-05 15:49:15">, #<Micropost id: 295, content: "Nihil odio eligendi officiis dolorem quibusdam qui...", user_id: 1, created_at: "2020-01-05 15:07:59", updated_at: "2020-01-05 15:07:59">, #<Micropost id: 289, content: "Necessitatibus eveniet consequatur labore voluptat...", user_id: 1, created_at: "2020-01-05 15:07:59", updated_at: "2020-01-05 15:07:59">, #<Micropost id: 283, content: "Quia reiciendis voluptas qui nulla ipsum.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 277, content: "Et laborum aperiam et ea rerum.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 271, content: "Veniam cumque voluptatem dolor eum et velit.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 265, content: "Nulla possimus sequi vel ut quisquam ratione.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 259, content: "Reprehenderit quod recusandae et nesciunt similiqu...", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 253, content: "Temporibus quam quas voluptatem rerum tenetur alia...", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 247, content: "Est atque a voluptatum enim distinctio.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, ...]>
>> user.microposts
 Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Micropost id: 301, content: "test", user_id: 1, created_at: "2020-01-05 15:49:15", updated_at: "2020-01-05 15:49:15">, #<Micropost id: 295, content: "Nihil odio eligendi officiis dolorem quibusdam qui...", user_id: 1, created_at: "2020-01-05 15:07:59", updated_at: "2020-01-05 15:07:59">, #<Micropost id: 289, content: "Necessitatibus eveniet consequatur labore voluptat...", user_id: 1, created_at: "2020-01-05 15:07:59", updated_at: "2020-01-05 15:07:59">, #<Micropost id: 283, content: "Quia reiciendis voluptas qui nulla ipsum.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 277, content: "Et laborum aperiam et ea rerum.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 271, content: "Veniam cumque voluptatem dolor eum et velit.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 265, content: "Nulla possimus sequi vel ut quisquam ratione.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 259, content: "Reprehenderit quod recusandae et nesciunt similiqu...", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 253, content: "Temporibus quam quas voluptatem rerum tenetur alia...", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 247, content: "Est atque a voluptatum enim distinctio.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, ...]>
>> user.feed
 Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Micropost id: 301, content: "test", user_id: 1, created_at: "2020-01-05 15:49:15", updated_at: "2020-01-05 15:49:15">, #<Micropost id: 295, content: "Nihil odio eligendi officiis dolorem quibusdam qui...", user_id: 1, created_at: "2020-01-05 15:07:59", updated_at: "2020-01-05 15:07:59">, #<Micropost id: 289, content: "Necessitatibus eveniet consequatur labore voluptat...", user_id: 1, created_at: "2020-01-05 15:07:59", updated_at: "2020-01-05 15:07:59">, #<Micropost id: 283, content: "Quia reiciendis voluptas qui nulla ipsum.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 277, content: "Et laborum aperiam et ea rerum.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 271, content: "Veniam cumque voluptatem dolor eum et velit.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 265, content: "Nulla possimus sequi vel ut quisquam ratione.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 259, content: "Reprehenderit quod recusandae et nesciunt similiqu...", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 253, content: "Temporibus quam quas voluptatem rerum tenetur alia...", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, #<Micropost id: 247, content: "Est atque a voluptatum enim distinctio.", user_id: 1, created_at: "2020-01-05 15:07:58", updated_at: "2020-01-05 15:07:58">, ...]>
>> Micropost.where("user_id = ?", user.id)==user.microposts
 Micropost Load (1.4ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
 Micropost Load (0.6ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
>> Micropost.where("user_id = ?", user.id)==user.feed
=> true
>> 
----------------------------


・13 3 4 マイクロポストを削除する

マイクロポストを削除する機能を追加する
そのためのリンクを追加する

リスト 13.51: マイクロポストのパーシャルに削除リンクを追加する
app/views/microposts/_micropost.html.erb
----------------------------
<li id="micropost-<%= micropost.id %>">
 <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
 <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
 <span class="content"><%= micropost.content %></span>
 <span class="timestamp">
   Posted <%= time_ago_in_words(micropost.created_at) %> ago.
   <% if current_user?(micropost.user) %>
     <%= link_to "delete", micropost, method: :delete,
                                      data: { confirm: "You sure?" } %>
   <% end %>
 </span>
</li>

destroyアクションを追加する
ログイン中のユーザーが、削除しようとしたマイクロポストの所持者であるかどうかをチェックする

リスト 13.52: Micropostsコントローラのdestroyアクション
app/controllers/microposts_controller.rb
----------------------------
class MicropostsController < ApplicationController
 before_action :logged_in_user, only: [:create, :destroy]
 before_action :correct_user,   only: :destroy
 .
 .
 .
 def destroy
   @micropost.destroy
   flash[:success] = "Micropost deleted"
   redirect_to request.referrer || root_url
 end
 private
   def micropost_params
     params.require(:micropost).permit(:content)
   end
   def correct_user
     @micropost = current_user.microposts.find_by(id: params[:id])
     redirect_to root_url if @micropost.nil?
   end
end

request.referrerメソッドはフレンドリーフォワーディングのrequest.url変数と似てる
これを使うとDELETEリクエストが発行されたページに戻すことができるので非常に便利

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

作成時のログ
----------------------------
Started POST "/microposts" for 113.158.199.15 at 2020-01-05 15:55:50 +0000
Cannot render console from 113.158.199.15! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by MicropostsController#create as HTML
 Parameters: {"utf8"=>"", "authenticity_token"=>"lqOTpDi+qMCdkoPmVgFUgK+2rBG+11TMwNHKHGFo3sSrrZ5XuhtWXWUjvJuNajX5i4s7nmum+SPvei2u0ozfnQ==", "micropost"=>{"content"=>"testes"}, "commit"=>"Post"}
 User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  (0.0ms)  begin transaction
 SQL (2.3ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "testes"], ["user_id", 1], ["created_at", "2020-01-05 15:55:50.365409"], ["updated_at", "2020-01-05 15:55:50.365409"]]
  (6.6ms)  commit transaction
Redirected to https://3af3c1f1ae1947eb8b6ab78acd1007b4.vfs.cloud9.ap-northeast-1.amazonaws.com/
Completed 302 Found in 16ms (ActiveRecord: 9.1ms)

Started GET "/" for 113.158.199.15 at 2020-01-05 15:55:50 +0000
Cannot render console from 113.158.199.15! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by StaticPagesController#home as HTML
 User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 Rendering static_pages/home.html.erb within layouts/application
  (0.1ms)  SELECT COUNT(*) FROM "microposts" WHERE "microposts"."user_id" = ?  [["user_id", 1]]
 Rendered shared/_user_info.html.erb (1.8ms)
 Rendered shared/_error_messages.html.erb (0.3ms)
 Rendered shared/_micropost_form.html.erb (1.9ms)
  (0.1ms)  SELECT COUNT(*) FROM "microposts" WHERE (user_id = 1)
 Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 Rendered collection of microposts/_micropost.html.erb [30 times] (46.5ms)
 CACHE  (0.0ms)  SELECT COUNT(*) FROM "microposts" WHERE (user_id = 1)
 Rendered shared/_feed.html.erb (51.1ms)
 Rendered static_pages/home.html.erb within layouts/application (57.2ms)
 Rendered layouts/_rails_default.html.erb (39.0ms)
 Rendered layouts/_shim.html.erb (0.3ms)
 Rendered layouts/_header.html.erb (0.8ms)
 Rendered layouts/_footer.html.erb (0.3ms)
Completed 200 OK in 106ms (Views: 101.6ms | ActiveRecord: 1.1ms)
----------------------------
削除時のログ
----------------------------
Started DELETE "/microposts/303" for 113.158.199.15 at 2020-01-05 15:56:27 +0000
Cannot render console from 113.158.199.15! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by MicropostsController#destroy as HTML
 Parameters: {"authenticity_token"=>"ue/dRbxlB+zUuLQxvm1pLyLbcVAnLve231aFFLwc2/iE4dC2PsD5cSwJi0xlBghWBubm3/JfWlnw/WKmD/jaoQ==", "id"=>"303"}
 User Load (0.9ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 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", 303], ["LIMIT", 1]]
  (0.1ms)  begin transaction
 SQL (1.8ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 303]]
  (7.6ms)  commit transaction
Redirected to https://3af3c1f1ae1947eb8b6ab78acd1007b4.vfs.cloud9.ap-northeast-1.amazonaws.com/
Completed 302 Found in 16ms (ActiveRecord: 10.5ms)

Started GET "/" for 113.158.199.15 at 2020-01-05 15:56:27 +0000
Cannot render console from 113.158.199.15! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by StaticPagesController#home as HTML
 User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 Rendering static_pages/home.html.erb within layouts/application
  (0.2ms)  SELECT COUNT(*) FROM "microposts" WHERE "microposts"."user_id" = ?  [["user_id", 1]]
 Rendered shared/_user_info.html.erb (1.8ms)
 Rendered shared/_error_messages.html.erb (0.3ms)
 Rendered shared/_micropost_form.html.erb (1.8ms)
  (0.1ms)  SELECT COUNT(*) FROM "microposts" WHERE (user_id = 1)
 Micropost Load (0.3ms)  SELECT  "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["LIMIT", 30], ["OFFSET", 0]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 Rendered collection of microposts/_micropost.html.erb [30 times] (47.4ms)
 CACHE  (0.0ms)  SELECT COUNT(*) FROM "microposts" WHERE (user_id = 1)
 Rendered shared/_feed.html.erb (51.6ms)
 Rendered static_pages/home.html.erb within layouts/application (57.7ms)
 Rendered layouts/_rails_default.html.erb (57.4ms)
 Rendered layouts/_shim.html.erb (0.3ms)
 Rendered layouts/_header.html.erb (0.9ms)
 Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 125ms (Views: 121.9ms | ActiveRecord: 1.0ms)
----------------------------


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


・13 3 5 フィード画面のマイクロポストをテストする

fixtureを作成していく

リスト 13.53: 別のユーザーに所属しているマイクロポストを追加する
test/fixtures/microposts.yml
----------------------------
.
.
.
ants:
 content: "Oh, is that what you want? Because that's how you get ants!"
 created_at: <%= 2.years.ago %>
 user: archer
zone:
 content: "Danger zone!"
 created_at: <%= 3.days.ago %>
 user: archer
tone:
 content: "I'm sorry. Your words made sense, but your sarcastic tone did not."
 created_at: <%= 10.minutes.ago %>
 user: lana
van:
 content: "Dude, this van's, like, rolling probable cause."
 created_at: <%= 4.hours.ago %>
 user: lana


自分以外のユーザーのマイクロポストは削除しようとすると、適切にリダイレクトされることをテストで確認する

リスト 13.54: 間違ったユーザーによるマイクロポスト削除に対してテストする green
test/controllers/microposts_controller_test.rb
----------------------------
require 'test_helper'
class MicropostsControllerTest < ActionDispatch::IntegrationTest
 def setup
   @micropost = microposts(:orange)
 end
 test "should redirect create when not logged in" do
   assert_no_difference 'Micropost.count' do
     post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
   end
   assert_redirected_to login_url
 end
 test "should redirect destroy when not logged in" do
   assert_no_difference 'Micropost.count' do
     delete micropost_path(@micropost)
   end
   assert_redirected_to login_url
 end
 test "should redirect destroy for wrong micropost" do
   log_in_as(users(:michael))
   micropost = microposts(:ants)
   assert_no_difference 'Micropost.count' do
     delete micropost_path(micropost)
   end
   assert_redirected_to root_url
 end
end


ここで統合テストを作成
rails generate integration_test microposts_interface

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails generate integration_test microposts_interface
Running via Spring preloader in process 7764
     invoke  test_unit
     create    test/integration/microposts_interface_test.rb
----------------------------

テストを書く

リスト 13.55: マイクロポストのUIに対する統合テスト green
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_select 'div.pagination'
   # 無効な送信
   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"
   assert_difference 'Micropost.count', 1 do
     post microposts_path, params: { micropost: { content: content } }
   end
   assert_redirected_to root_url
   follow_redirect!
   assert_match content, 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.56: green
----------------------------
rails test

redirect_to root_url if @micropost.nil?を
redirect_back(fallback_location: root_url)
にしてしまったため、末尾が抜けた
redirect_back(fallback_location: root_url) if @micropost.nil?
にすることで成功!

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

app/models/micropost.rbでcontentのvalidatesをコメントアウト
ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 8150
Started with run options --seed 3467
FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 0.9345738359997995]
test_content_should_be_at_most_140_characters#MicropostTest (0.93s)
       Expected true to be nil or false
       test/models/micropost_test.rb:26:in `block in <class:MicropostTest>'
FAIL["test_content_should_be_present", MicropostTest, 0.9440019510002458]
test_content_should_be_present#MicropostTest (0.94s)
       Expected true to be nil or false
       test/models/micropost_test.rb:21:in `block in <class:MicropostTest>'
FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.7362438300006033]
test_micropost_interface#MicropostsInterfaceTest (1.74s)
       "Micropost.count" didn't change by 0.
       Expected: 38
         Actual: 39
       test/integration/microposts_interface_test.rb:14:in `block in <class:MicropostsInterfaceTest>'
 58/58: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.08127s
58 tests, 294 assertions, 3 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

app/controllers/microposts_controller.rbでsave後をコメントアウト

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 8150
Started with run options --seed 3467
FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 0.9345738359997995]
test_content_should_be_at_most_140_characters#MicropostTest (0.93s)
       Expected true to be nil or false
       test/models/micropost_test.rb:26:in `block in <class:MicropostTest>'
FAIL["test_content_should_be_present", MicropostTest, 0.9440019510002458]
test_content_should_be_present#MicropostTest (0.94s)
       Expected true to be nil or false
       test/models/micropost_test.rb:21:in `block in <class:MicropostTest>'
FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.7362438300006033]
test_micropost_interface#MicropostsInterfaceTest (1.74s)
       "Micropost.count" didn't change by 0.
       Expected: 38
         Actual: 39
       test/integration/microposts_interface_test.rb:14:in `block in <class:MicropostsInterfaceTest>'
 58/58: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.08127s
58 tests, 294 assertions, 3 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 8175
Started with run options --seed 58661
 58/58: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.06301s
58 tests, 302 assertions, 0 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 8253
Started with run options --seed 31282
FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.8153790559999834]
test_micropost_interface#MicropostsInterfaceTest (1.82s)
       Expected response to be a <3XX: redirect>, but was a <204: No Content>
       Response body: 
       test/integration/microposts_interface_test.rb:23:in `block in <class:MicropostsInterfaceTest>'
 58/58: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.14312s
58 tests, 297 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

app/views/micropost.html.erbでlink_toをコメントアウト

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 8305
Started with run options --seed 2444
FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.8542083239999556]
test_micropost_interface#MicropostsInterfaceTest (1.85s)
       <delete> expected but was
       <sample app>..
       Expected 0 to be >= 1.
       test/integration/microposts_interface_test.rb:27:in `block in <class:MicropostsInterfaceTest>'
 58/58: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.05624s
58 tests, 300 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

app/views/_micropost.html.erbでifとendをコメントアウト

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 8340
Started with run options --seed 5930
FAIL["test_micropost_interface", MicropostsInterfaceTest, 2.0040418389999104]
test_micropost_interface#MicropostsInterfaceTest (2.00s)
       Expected exactly 0 elements matching "a", found 2..
       Expected: 0
         Actual: 2
       test/integration/microposts_interface_test.rb:34:in `block in <class:MicropostsInterfaceTest>'
 58/58: [=====================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.22806s
58 tests, 302 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

全て元に戻してエラーが出ないことを確認!


リスト 13.57: サイドバーでマイクロポストの投稿数をテストするためのテンプレート
test/integration/microposts_interface_test.rb
----------------------------
require 'test_helper'
class MicropostsInterfaceTest < 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

クラス名誤字ってるけどまあいいか
それも直してrails testで問題なく動くことを確認!


・13 4 マイクロポストの画像投稿

マイクロポストに関する基本的な操作はすべて実装できた
まずは開発環境用のβ版を実装して、
いくつかの改善を通して本番環境用の完成版を実装する

画像アップロード機能を追加するためには、2つの視覚的な要素の追加が必要。
1つは画像をアップロードするためのフォーム
もう1つは投稿された画像そのもの

CarrierWaveという画像アップローダを使う
mini_magicとfogも含めている点に注意
リサイズ用と、本番環境で画像をアップロードする用

・13 4 1 基本的な画像アップロード

リスト 13.58: GemfileにCarrierWaveを追加する
----------------------------
source 'https://rubygems.org'
gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'
.
.
.
group :production do
 gem 'pg',  '0.20.0'
 gem 'fog', '1.42'
 end
.
.
.

いつものように
bundle install

rails generate uploader Picture

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails generate uploader Picture
Running via Spring preloader in process 8671
     create  app/uploaders/picture_uploader.rb
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------


ここで、Micropostsモデルにpicture(string)が追加される

開発環境用にマイグレーションファイルを生成

rails generate migration add_picture_to_microposts picture:string

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ rails generate migration add_picture_to_microposts picture:string
Running via Spring preloader in process 8684
     invoke  active_record
     create    db/migrate/20200105161724_add_picture_to_microposts.rb
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

いつものようにマイグレーション
rails db:migrate

そして、Micropostモデルに画像を追加する

リスト 13.59: Micropostモデルに画像を追加する
app/models/micropost.rb
----------------------------
class Micropost < ApplicationRecord
 belongs_to :user
 default_scope -> { order(created_at: :desc) }
 mount_uploader :picture, PictureUploader
 validates :user_id, presence: true
 validates :content, presence: true, length: { maximum: 140 }
end

システムによってはここで一旦railsサーバーを再起動させる必要がある
Guardを使っている場合はうまくいかない可能性があるのでGuardは新しいターミナルで実行する

フォームに画像アップローダを追加する

リスト 13.60: マイクロポスト投稿フォームに画像アップローダーを追加する
app/views/shared/_micropost_form.html.erb
----------------------------
<%= form_for(@micropost) do |f| %>
 <%= render 'shared/error_messages', object: f.object %>
 <div class="field">
   <%= f.text_area :content, placeholder: "Compose new micropost..." %>
 </div>
 <%= f.submit "Post", class: "btn btn-primary" %>
 <span class="picture">
   <%= f.file_field :picture %>
 </span>
<% end %>

Webから更新できる許可リストにpicture属性を追加する

リスト 13.61: pictureを許可された属性のリストに追加する
app/controllers/microposts_controller.rb
----------------------------
class MicropostsController < ApplicationController
 before_action :logged_in_user, only: [:create, :destroy]
 before_action :correct_user,   only: :destroy
 .
 .
 .
 private
   def micropost_params
     params.require(:micropost).permit(:content, :picture)
   end
   def correct_user
     @micropost = current_user.microposts.find_by(id: params[:id])
     redirect_to root_url if @micropost.nil?
   end
end

Micropostパーシャルのimage_tageヘルパーでその画像を描画できるようになる

画像のないマイクロポストでは画像を表示させないようにするためにpicture?という論理値を返すメソッドを使っている点に注目
このメソッドはCarrierWaveが自動的に生成してくれているメソッド

リスト 13.62: マイクロポストの画像表示を追加する
app/views/microposts/_micropost.html.erb
----------------------------
<li id="micropost-<%= micropost.id %>">
 <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
 <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
 <span class="content">
   <%= micropost.content %>
   <%= image_tag micropost.picture.url if micropost.picture? %>
 </span>
 <span class="timestamp">
   Posted <%= time_ago_in_words(micropost.created_at) %> ago.
   <% if current_user?(micropost.user) %>
     <%= link_to "delete", micropost, method: :delete,
                                      data: { confirm: "You sure?" } %>
   <% end %>
 </span>
</li>

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

はい

リスト 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アクション内のマイクロポストにアクセスするようになります。

cp app/assets/images/rails.png test/fixtures/
を実行

リスト 13.63: 画像アップロードをテストするためのテンプレート
test/integration/microposts_interface_test.rb
----------------------------
require 'test_helper'
class MicropostInterfaceTest < ActionDispatch::IntegrationTest
 def setup
   @user = users(:michael)
 end
 test "micropost interface" do
   log_in_as(@user)
   get root_path
   assert_select 'div.pagination'
   assert_select 'input[type="file"]'
   # 無効な送信
   post microposts_path, params: { micropost: { content: "" } }
   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?
   follow_redirect!
   assert_match content, response.body
   # 投稿を削除する
   assert_select 'a', '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

ちなみにここでrails testを走らせてもエラーになるのでエラーでも心配しなくていい

以下のログ
ログ
----------------------------
$ rails test
Running via Spring preloader in process 9800
Started with run options --seed 39071
                 
ERROR["test_should_get_home", StaticPagesControllerTest, 0.010664004000318528]
test_should_get_home#StaticPagesControllerTest (0.01s)
NameError:         NameError: uninitialized constant Micropost::PictureUploader
           app/models/micropost.rb:4:in `<class:Micropost>'
           app/models/micropost.rb:1:in `<top (required)>'
FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.6414625710003747]
test_micropost_interface#MicropostsInterfaceTest (1.64s)
       Expected at least 1 element matching "div#error_explanation", found 0..
       Expected 0 to be >= 1.
       test/integration/microposts_interface_test.rb:20:in `block in <class:MicropostsInterfaceTest>'
FAIL["test_content_should_be_present", MicropostTest, 2.1318265959998826]
test_content_should_be_present#MicropostTest (2.13s)
       Expected true to be nil or false
       test/models/micropost_test.rb:21:in `block in <class:MicropostTest>'
FAIL["test_user_id_should_be_present", MicropostTest, 2.1404694530001507]
test_user_id_should_be_present#MicropostTest (2.14s)
       Expected true to be nil or false
       test/models/micropost_test.rb:16:in `block in <class:MicropostTest>'
FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 2.1441072590005206]
test_content_should_be_at_most_140_characters#MicropostTest (2.14s)
       Expected true to be nil or false
       test/models/micropost_test.rb:26:in `block in <class:MicropostTest>'
 59/59: [=====] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.14595s
59 tests, 299 assertions, 4 failures, 1 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
-------
ログ
----------------------------
croposts) $ rails test
Running via Spring preloader in process 10127
Started with run options --seed 11912
                                         

ERROR["test_should_be_valid", MicropostTest, 0.01233680500081391]
test_should_be_valid#MicropostTest (0.01s)
NameError:         NameError: uninitialized constant Micropost::PictureUploader
           app/models/micropost.rb:4:in `<class:Micropost>'
           app/models/micropost.rb:1:in `<top (required)>'
 57/0: [] 0% Time: 00:00:00,  ETA: ??:??: FAIL["test_user_id_should_be_present", MicropostTest, 0.48713144800058217]
test_user_id_should_be_present#MicropostTest (0.49s)
       Expected true to be nil or false
       test/models/micropost_test.rb:16:in `block in <class:MicropostTest>'
 57/1: [] 1% Time: 00:00:00,  ETA: 00:00: FAIL["test_content_should_be_at_most_140_characters", MicropostTest, 0.4915882950008381]
test_content_should_be_at_most_140_characters#MicropostTest (0.49s)
       Expected true to be nil or false
       test/models/micropost_test.rb:26:in `block in <class:MicropostTest>'
FAIL["test_content_should_be_present", MicropostTest, 0.49529548100144893]
test_content_should_be_present#MicropostTest (0.50s)
       Expected true to be nil or false
       test/models/micropost_test.rb:21:in `block in <class:MicropostTest>'
 57/5: [] 8% Time: 00:00:00,  ETA: 00:00:  57/6: [] 10% Time: 00:00:00,  ETA: 00:00  57/7: [] 12% Time: 00:00:01,  ETA: 00:00  57/8: [] 14% Time: 00:00:01,  ETA: 00:00  57/9: [] 15% Time: 00:00:01,  ETA: 00:00  57/10: [] 17% Time: 00:00:01,  ETA: 00:0  57/11: [] 19% Time: 00:00:01,  ETA: 00:0  57/13: [] 22% Time: 00:00:01,  ETA: 00:0  57/15: [] 26% Time: 00:00:01,  ETA: 00:0  57/18: [] 31% Time: 00:00:01,  ETA: 00:0  57/19: [] 33% Time: 00:00:01,  ETA: 00:0  57/20: [] 35% Time: 00:00:01,  ETA: 00:0  57/22: [] 38% Time: 00:00:01,  ETA: 00:0  57/24: [] 42% Time: 00:00:01,  ETA: 00:0  57/26: [] 45% Time: 00:00:01,  ETA: 00:0  57/28: [] 49% Time: 00:00:01,  ETA: 00:0  57/29: [] 50% Time: 00:00:01,  ETA: 00:0  57/30: [] 52% Time: 00:00:01,  ETA: 00:0  57/31: [] 54% Time: 00:00:01,  ETA: 00:0  57/32: [] 56% Time: 00:00:01,  ETA: 00:0  57/34: [] 59% Time: 00:00:01,  ETA: 00:0  57/35: [] 61% Time: 00:00:01,  ETA: 00:0  57/36: [] 63% Time: 00:00:01,  ETA: 00:0  57/37: [] 64% Time: 00:00:01,  ETA: 00:0  57/38: [] 66% Time: 00:00:01,  ETA: 00:0  57/39: [] 68% Time: 00:00:01,  ETA: 00:0  57/40: [] 70% Time: 00:00:01,  ETA: 00:0  57/43: [] 75% Time: 00:00:01,  ETA: 00:0  57/45: [] 78% Time: 00:00:01,  ETA: 00:0  57/47: [] 82% Time: 00:00:01,  ETA: 00:0  57/50: [] 87% Time: 00:00:01,  ETA: 00:0  57/51: [] 89% Time: 00:00:01,  ETA: 00:0  57/54: [] 94% Time: 00:00:01,  ETA: 00:0  57/55: [] 96% Time: 00:00:01,  ETA: 00:0  57/57: [] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.00215s
57 tests, 291 assertions, 3 failures, 1 errors, 0 skips
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------

・13 4 2 画像の検証

問題点がある
ユーザーが巨大すぎるファイルをアップロード
無効なファイルをアップロード

これを直す

画像サイズやフォーマットに対するバリデーションを実装

サーバー用とクライアント用の両方に追加する

バリデーションは、コメントアウトされたコードを有効化するだけでよい

リスト 13.64: 画像フォーマットのバリデーション
app/uploaders/picture_uploader.rb
----------------------------
class PictureUploader < CarrierWave::Uploader::Base
 storage :file
 # アップロードファイルの保存先ディレクトリは上書き可能
 # 下記はデフォルトの保存先  
 def store_dir
   "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
 end
 # アップロード可能な拡張子のリスト
 def extension_whitelist
   %w(jpg jpeg gif png)
 end
end

画像のサイズの制御は、Micropostモデルに書き足す

自動では作られないため、手動でpicture_sizeという独自のバリデーションを定義する必要がある。

今まで使っていたvalidatesメソッドではなく、validateメソッドを使っている点に注目

リスト 13.65: 画像に対するバリデーションを追加する
app/models/micropost.rb
----------------------------
class Micropost < ApplicationRecord
 belongs_to :user
 default_scope -> { order(created_at: :desc) }
 mount_uploader :picture, PictureUploader
 validates :user_id, presence: true
 validates :content, presence: true, length: { maximum: 140 }
 validate  :picture_size
 private
   # アップロードされた画像のサイズをバリデーションする
   def picture_size
     if picture.size > 5.megabytes
       errors.add(:picture, "should be less than 5MB")
     end
   end
end

ファイルサイズはjQueryでチェックする

リスト 13.66: ファイルサイズをjQueryでチェックする
app/views/shared/_micropost_form.html.erb
----------------------------
<%= form_for(@micropost) do |f| %>
 <%= render 'shared/error_messages', object: f.object %>
 <div class="field">
   <%= f.text_area :content, placeholder: "Compose new micropost..." %>
 </div>
 <%= f.submit "Post", class: "btn btn-primary" %>
 <span class="picture">
   <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
 </span>
<% end %>
<script type="text/javascript">
 $('#micropost_picture').bind('change', function() {
   var size_in_megabytes = this.files[0].size/1024/1024;
   if (size_in_megabytes > 5) {
     alert('Maximum file size is 5MB. Please choose a smaller file.');
   }
 });
</script>

実は、これでは完全に阻止はできない。アラートを無視してアップロードを強行する、といったことが可能。
今回はjQueryチュートリアルではないため、まだ不完全ということだけ把握すればOK

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

ログ
----------------------------
Maximum file size is 5MB. Please choose a smaller file.
----------------------------


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

ログ
----------------------------
The form contains 1 error.
Picture You are not allowed to upload "bmp" files, allowed types: jpg, jpeg, gif, png
----------------------------


・13 4 3 画像のリサイズ

大きすぎる画像サイズのものがアップロードされるとレイアウトが壊れる
なので、リサイズされるようにする

クラウドIDEであれば、次のコマンドでこのプログラムをインストールできる
sudo yum install -y ImageMagick

ログ
----------------------------
ec2-user:~/environment/sample_app (user-microposts) $ sudo yum install -y ImageMagick
Loaded plugins: priorities, update-motd,
             : upgrade-helper
amzn-main          | 2.1 kB     00:00     

amzn-updates       | 2.5 kB     00:00     
1067 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package ImageMagick.x86_64 0:6.7.8.9-15.21.amzn1 will be installed
--> Processing Dependency: libtiff.so.5(LIBTIFF_4.0)(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Processing Dependency: libwmflite-0.2.so.7()(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Processing Dependency: libtiff.so.5()(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Processing Dependency: libltdl.so.7()(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Processing Dependency: libjasper.so.1()(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Processing Dependency: libgs.so.8()(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Processing Dependency: libXt.so.6()(64bit) for package: ImageMagick-6.7.8.9-15.21.amzn1.x86_64
--> Running transaction check
---> Package ghostscript.x86_64 0:8.70-24.26.amzn1 will be installed
--> Processing Dependency: urw-fonts >= 1.1 for package: ghostscript-8.70-24.26.amzn1.x86_64
--> Processing Dependency: ghostscript-fonts for package: ghostscript-8.70-24.26.amzn1.x86_64
--> Processing Dependency: libcupsimage.so.2()(64bit) for package: ghostscript-8.70-24.26.amzn1.x86_64
--> Processing Dependency: libcups.so.2()(64bit) for package: ghostscript-8.70-24.26.amzn1.x86_64
--> Processing Dependency: libcairo.so.2()(64bit) for package: ghostscript-8.70-24.26.amzn1.x86_64
---> Package jasper-libs.x86_64 0:1.900.1-21.9.amzn1 will be installed
---> Package libXt.x86_64 0:1.1.4-6.1.9.amzn1 will be installed
---> Package libtiff.x86_64 0:4.0.3-32.34.amzn1 will be installed
--> Processing Dependency: libjbig.so.2.0()(64bit) for package: libtiff-4.0.3-32.34.amzn1.x86_64
---> Package libtool-ltdl.x86_64 0:2.4.2-20.4.8.5.32.amzn1 will be installed
---> Package libwmf-lite.x86_64 0:0.2.8.4-41.13.amzn1 will be installed
--> Running transaction check
---> Package cairo.x86_64 0:1.12.14-6.8.amzn1 will be installed
--> Processing Dependency: libpixman-1.so.0()(64bit) for package: cairo-1.12.14-6.8.amzn1.x86_64
--> Processing Dependency: libGL.so.1()(64bit) for package: cairo-1.12.14-6.8.amzn1.x86_64
---> Package cups-libs.x86_64 1:1.4.2-67.21.amzn1 will be installed
--> Processing Dependency: libavahi-common.so.3()(64bit) for package: 1:cups-libs-1.4.2-67.21.amzn1.x86_64
--> Processing Dependency: libavahi-client.so.3()(64bit) for package: 1:cups-libs-1.4.2-67.21.amzn1.x86_64
---> Package ghostscript-fonts.noarch 0:5.50-23.2.7.amzn1 will be installed
---> Package jbigkit-libs.x86_64 0:2.0-11.4.amzn1 will be installed
---> Package urw-fonts.noarch 0:2.4-10.7.amzn1 will be installed
--> Running transaction check
---> Package avahi-libs.x86_64 0:0.6.25-12.17.amzn1 will be installed
---> Package libglvnd-glx.x86_64 1:0.2.999-14.20170308git8e6e102.3.amzn1 will be installed
--> Processing Dependency: libglvnd(x86-64) = 1:0.2.999-14.20170308git8e6e102.3.amzn1 for package: 1:libglvnd-glx-0.2.999-14.20170308git8e6e102.3.amzn1.x86_64
--> Processing Dependency: mesa-libGL(x86-64) >= 13.0.4-1 for package: 1:libglvnd-glx-0.2.999-14.20170308git8e6e102.3.amzn1.x86_64
--> Processing Dependency: libGLdispatch.so.0()(64bit) for package: 1:libglvnd-glx-0.2.999-14.20170308git8e6e102.3.amzn1.x86_64
---> Package pixman.x86_64 0:0.32.4-4.11.amzn1 will be installed
--> Running transaction check
---> Package libglvnd.x86_64 1:0.2.999-14.20170308git8e6e102.3.amzn1 will be installed
---> Package mesa-libGL.x86_64 0:17.1.5-2.41.amzn1 will be installed
--> Processing Dependency: mesa-libglapi(x86-64) = 17.1.5-2.41.amzn1 for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Processing Dependency: libxshmfence.so.1()(64bit) for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Processing Dependency: libglapi.so.0()(64bit) for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Processing Dependency: libdrm.so.2()(64bit) for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Processing Dependency: libXxf86vm.so.1()(64bit) for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Processing Dependency: libXfixes.so.3()(64bit) for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Processing Dependency: libXdamage.so.1()(64bit) for package: mesa-libGL-17.1.5-2.41.amzn1.x86_64
--> Running transaction check
---> Package libXdamage.x86_64 0:1.1.3-4.7.amzn1 will be installed
---> Package libXfixes.x86_64 0:5.0.1-2.1.8.amzn1 will be installed
---> Package libXxf86vm.x86_64 0:1.1.3-2.1.9.amzn1 will be installed
---> Package libdrm.x86_64 0:2.4.82-1.14.amzn1 will be installed
--> Processing Dependency: libpciaccess.so.0()(64bit) for package: libdrm-2.4.82-1.14.amzn1.x86_64
---> Package libxshmfence.x86_64 0:1.2-1.4.amzn1 will be installed
---> Package mesa-libglapi.x86_64 0:17.1.5-2.41.amzn1 will be installed
--> Processing Dependency: mesa-dri-drivers(x86-64) = 17.1.5-2.41.amzn1 for package: mesa-libglapi-17.1.5-2.41.amzn1.x86_64
--> Running transaction check
---> Package libpciaccess.x86_64 0:0.13.1-4.1.11.amzn1 will be installed
---> Package mesa-dri-drivers.x86_64 0:17.1.5-2.41.amzn1 will be installed
--> Processing Dependency: mesa-filesystem(x86-64) = 17.1.5-2.41.amzn1 for package: mesa-dri-drivers-17.1.5-2.41.amzn1.x86_64
--> Running transaction check
---> Package mesa-filesystem.x86_64 0:17.1.5-2.41.amzn1 will be installed
--> Finished Dependency Resolution
Dependencies Resolved
==========================================
Package      Arch   Version
                      Repository    Size
==========================================
Installing:
ImageMagick  x86_64 6.7.8.9-15.21.amzn1
                      amzn-main    2.5 M
Installing for dependencies:
avahi-libs   x86_64 0.6.25-12.17.amzn1
                      amzn-main     64 k
cairo        x86_64 1.12.14-6.8.amzn1
                      amzn-main    739 k
cups-libs    x86_64 1:1.4.2-67.21.amzn1
                      amzn-main    364 k
ghostscript  x86_64 8.70-24.26.amzn1
                      amzn-updates 8.5 M
ghostscript-fonts
             noarch 5.50-23.2.7.amzn1
                      amzn-main    634 k
jasper-libs  x86_64 1.900.1-21.9.amzn1
                      amzn-main    159 k
jbigkit-libs x86_64 2.0-11.4.amzn1
                      amzn-main     47 k
libXdamage   x86_64 1.1.3-4.7.amzn1
                      amzn-main     19 k
libXfixes    x86_64 5.0.1-2.1.8.amzn1
                      amzn-main     18 k
libXt        x86_64 1.1.4-6.1.9.amzn1
                      amzn-main    185 k
libXxf86vm   x86_64 1.1.3-2.1.9.amzn1
                      amzn-main     17 k
libdrm       x86_64 2.4.82-1.14.amzn1
                      amzn-main    162 k
libglvnd     x86_64 1:0.2.999-14.20170308git8e6e102.3.amzn1
                      amzn-main     94 k
libglvnd-glx x86_64 1:0.2.999-14.20170308git8e6e102.3.amzn1
                      amzn-main    156 k
libpciaccess x86_64 0.13.1-4.1.11.amzn1
                      amzn-main     26 k
libtiff      x86_64 4.0.3-32.34.amzn1
                      amzn-updates 439 k
libtool-ltdl x86_64 2.4.2-20.4.8.5.32.amzn1
                      amzn-main     51 k
libwmf-lite  x86_64 0.2.8.4-41.13.amzn1
                      amzn-updates  70 k
libxshmfence x86_64 1.2-1.4.amzn1
                      amzn-main    6.0 k
mesa-dri-drivers
             x86_64 17.1.5-2.41.amzn1
                      amzn-main    5.1 M
mesa-filesystem
             x86_64 17.1.5-2.41.amzn1
                      amzn-main     23 k
mesa-libGL   x86_64 17.1.5-2.41.amzn1
                      amzn-main    170 k
mesa-libglapi
             x86_64 17.1.5-2.41.amzn1
                      amzn-main     51 k
pixman       x86_64 0.32.4-4.11.amzn1
                      amzn-main    283 k
urw-fonts    noarch 2.4-10.7.amzn1
                      amzn-main    3.2 M
Transaction Summary
==========================================
Install  1 Package (+25 Dependent packages)
Total download size: 23 M
Installed size: 63 M
Downloading packages:
(1/26): avahi-libs-0 |  64 kB   00:00     
(2/26): cairo-1.12.1 | 739 kB   00:00     
(3/26): cups-libs-1. | 364 kB   00:00     
(4/26): ImageMagick- | 2.5 MB   00:01     
(5/26): ghostscript- | 634 kB   00:00     
(6/26): jasper-libs- | 159 kB   00:00     
(7/26): jbigkit-libs |  47 kB   00:00     
(8/26): libXdamage-1 |  19 kB   00:00     
(9/26): libXfixes-5. |  18 kB   00:00     
(10/26): libXt-1.1.4 | 185 kB   00:00     
(11/26): libXxf86vm- |  17 kB   00:00     
(12/26): libdrm-2.4. | 162 kB   00:00     
(13/26): libglvnd-0. |  94 kB   00:00     
(14/26): libglvnd-gl | 156 kB   00:00     
(15/26): libpciacces |  26 kB   00:00     
(16/26): libtool-ltd |  51 kB   00:00     
(17/26): libtiff-4.0 | 439 kB   00:00     
(18/26): libwmf-lite |  70 kB   00:00     
(19/26): libxshmfenc | 6.0 kB   00:00     
(20/26): ghostscript | 8.5 MB   00:01     
(21/26): mesa-filesy |  23 kB   00:00     
(22/26): mesa-libGL- | 170 kB   00:00     
(23/26): mesa-libgla |  51 kB   00:00     
(24/26): pixman-0.32 | 283 kB   00:00     
(25/26): mesa-dri-dr | 5.1 MB   00:01     
(26/26): urw-fonts-2 | 3.2 MB   00:00     
------------------------------------------
Total        5.0 MB/s |  23 MB  00:04     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
 Installing : jasper-libs-1.900.    1/26 
 Installing : libXfixes-5.0.1-2.    2/26 
 Installing : libXt-1.1.4-6.1.9.    3/26 
 Installing : libXdamage-1.1.3-4    4/26 
 Installing : libpciaccess-0.13.    5/26 
 Installing : libdrm-2.4.82-1.14    6/26 
 Installing : libwmf-lite-0.2.8.    7/26 
 Installing : libXxf86vm-1.1.3-2    8/26 
 Installing : mesa-filesystem-17    9/26 
 Installing : mesa-libglapi-17.1   10/26 
 Installing : mesa-dri-drivers-1   11/26 
 Installing : ghostscript-fonts-   12/26 
 Installing : avahi-libs-0.6.25-   13/26 
 Installing : urw-fonts-2.4-10.7   14/26 
 Installing : 1:libglvnd-0.2.999   15/26 
 Installing : pixman-0.32.4-4.11   16/26 
 Installing : jbigkit-libs-2.0-1   17/26 
 Installing : libtiff-4.0.3-32.3   18/26 
 Installing : 1:cups-libs-1.4.2-   19/26 
 Installing : libxshmfence-1.2-1   20/26 
 Installing : mesa-libGL-17.1.5-   21/26 
 Installing : 1:libglvnd-glx-0.2   22/26 
 Installing : cairo-1.12.14-6.8.   23/26 
 Installing : ghostscript-8.70-2   24/26 
 Installing : libtool-ltdl-2.4.2   25/26 
 Installing : ImageMagick-6.7.8.   26/26 
 Verifying  : 1:cups-libs-1.4.2-    1/26 
 Verifying  : libXt-1.1.4-6.1.9.    2/26 
 Verifying  : libtool-ltdl-2.4.2    3/26 
 Verifying  : ghostscript-8.70-2    4/26 
 Verifying  : libtiff-4.0.3-32.3    5/26 
 Verifying  : 1:libglvnd-glx-0.2    6/26 
 Verifying  : libxshmfence-1.2-1    7/26 
 Verifying  : jbigkit-libs-2.0-1    8/26 
 Verifying  : ImageMagick-6.7.8.    9/26 
 Verifying  : cairo-1.12.14-6.8.   10/26 
 Verifying  : pixman-0.32.4-4.11   11/26 
 Verifying  : libXdamage-1.1.3-4   12/26 
 Verifying  : 1:libglvnd-0.2.999   13/26 
 Verifying  : urw-fonts-2.4-10.7   14/26 
 Verifying  : mesa-libGL-17.1.5-   15/26 
 Verifying  : libdrm-2.4.82-1.14   16/26 
 Verifying  : avahi-libs-0.6.25-   17/26 
 Verifying  : mesa-dri-drivers-1   18/26 
 Verifying  : libXfixes-5.0.1-2.   19/26 
 Verifying  : jasper-libs-1.900.   20/26 
 Verifying  : ghostscript-fonts-   21/26 
 Verifying  : mesa-filesystem-17   22/26 
 Verifying  : libXxf86vm-1.1.3-2   23/26 
 Verifying  : libwmf-lite-0.2.8.   24/26 
 Verifying  : mesa-libglapi-17.1   25/26 
 Verifying  : libpciaccess-0.13.   26/26 
Installed:
 ImageMagick.x86_64 0:6.7.8.9-15.21.amzn1
Dependency Installed:
 avahi-libs.x86_64 0:0.6.25-12.17.amzn1  
 cairo.x86_64 0:1.12.14-6.8.amzn1        
 cups-libs.x86_64 1:1.4.2-67.21.amzn1    
 ghostscript.x86_64 0:8.70-24.26.amzn1   
 ghostscript-fonts.noarch 0:5.50-23.2.7.amzn1
 jasper-libs.x86_64 0:1.900.1-21.9.amzn1 
 jbigkit-libs.x86_64 0:2.0-11.4.amzn1    
 libXdamage.x86_64 0:1.1.3-4.7.amzn1     
 libXfixes.x86_64 0:5.0.1-2.1.8.amzn1    
 libXt.x86_64 0:1.1.4-6.1.9.amzn1        
 libXxf86vm.x86_64 0:1.1.3-2.1.9.amzn1   
 libdrm.x86_64 0:2.4.82-1.14.amzn1       
 libglvnd.x86_64 1:0.2.999-14.20170308git8e6e102.3.amzn1
 libglvnd-glx.x86_64 1:0.2.999-14.20170308git8e6e102.3.amzn1
 libpciaccess.x86_64 0:0.13.1-4.1.11.amzn1
 libtiff.x86_64 0:4.0.3-32.34.amzn1      
 libtool-ltdl.x86_64 0:2.4.2-20.4.8.5.32.amzn1
 libwmf-lite.x86_64 0:0.2.8.4-41.13.amzn1
 libxshmfence.x86_64 0:1.2-1.4.amzn1     
 mesa-dri-drivers.x86_64 0:17.1.5-2.41.amzn1
 mesa-filesystem.x86_64 0:17.1.5-2.41.amzn1
 mesa-libGL.x86_64 0:17.1.5-2.41.amzn1   
 mesa-libglapi.x86_64 0:17.1.5-2.41.amzn1
 pixman.x86_64 0:0.32.4-4.11.amzn1       
 urw-fonts.noarch 0:2.4-10.7.amzn1       
Complete!
ec2-user:~/environment/sample_app (user-microposts) $ 
----------------------------
御覧の通り結構時間かかった


本番環境がHerokuであれば、既に本番環境でImageMagickが使えるようになっている
ローカル環境での開発の場合は、Homebrewを導入して、brew install imagemagickコマンドを使ってインストールするらしい


今回は、大きなもののみ400x400でリサイズする

リスト 13.67: 画像をリサイズするために画像アップローダーを修正する
app/uploaders/picture_uploader.rb
----------------------------
class PictureUploader < CarrierWave::Uploader::Base
 include CarrierWave::MiniMagick
 process resize_to_limit: [400, 400]
 storage :file
 # アップロードファイルの保存先ディレクトリは上書き可能
 # 下記はデフォルトの保存先  
 def store_dir
   "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
 end
 # アップロード可能な拡張子のリスト
 def extension_whitelist
   %w(jpg jpeg gif png)
 end
end

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

うまくいってる!


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

リスト 13.68: テスト時は画像のリサイズをさせない設定
config/initializers/skip_image_resizing.rb
----------------------------
if Rails.env.test?
 CarrierWave.configure do |config|
   config.enable_processing = false
 end
end

・13 4 4 本番環境での画像アップロード

うまくいかなければスキップしても問題ない手順だけど今回はやっていく

本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにしてみる

リスト 13.69: 本番環境での画像アップロードを調整する
app/uploaders/picture_uploader.rb
----------------------------
class PictureUploader < CarrierWave::Uploader::Base
 include CarrierWave::MiniMagick
 process resize_to_limit: [400, 400]
 if Rails.env.production?
   storage :fog
 else
   storage :file
 end
 # アップロードファイルの保存先ディレクトリは上書き可能
 # 下記はデフォルトの保存先  
 def store_dir
   "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
 end
 # アップロード可能な拡張子のリスト
 def extension_whitelist
   %w(jpg jpeg gif png)
 end
end

S3を使う

手順としては下記の通り
1.AWSにサインアップ
2.AWS Identity and Access Management(IAM)でユーザーを作成し、AccessキーとSecretキーをメモする
3.AWS ConsoleからS3 bucketを作成(bucketの名前はなんでも大丈夫)し、2.で作成したユーザーに対してRead権限とWrite権限を付与する

手順は以下にまとめる。
開発環境構築備忘録 AWS IAM編
https://note.com/el93019205/n/n4fea93e656ad

開発環境構築備忘録 AWS S3編
https://note.com/el93019205/n/n0031b3939ecb

S3アカウントの作成と設定が終わったら、CarrierWaveの設定ファイルを次のように修正する


リスト 13.70: CarrierWaveを通してS3を使うように修正する
config/initializers/carrier_wave.rb
----------------------------
if Rails.env.production?
 CarrierWave.configure do |config|
   config.fog_credentials = {
     # Amazon S3用の設定
     :provider              => 'AWS',
     :region                => ENV['S3_REGION'],     # 例: 'ap-northeast-1'
     :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
     :aws_secret_access_key => ENV['S3_SECRET_KEY']
   }
   config.fog_directory     =  ENV['S3_BUCKET']
 end
end


regionは領域の意味

S3_REGIONには何を設定すればいいんだ・・・?
ap-northeast-1を設定すればいいらしい
これは東京のリージョン

リスト 13.71: .gitignoreファイルにアップロード用ディレクトリを追加する
----------------------------
.
.
.
# アップロードされたテスト画像を無視する
/public/uploads

そして、いつものようにpush

rails test
git add -A
git commit -m "Add user microposts"
git checkout master
git merge user-microposts
git push origin master

source <(curl -sL https://cdn.learnenough.com/heroku_install)
heroku login --interactive
heroku create
git remote set-url heroku *******.git
heroku rename jun-killer-rails13

SendGridのアドオンのときは環境変数を自動的に設定してくれていたが、今回は手動で設定する必要がある

次のようにheroku上の環境変数を設定する

heroku config:set S3_ACCESS_KEY="ココに先ほどメモしたAccessキーを入力"
heroku config:set S3_SECRET_KEY="同様に、Secretキーを入力"
heroku config:set S3_BUCKET="Bucketの名前を入力"
heroku config:set S3_REGION="ap-northeast-1"

heroku maintenance:on
git push heroku master
heroku pg:reset DATABASE
jun-killer-rails13
heroku run rails db:migrate
heroku run rails db:seed
heroku restart
heroku maintenance:off

https://jun-killer-rails13.herokuapp.com/

画像がアップロードできない・・・
https://takanash-tech.hatenablog.jp/entry/2018/12/19/120923
を参考にs3バケットの設定を弄る

いけた!!!!!!!!!!!

・13 5 最後に


リスト 13.72: サンプルアプリケーションのGemfile (完成)
----------------------------
source 'https://rubygems.org'
gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
gem 'will_paginate',           '3.1.6'
gem 'bootstrap-will_paginate', '1.0.0'
gem 'bootstrap-sass',          '3.3.7'
gem 'puma',                    '3.9.1'
gem 'sass-rails',              '5.0.6'
gem 'uglifier',                '3.2.0'
gem 'coffee-rails',            '4.2.2'
gem 'jquery-rails',            '4.3.1'
gem 'turbolinks',              '5.0.1'
gem 'jbuilder',                '2.7.0'
group :development, :test do
 gem 'sqlite3', '1.3.13'
 gem 'byebug',  '9.0.6', platform: :mri
end
group :development do
 gem 'web-console',           '3.5.1'
 gem 'listen',                '3.1.5'
 gem 'spring',                '2.0.2'
 gem 'spring-watcher-listen', '2.0.1'
end
group :test do
 gem 'rails-controller-testing', '1.0.2'
 gem 'minitest',                 '5.10.3'
 gem 'minitest-reporters',       '1.1.14'
 gem 'guard',                    '2.14.1'
 gem 'guard-minitest',           '2.4.6'
end
group :production do
 gem 'pg',   '0.20.0'
 gem 'fog',  '1.42'
end
# Windows環境ではtzinfo-dataというgemを含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]


・13 5 1 本章のまとめ


ActiveRecordモデルの力によって、マイクロポストも(ユーザーと同じで)リソースとして扱える
Railsは複数のキーインデックスをサポートしている
Userは複数のキーインデックスをサポートしている
Userは複数のMicropostsを持っていて(has_many)、Micropostは1人のUserに依存している(belongs_to)といった関係性をモデル化した
has_manyやbelongs_toを利用することで、関連付けを通して多くのメソッドが使えるようになった
user.microposts.build(...)というコードは、引数で与えたユーザーに関連付けされたマイクロポストを返す
default_scopeを使うとデフォルトの順序を変更できる
default_scopeは引数に無名関数(->)を取る
dependent: :destroyオプションを使うと、関連付けされたオブジェクトと自分自身を同時に削除できる
paginateメソッドやcountメソッドは、どちらも関連付けを通して実行され、効率的にデータベースに問い合わせしている
fixtureは、関連付けを使ったオブジェクトの作成もサポートしている
パーシャルを呼び出すときに、一緒に変数を渡すことができる
whereメソッドを使うと、ActiveRecordを通して選択(部分集合を取り出すこと)ができる
依存しているオブジェクトを作成/削除するときは、常に関連付けを通すようにすることで、よりセキュアな操作が実現できる
CarrierWaveを使うと画像アップやリサイズができる


この記事が気に入ったらサポートをしてみませんか?