はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。
このままだとプログラムがまったく書けない人になってしまう危機感(迫り来る35歳定年説)と、
新しいことに挑戦したいという思いから、
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。
これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
13.2.1 マイクロポストの描画
本章での学び
【事前準備】サンプルデータのリセット
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20170417215343 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0019s
== 20170417215343 CreateUsers: migrated (0.0021s) =============================
== 20170423125906 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
-> 0.0011s
== 20170423125906 AddIndexToUsersEmail: migrated (0.0017s) ====================
== 20170424041610 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
-> 0.0008s
== 20170424041610 AddPasswordDigestToUsers: migrated (0.0008s) ================
== 20170514215307 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
-> 0.0006s
== 20170514215307 AddRememberDigestToUsers: migrated (0.0007s) ================
== 20170614215124 AddAdminToUsers: migrating ==================================
-- add_column(:users, :admin, :boolean, {:default=>false})
-> 0.0016s
== 20170614215124 AddAdminToUsers: migrated (0.0017s) =========================
== 20170618221238 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
-> 0.0007s
-- add_column(:users, :activated, :boolean, {:dafault=>false})
-> 0.0004s
-- add_column(:users, :activated_at, :datetime)
-> 0.0005s
== 20170618221238 AddActivationToUsers: migrated (0.0023s) ====================
== 20170703042006 AddResetToUsers: migrating ==================================
-- add_column(:users, :reset_digest, :string)
-> 0.0007s
-- add_column(:users, :reset_sent_at, :datetime)
-> 0.0003s
== 20170703042006 AddResetToUsers: migrated (0.0016s) =========================
== 20170719214241 CreateMicroposts: migrating =================================
-- create_table(:microposts)
-> 0.0016s
-- add_index(:microposts, [:user_id, :created_at])
-> 0.0009s
== 20170719214241 CreateMicroposts: migrated (0.0032s) ========================
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:seed
【事前準備】Micropostsコントローラの生成
yokoyan:~/workspace/sample_app (user-microposts) $ rails generate controller Microposts
Running via Spring preloader in process 2577
Expected string default value for '--jbuilder'; got true (boolean)
Expected string default value for '--helper'; got true (boolean)
Expected string default value for '--assets'; got true (boolean)
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
【事前準備】micropostパーシャルの作成
yokoyan:~/workspace/sample_app (user-microposts) $ pwd
/home/ubuntu/workspace/sample_app
yokoyan:~/workspace/sample_app (user-microposts) $ touch app/views/microposts/_micropost.html.erb
【View】micropostパーシャルの実装
マイクロポストの内容を一覧表示するパーシャルを作成する。
将来的な拡張を見越して、マイクロポスト毎にidを割り振っている。
はじめて出てきた、time_ago_in_wordsは、ActionViewのヘルパーメソッド
「3分前に投稿」というような文字列を返すらしい。
<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>
【controller】ページング処理の実装のための事前準備
Usersコントローラから、ユーザのページングを行うのであれば、
ユーザープロフィール画面(View)のwill_paginateの引数に@usersを明示的に渡す必要はない。
今回は、Usersコントローラから、マイクロポストのページング処理を行うため、
同画面(View)のwill_paginateの引数に@microposts変数をwill_paginateに明示的に渡す必要がある。
Usersコントローラにて、@microposts変数を定義する。
def show
@user = User.find(params[:id])
@microposts = @user.microposts.paginate(page: params[:page])
redirect_to root_url and return unless @user.activated?
#debugger
end
【View】ユーザープロフィール画面にマイクロポストを表示する
<% 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>
演習1
7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.agoや6.months.agoを実行してみましょう。
>> helper.time_ago_in_words(3.weeks.ago)
=> "21 days"
>>
?> helper.time_ago_in_words(6.month.ago)
=> "6 months"
演習2
helper.time_ago_in_words(1.year.ago)と実行すると、どういった結果が返ってくるでしょうか?
?> helper.time_ago_in_words(1.year.ago)
=> "about 1 year"
演習3
micropostsオブジェクトのクラスは何でしょうか? ヒント: リスト 13.23内のコードににあるように、まずはpaginateメソッド (引数はpage: nil) でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。
?> user = User.first
User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-07-25 21:45:26", updated_at: "2017-07-25 21:45:26", password_digest: "$2a$10$G03Oh9yI0VMHnGF38MA3iOkoHiLtJ2qnuQ8whUZ8lEp...", remember_digest: nil, admin: true, activation_digest: "$2a$10$M9ShPLhv8mtQE6Yb4.BDMeTjvoWnGI1xfeEAvka1I7Z...", activated: true, activated_at: "2017-07-25 21:45:26", reset_digest: nil, reset_sent_at: nil>
>>
?> microposts = user.microposts.paginate(page: nil)
Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ? [["user_id", 1], ["LIMIT", 30], ["OFFSET", 0]]
=> #<ActiveRecord::AssociationRelation []>
>>
?> microposts.class
=> Micropost::ActiveRecord_AssociationRelation
13.2.2 マイクロポストのサンプル
micropostsオブジェクトのクラスは何でしょうか? ヒント: リスト 13.23内のコードににあるように、まずはpaginateメソッド (引数はpage: nil) でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。
本章での学び
【事前準備】サンプルデータにマイクロポストを追加する
サンプルユーザのうち、IDが小さい順に6人呼び出して、50件のマイクロポストを登録する。
ちなみに、lorem ipsumとは、意味のない典型的なダミーテキストのこと。
wiki:lorem ipsum
【事前準備】DBのリセット
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20170417215343 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0014s
== 20170417215343 CreateUsers: migrated (0.0015s) =============================
== 20170423125906 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
-> 0.0010s
== 20170423125906 AddIndexToUsersEmail: migrated (0.0011s) ====================
== 20170424041610 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
-> 0.0008s
== 20170424041610 AddPasswordDigestToUsers: migrated (0.0013s) ================
== 20170514215307 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
-> 0.0007s
== 20170514215307 AddRememberDigestToUsers: migrated (0.0008s) ================
== 20170614215124 AddAdminToUsers: migrating ==================================
-- add_column(:users, :admin, :boolean, {:default=>false})
-> 0.0013s
== 20170614215124 AddAdminToUsers: migrated (0.0014s) =========================
== 20170618221238 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
-> 0.0011s
-- add_column(:users, :activated, :boolean, {:dafault=>false})
-> 0.0006s
-- add_column(:users, :activated_at, :datetime)
-> 0.0005s
== 20170618221238 AddActivationToUsers: migrated (0.0024s) ====================
== 20170703042006 AddResetToUsers: migrating ==================================
-- add_column(:users, :reset_digest, :string)
-> 0.0010s
-- add_column(:users, :reset_sent_at, :datetime)
-> 0.0006s
== 20170703042006 AddResetToUsers: migrated (0.0023s) =========================
== 20170719214241 CreateMicroposts: migrating =================================
-- create_table(:microposts)
-> 0.0019s
-- add_index(:microposts, [:user_id, :created_at])
-> 0.0010s
== 20170719214241 CreateMicroposts: migrated (0.0035s) ========================
【事前準備】DBにサンプルデータを登録する
yokoyan:~/workspace/sample_app (user-microposts) $ rails db:seed
yokoyan:~/workspace/sample_app (user-microposts) $
【動作確認】マイクロポストの表示確認
登録が終わったら、Railsサーバを再起動して、ブラウザで確認。
【scss】マイクロポストのCSSを追加
/* 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;
}
}
【動作確認】マイクロポストの表示確認2
ブラウザで確認。50件のマイクロポストが表示され、ページネーションも動くことを確認。
演習1
(1..10).to_a.take(6)というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。
1から10までのうち先頭6件を表示。
?> (1..10).to_a.take(6)
=> [1, 2, 3, 4, 5, 6]
演習2
先ほどの演習にあったto_aメソッドの部分は本当に必要でしょうか? 確かめてみてください。
演習1と同じ結果となる。
?> (1..10).take(6)
=> [1, 2, 3, 4, 5, 6]
>>
演習3
Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に大学名や電話番号、Hipster IpsumやChuck Norris facts (参考: チャック・ノリスの真実) を画面に出力してみましょう。(訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力するfaker-okinawaもあります。ぜひ遊んでみてください。)
Fakerで大学名を表示する。
?> Faker::University.name
=> "Volkman Institute"
>>
Fakerで電話番号を表示する。
?> Faker::PhoneNumber.phone_number
=> "1-782-129-1939"
>>
Hipster(ジャズ?)のテキストを表示する。
?> Faker::Hipster.sentence
=> "Street forage tousled asymmetrical."
チャック・ノリスの真実(ジョーク)を表示する。
量子暗号はチャック・ノリスでは機能しませんwww
?> Faker::ChuckNorris.fact
=> "Quantum cryptography does not work on Chuck Norris. When something is being observed by Chuck it stays in the same state until he's finished. "
>>
faker-okinawaを試す。
【事前準備】faker-okinawaのインストール
gemfileを修正する。
※fakerと同時に入れるとエラーになったため、一旦fakerをコメントアウトする。
# gem 'faker', '1.6.6'
gem 'faker-okinawa'
バンドルする。
yokoyan:~/workspace/sample_app (user-microposts) $ bundle
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/.........
Fetching version metadata from https://rubygems.org/..
Fetching dependency metadata from https://rubygems.org/.
Resolving dependencies...
・・・略・・・
Installing faker-okinawa 0.1.0
Bundle complete! 26 Gemfile dependencies, 84 gems now installed.
Gems in the group production were not installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
gemをインストール。
yokoyan:~/workspace/sample_app (user-microposts) $ gem install faker-okinawa
Successfully installed faker-okinawa-0.1.0
1 gem installed
【動作確認】faker-okinawaを試してみる。
泡盛の名前を取得。
?> Faker::Okinawa::Awamori.name
=> "かねやま"
設定を戻す
動作確認が完了したら、fakerを元に戻す。
Gemfileを戻す。
gem 'faker', '1.6.6'
#gem 'faker-okinawa'
bundleする。
yokoyan:~/workspace/sample_app (user-microposts) $ bundle
fakerの再インストール。
yokoyan:~/workspace/sample_app (user-microposts) $ gem install faker
Fetching: faker-1.8.4.gem (100%)
Successfully installed faker-1.8.4
1 gem installed
13.2.3 プロフィール画面のマイクロポストをテストする
本章での学び
【事前準備】統合テストの作成
プロフィール画面用の統合テストを生成する。
yokoyan:~/workspace/sample_app (user-microposts) $ rails generate integration_test users_profile
Running via Spring preloader in process 1595
Expected string default value for '--jbuilder'; got true (boolean)
invoke test_unit
create test/integration/users_profile_test.rb
【事前準備】テストデータの生成
マイクロポスト用のfixtureファイルを修正する。
各マイクロポストデータに、user: michael
を追記すると、michaelユーザの投稿として関連付けされる。また、マイクロポストのページャーのテストを行うために、30回マイクロポストを自動生成する。
なお、ymlの各属性の後ろに:をつけないと、エラーが発生するので注意。
詳細は以下参照。
Railsチュートリアル中にcould not find expected ':' while scanning a simple keyが出た時の対処法
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 %>
【test】統合テストの実装
以下の仕様で実装する。
- GETリクエスト送信(ユーザープロフィールページ)
- ユーザープロフィールページ(users/show)のテンプレートが表示されること
- HTMLのtitle属性にユーザーの名前が表示されること
- HTMLのh1タグにユーザーの名前が表示されること
- HTMLのh1タグの下に、imgタグ(gravatorクラス付き)が表示されること
- ユーザーのマイクロポスト数をカウントして文字列に変換する。レスポンスのHTML内に存在するかチェック
- HTMLにdivタグ(paginationクラス付き)が表示されること
- ユーザーのマイクロポストから、paginateを使って1ページ目を取得する。取得した結果を繰り返す
- マイクロポストのcontent属性が、レスポンスのHTML内に存在するかチェック
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.gravator'
assert_match @user.microposts.count.to_s, response.body
assert_select 'div.pagination'
@user.microposts.paginate(page: 1) do |micropost|
assert_match micropost.content, response.body
end
end
end
【test】テスト実行
テスト結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 1230
Started with run options --seed 10439
54/54: [================================================================================================================================================================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.15487s
54 tests, 238 assertions, 0 failures, 0 errors, 0 skips
演習1
リスト 13.28にある2つの’h1’のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが green から redに変わることを確認してみてください。
viewの該当箇所のタグをコメントアウトする。
<!-- <h1> -->
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
テストがredになることを確認。
(確認後はコメントアウトを戻すこと)
yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 1915
Started with run options --seed 50720
FAIL["test_profile_display", UsersProfileTest, 1.6931300410069525]
test_profile_display#UsersProfileTest (1.69s)
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>'
54/54: [================================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.94467s
54 tests, 234 assertions, 1 failures, 0 errors, 0 skips
演習2
リスト 13.28にあるテストを変更して、will_paginateが1度のみ表示されていることをテストしてみましょう。ヒント: 表 5.2を参考にしてください。
will_paginateに該当する、div class=pagination
が1度のみ表示されていることをテストする。
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.gravator'
assert_match @user.microposts.count.to_s, response.body
assert_select 'div.pagination'
@user.microposts.paginate(page: 1) do |micropost|
assert_match micropost.content, response.body
end
assert_select 'div.pagination', count:1 #追加
end
テストがgreenになることを確認。
yokoyan:~/workspace/sample_app (user-microposts) $ rails test
Running via Spring preloader in process 2471
Started with run options --seed 42382
54/54: [================================================================================================================================================================================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.10291s
54 tests, 239 assertions, 0 failures, 0 errors, 0 skips
おわりに
お盆休みをはさんだため、久々の更新です。
マイクロポストのテストデータを作成し、テストコードの実装が完了しました。
そろそろ最新版 (Rails 5.1対応) が出そうなので、8月中に14章まで完了できるようにがんばります!