演習1
RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラムであるcreated_atとupdated_atには何が入っているでしょうか?
指示通りにやってみると、nilとなります
micropost = Micropost.new
=> #<Micropost:0x00007fd6b50cfe98 id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> micropost.user_id = User.first.id
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> 1
irb(main):003:0> micropost.content = "Lorem ipsum"
=> "Lorem ipsum"
irb(main):004:0> micropost.created_at
=> nil
irb(main):005:0> 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:0x00007fd6b4e0b630
id: 1,
name: "Example User",
email: "example@railstutorial.org",
created_at: Sun, 01 Oct 2023 08:28:59.861078000 UTC +00:00,
updated_at: Tue, 03 Oct 2023 20:58:27.716591000 UTC +00:00,
password_digest: "$2a$12$0bjsfJOdgTsq9bBRNTJFbu7.Z7LEPIoz4Q8KJIjcxX9UOOu5LcK2a",
remember_digest: "$2a$12$T159OCf/5y10RMmQb8zCH.KLUna7G3CJFpbnJn1zr73jcNk8OKA.C",
admin: true,
activation_digest: "$2a$12$pkJnv9FWgk753jd0oPM4TeV7y4P2q7fZNgfTXZH.D.ATGhwZrAnz2",
activated: true,
activated_at: Sun, 01 Oct 2023 08:28:59.606888000 UTC +00:00,
reset_digest: "$2a$12$O6oFLkLe8Cc81C0VCQPi2.sDODGvr801KmgmZ0G/AgSffQ1xz6FqO",
reset_sent_at: Tue, 03 Oct 2023 20:58:27.716286000 UTC +00:00>
micropost.user.name
=> "Example User"
先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?
データベースに保存されると、更新日時などが示されました
> micropost.save
TRANSACTION (0.1ms) begin transaction
Micropost Create (0.5ms) INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["content", "Lorem ipsum"], ["user
micropost.created_at
=> Sun, 08 Oct 2023 07:00:16.111795000 UTC +00:00
irb(main):014:0> micropost.updated_at
=> Sun, 08 Oct 2023 07:00:16.111795000 UTC +00:00
演習2
Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?
ropost
irb(main):004:0> micropost = Micropost.new
=> #<Micropost:0x00007f191cb0d358 id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):005:0> micropost.valid?
=> false
micropost.errors.full_messages
=> ["User must exist", "User can't be blank", "Content can't be blank"]
コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?
上記の演習からそのまま続行しています
micropost.content = "a"*141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
irb(main):011:0> micropost.valid?
=> false
irb(main):012:0> micropost.errors.full_messages
=> ["User must exist", "User can't be blank", "Content is too long (maximum is 140 characters)"]
演習3
データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?
前回のマイクロポストを残してしまっていますが、そのまま載せています
user = User.first
User Load (5.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=>
#<User:0x00007fc9406b3350
...
irb(main):002:0> micropost = user.microposts.create(content: "Lorem ipsum")
TRANSACTION (0.1ms) begin transaction
Micropost Create (12.3ms) INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2023-10-08 21:37:34.533382"], ["updated_at", "2023-10-08 21:37:34.533382"]]
TRANSACTION (31.2ms) commit transaction
先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?
user.microposts.find(micropost.id)
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ? [["user_id", 1], ["id", 2], ["LIMIT", 1]]
=>
#<Micropost:0x00007fc9413d26d0
id: 2,
content: "Lorem ipsum",
user_id: 1,
created_at: Sun, 08 Oct 2023 21:37:34.533382000 UTC +00:00,
updated_at: Sun, 08 Oct 2023 21:37:34.533382000 UTC +00:00>
irb(main):005:0> user.microposts.find(micropost)
/usr/local/rvm/gems/default/gems/activerecord-7.0.4.3/lib/active_record/relation/finder_methods.rb:466:in `find_one': You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. (ArgumentError)
raise ArgumentError, <<-MSG.squish
micropostにするとエラーになるようです。これはrails5.1から警告からエラーになるように変わったようです
まあ、かならずfindを使用する場合にはidを付けてあげましょうということですね
user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。
irb(main):006:0> user == micropost.user
=> true
irb(main):007:0> user.microposts.first == micropost
=> false
演習4
Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。
最初の投稿と最後の投稿で時間帯が異なることを確認します
Micropost.first.created_at
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ? [["LIMIT", 1]]
=> Sun, 08 Oct 2023 21:37:34.533382000 UTC +00:00
irb(main):002:0> Micropost.last.created_at
Micropost Load (0.3ms) SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ? [["LIMIT", 1]]
=> Sun, 08 Oct 2023 07:00:16.111795000 UTC +00:00
Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか?(ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。)
irb(main):003:0> Micropost.first
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ? [["LIMIT", 1]]
Micropost.last
Micropost Load (0.1ms) SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ? [["LIMIT", 1]]
注目して欲しいのは降順か、照準かということになります
Micropost.firstは"created_at" DESC
つまり降順から最初の投稿を拾っているのに対し
Micropost.lastはcreated_at" ASC
つまり昇順から拾っています
データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。
#userを代入
user = User.first
user.microposts.first
id: 1,
content: "Lorem ipsum",
user_id: 1,
created_at: Sun, 08 Oct 2023 07:00:16.111795000 UTC +00:00,
updated_at: Sun, 08 Oct 2023 07:00:16.111795000 UTC +00:00>
#ユーザーを削除
irb(main):003:0> user.destroy
id: 1,
name: "Example User",
email: "example@railstutorial.org",
created_at: Sun, 01 Oct 2023 08:28:59.861078000 UTC +00:00,
updated_at: Tue, 03 Oct 2023 20:58:27.716591000 UTC +00:00,
password_digest: "$2a$12$0bjsfJOdgTsq9bBRNTJFbu7.Z7LEPIoz4Q8KJIjcxX9UOOu5LcK2a",
remember_digest: "$2a$12$T159OCf/5y10RMmQb8zCH.KLUna7G3CJFpbnJn1zr73jcNk8OKA.C",
admin: true,
activation_digest: "$2a$12$pkJnv9FWgk753jd0oPM4TeV7y4P2q7fZNgfTXZH.D.ATGhwZrAnz2",
activated: true,
activated_at: Sun, 01 Oct 2023 08:28:59.606888000 UTC +00:00,
reset_digest: "$2a$12$O6oFLkLe8Cc81C0VCQPi2.sDODGvr801KmgmZ0G/AgSffQ1xz6FqO",
reset_sent_at: Tue, 03 Oct 2023 20:58:27.716286000 UTC +00:00>
#マイクロポストを検索
Micropost.find
/usr/local/rvm/gems/default/gems/activerecord-7.0.4.3/lib/active_record/relation/finder_methods.rb:455:in `find_with_ids': Couldn't find Micropost without an ID (ActiveRecord::RecordNotFound)
演習5
7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.agoや6.months.agoを実行してみましょう。
helper.time_ago_in_words(3.weeks.ago)
=> "21 days"
irb(main):005:0> helper.time_ago_in_words(6.month.ago)
=> "6 months"
helper.time_ago_in_words(1.year.ago)と実行すると、どういった結果が返ってくるでしょうか?
helper.time_ago_in_words(1.year.ago)
=> "about 1 year"
micropostsオブジェクトのクラスは何でしょうか? (ヒント: リスト 13.24内のコードを参考に、まずはpaginate(page: nil)でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。)
user.microposts.paginate(page: nil)
Micropost Load (2.4ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ? [["user_id", 1], ["LIMIT", 30], ["OFFSET", 0]]
=> []
irb(main):012:0> user.microposts.paginate(page: nil).class
=> Micropost::ActiveRecord_AssociationRelation
演習6
(1..10).to_a.take(6)というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。[
1から10までの値のうち、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::University.name
=> "Southern Kutch College"
Faker::Hipster.word
=> "taxidermy"
Faker::ChuckNorris.fact
=> "When Chuck Norris gives a method an argument, the method loses."
余談ですが、ちゃんと日本のキャラクターも生成できるようです笑
irb(main):014:0> Faker::JapaneseMedia::DragonBall.character
=> "Shenron"
irb(main):015:0> Faker::JapaneseMedia::DragonBall.character
=> "Android 16"
irb(main):016:0> Faker::JapaneseMedia::DragonBall.character
=> "Super Saiyan Trunks"
irb(main):017:0> Faker::JapaneseMedia::DragonBall.character
=> "Guru"
irb(main):018:0> Faker::JapaneseMedia::DragonBall.character
=> "Chi-Chi"
fakerのgithubのREAD.meの下の方にあるので、遊んでみてください
演習7
リスト 13.29にある2つの'h1'のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが green から red に変わることを確認してみてください。
該当するのが'users/show'の部分のため、show.html.erbをのぞいてみましょう
このコードからh1タグをコメントアウトしてみます
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>
h1が無かったよというエラーが発生します
UsersProfileTest#test_profile_display (2.57s)
Expected at least 1 element matching "h1", found 0.
Expected 0 to be >= 1.
test/integration/users_profile_test.rb:14:in `block in <class:UsersProfileTest>'
72/72: [=======================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.82599s
72 tests, 230 assertions, 1 failures, 0 errors, 0 skips
リスト 13.29にあるテストを変更して、will_paginateが1回だけ表示されていることをテストしてみましょう。(ヒント: 表 5.2を参考にしてください。)
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',count: 1 #ここを追加
@user.microposts.paginate(page: 1).each do |micropost|
assert_match micropost.content, response.body
end
end
end
演習8
なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。
各コントローラが継承するApplicationコントローラに移したため、UsersでもMicropostsでも共通して使用できるようになったため、わざわざUsersコントローラー内に置くのはコードが重複して無駄が多くなるためです
演習9
Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。
まずはhome.html.erbを条件分岐だけを残し、2つの新しいファイルを作成し
そのファイル内に条件分岐するコードを入れ、パーシャルしていきます
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 %>
続いて2つのファイルを作成しましょう
touch app/views/static_pages/_home_lodgged_in.html.erb
touch app/views/static_pages/_home_not_logged_in.html.erb
その中にコードを入れていきます
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>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.svg", alt: "Rails logo", width: "200px"),
"https://rubyonrails.org/" %>
マイクロポストを投稿した直後に、ブラウザの更新ボタンを押すとエラーが表示されます。なぜエラーが表示されるのでしょうか?その原因を考えてみましょう。
私は上記方法を試してみましたが、特にエラーが出ませんでした。
恐らくこのページのこのコードが影響しているはずです
app/controllers/microposts_controller.rb
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', status: :unprocessable_entity
end
end
ここで投稿に失敗した場合、renderで'static_pages/home'が反映されますが、これはただこのページを反映しているだけで、この状態で更新してもルーティング上にGETリクエストが存在しないため、ルートエラーになる?のかなと思います。
実はここの解答ですが、読み進めていくとこれに対する説明が書いてあります
そこから考えるにやはり/micropostsにはgetが必要であるなと言うことが分かりますね
もし上記の現象に対応するとしたら、どんな対応方法があるでしょうか?その対応方法を考えてみましょう。(ヒント: さまざまな対応方法がありますが、対応方法によっては今後の実装に支障が出ることがあります。ここでは対応方法のアイデア出しに留めておきましょう。)
redirect_toなどを使い、失敗した場合は、root_urlに戻るのが一番良いのではないかと思います
演習10
新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。
"content", "user_id", "created_at", "updated_at"
この4つをデータベースに送っているようですね
INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at")
VALUES (?, ?, ?, ?) [["content", "test"], ["user_id", 1],
["created_at", "2023-10-12 06:53:29.340704"], ["updated_at", "2023-10-12 06:53:29.340704"]]
コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where("user_id = ?", user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。(ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。)
user = User.first
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Micropost.where("user_id = ?"
user.microposts
user.feed
# 表示結果が共に長いので、省略
Micropost.where("user_id = ?", user.id) == user.microposts
Micropost Load (5.8ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
user.microposts == user.feed
Micropost Load (4.8ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC [["user_id", 1]]
Micropost Load (48.4ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
演習11
マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを開いて、DELETE文の内容を確認してみてください。
DELETE FROM "microposts" WHERE "microposts"."id" = ? [["id", 14105]]
↳ app/controllers/microposts_controller.rb:17:in `destroy'
リスト 13.56の2つのリダイレクトを、redirect_back_or_to(root_url, status: :see_other)と1行で置き換えてもうまく動くことを、ブラウザを使って確認してみましょう。これは、参照元URLがnilである場合は指定のURLにリダイレクトします。
def destroy
@micropost.destroy
flash[:success] = "Micropost deleted"
if request.referrer.nil?
redirect_back_or_to(root_url, status: :see_other)
end
end
演習12
リスト 13.59で示した4つのテスト項目が正しく動いているかをテスト項目ごとに確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが red になることを確認し、元に戻すと green になることを確認してみましょう。
テスト6つじゃない?と思いますが、こちらは動作確認のため、いったんパスします
サイドバーにあるマイクロポストの合計投稿数のテストを追加してみましょう。このとき、単数形のmicropostと複数形のmicropostsが正しく表示されているかどうかもテストしてください。(ヒント: リスト 13.61を参考にしてみてください。)
test "should display the right micropost count" do
get root_path
assert_match "#{@user.microposts.count} microposts", response.body
end
test "should user proper pluralization for zero microposts" do
log_in_as(users(:malory))
get root_path
assert_match "0 microposts", response.body
end
test "should user proper pluralization for one micropost" do
log_in_as(users(:lana))
get root_path
assert_match "1 micropost", response.body
end
演習13
画像付きのマイクロポストを投稿してみましょう。画像が大きすぎますか?(次の13.4.3で画像サイズの問題を修正します)。
めちゃ大きいです
リスト 13.67に示すテンプレートを参考に、13.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはターミナルでリスト 13.66を入力し、サンプル画像をfixtureディレクトリに追加してください。継続を表すバックスラッシュ記号\は入力が必要ですが、シェルで自動的に追加される2行目冒頭の >記号は入力しないようご注意ください。リスト 13.67で追加したアサーションでは、Homeページにあるファイルアップロードと、投稿に成功した時に画像が表示されているかどうかをチェックしています。なお、テスト内にあるfixture_file_uploadというメソッドは、fixtureで定義されたファイルをアップロードする特別なメソッドです22 。(ヒント: image属性が有効かどうかを確かめるときは、11.3.3で紹介したassignsメソッドを使ってください。このメソッドを使うと、投稿に成功した後にcreateアクション内のマイクロポストにアクセスするようになります。)
test/integration/microposts_interface_test.rb
class ImageUploadTest < MicropostsInterface
test "should have a file input field for images" do
get root_path
assert_select 'input[type=file]'
end
test "should be able to attach an image" do
cont = "This micropost really ties the room together."
img = fixture_file_upload('kitten.jpg', 'image/jpeg')
post microposts_path, params: { micropost: { content: cont, image: img } }
assert assigns(:micropost).image.attached?
end
end
演習14
5MB以上の画像ファイルを送信しようとした場合、どうなりますか?
無効な拡張子のファイルを送信しようとした場合、どうなりますか?
そもそも無効な拡張子が選択できないようになっていたので、送信できませんでした
演習15
サイズの大きい画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形の場合も正しくリサイズされていますか?
正しくリサイズされています