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を使うと画像アップやリサイズができる
この記事が気に入ったらサポートをしてみませんか?