はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。
このままだとプログラムがまったく書けない人になってしまう危機感(迫り来る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
14.3.1 動機と計画
本章での学び
【test】ステータスフィードのテスト
- feed should have the right posts
- fixtureから、michaelユーザの情報を取得する
- fixtureから、archerユーザの情報を取得する
- fixtureから、lanaユーザの情報を取得する
- フォローしているユーザの投稿を確認
- lanaのマイクロポストを取得
- michaelのフィードに含まれていること
- lanaのマイクロポストを取得
- 自分自身の投稿を確認
- michaelのマイクロポストを取得
- michaelのフィードに含まれていること
- michaelのマイクロポストを取得
- フォローしていないユーザの投稿を確認
- archerのマイクロポストを取得
- michaelのフィードに含まれていないこと
- archerのマイクロポストを取得
上記を踏まえて実装する。
test "feed should have the right posts" do
michael = users(:michael)
archer = users(:archer)
lana = users(:lana)
# フォローしているユーザの投稿を確認
lana.microposts.each do |post_following|
assert michael.feed.include?(post_following)
end
# 自分自身の投稿を確認
michael.microposts.each do |post_self|
assert michael.feed.include?(post_self)
end
# フォローしていないユーザの投稿を確認
archer.microposts.each do |post_unfollowed|
assert_not michael.feed.include?(post_unfollowed)
end
end
テストは現時点ではredとなる。
演習1
マイクロポストのidが正しく並んでいると仮定して (すなわち若いidの投稿ほど古くなる前提で)、図 14.22のデータセットでuser.feed.map(&:id)を実行すると、どのような結果が表示されるでしょうか? 考えてみてください。ヒント: 13.1.4で実装したdefault_scopeを思い出してください。
user自身とuserがフォローしているマイクロポストが昇順で取得できる。
default_scopeで降順になるように実装していれば、マイクロポストが降順で取得できる。
14.3.2 フィードを初めて実装する
本章での学び
【事前準備】Rubyのmapメソッド
mapメソッドは、&とメソッドに対応するシンボルを使った短縮表記ができる。
okoyan:~/workspace/sample_app (following-users) $ rails console
Running via Spring preloader in process 2367
Loading development environment (Rails 5.0.0.1)
>> [1,2,3,4].map { |i| i.to_s }
=> ["1", "2", "3", "4"]
>>
?> [1,2,3,4].map(&:to_s)
=> ["1", "2", "3", "4"]
>>
カンマ区切りの文字列としてつなげることもできる。
?> [1,2,3,4].map(&:to_s).join(', ')
=> "1, 2, 3, 4"
user.followingにある各要素のidを呼び出し、フォローしているユーザのidを配列とする。
?> User.first.following.map(&:id)
User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
User Load (0.7ms) SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
ActiveRecordで用意しているfollowing_ids
メソッドを使うことで、上記と同じ結果が得られる。
?> User.first.following_ids
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.3ms) SELECT "users".id FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
応用して、カンマ区切りの文字列を取得することもできる。
?> User.first.following_ids.join(', ')
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.4ms) SELECT "users".id FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
=> "3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51"
【test】テスト実行
テスト実行時に以下のようなエラーが発生。
7ffe7035b000-7ffe7035d000 r--p 00000000 00:00 0 [vvar]
7ffe7035d000-7ffe7035f000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html
エラーになるため、bundle update
を実行。
yokoyan:~/workspace/sample_app (following-users) $ bundle update
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.....
Using rake 12.0.0
Using CFPropertyList 2.3.5
Using concurrent-ruby 1.0.5
Installing i18n 0.8.6 (was 0.8.1)
Installing minitest 5.10.3 (was 5.10.1)
Using thread_safe 0.3.6
Using builder 3.2.3
Using erubis 2.7.0
Installing mini_portile2 2.2.0 (was 2.1.0)
Installing rack 2.0.3 (was 2.0.1)
Using nio4r 1.2.1
Using websocket-extensions 0.1.2
Using mime-types-data 3.2016.0521
Using arel 7.1.4
Using ansi 1.5.0
Using execjs 2.7.0
Using bcrypt 3.1.11
Installing rb-fsevent 0.10.2 (was 0.9.8)
Using ffi 1.9.18
Using will_paginate 3.1.0
Using bundler 1.14.6
Using byebug 9.0.0
Using coderay 1.1.1
Using coffee-script-source 1.12.2
Using method_source 0.8.2
Installing thor 0.20.0 (was 0.19.4)
Installing debug_inspector 0.0.3 (was 0.0.2) with native extensions
Using excon 0.58.0
Using formatador 0.2.5
Using multi_json 1.12.1
Using ipaddress 0.8.3
Using xml-simple 1.1.5
Using inflecto 0.0.2
Using json 1.8.6
Using trollop 2.1.2
Installing lumberjack 1.0.12 (was 1.0.11)
Using nenv 0.3.0
Using shellany 0.0.1
Using slop 3.6.0
Using guard-compat 1.2.1
Using mini_magick 4.7.0
Using ruby-progressbar 1.8.1
Using puma 3.4.0
Installing tilt 2.0.8 (was 2.0.6)
Using spring 1.7.2
Using sqlite3 1.3.11
Installing turbolinks-source 5.0.3 (was 5.0.0)
Using fission 0.5.0
Using faker 1.6.6
Installing tzinfo 1.2.3 (was 1.2.2)
Installing nokogiri 1.8.0 (was 1.7.0.1) with native extensions
Using rack-test 0.6.3
Using sprockets 3.7.1
Using websocket-driver 0.6.5
Using mime-types 3.1
Installing autoprefixer-rails 7.1.3 (was 6.7.7.1)
Using uglifier 3.0.0
Installing rb-inotify 0.9.10 (was 0.9.8)
Using bootstrap-will_paginate 0.0.10
Using coffee-script 2.4.1
Using fog-core 1.45.0
Using notiffany 0.1.1
Using pry 0.10.4
Using guard-minitest 2.4.4
Using minitest-reporters 1.1.9
Using turbolinks 5.0.1
Using activesupport 5.0.0.1
Using loofah 2.0.3
Using rbvmomi 1.11.3
Installing mail 2.6.6 (was 2.6.4)
Installing sass-listen 4.0.0
Using listen 3.0.8
Using fog-json 1.0.2
Using fog-xml 0.1.3
Using fog-local 0.3.1
Using fog-vmfusion 0.1.0
Installing rails-dom-testing 2.0.3 (was 2.0.2)
Installing globalid 0.4.0 (was 0.3.7)
Using activemodel 5.0.0.1
Using jbuilder 2.4.1
Using rails-html-sanitizer 1.0.3
Installing fog-vsphere 1.12.0 (was 1.11.3)
Installing sass 3.5.1 (was 3.4.23)
Using guard 2.13.0
Using spring-watcher-listen 2.0.0
Using fog-aliyun 0.2.0
Using fog-brightbox 0.13.0
Using fog-dnsimple 1.0.0
Using fog-openstack 0.1.21
Using fog-profitbricks 4.0.0
Using fog-sakuracloud 1.7.5
Using fog-serverlove 0.1.2
Using fog-softlayer 1.1.4
Using fog-storm_on_demand 0.1.1
Using fog-atmos 0.1.0
Installing fog-aws 1.4.1 (was 1.4.0)
Using fog-cloudatcost 0.1.2
Using fog-digitalocean 0.3.0
Using fog-dynect 0.0.3
Using fog-ecloud 0.3.0
Using fog-google 0.1.0
Using fog-powerdns 0.1.1
Using fog-rackspace 0.1.5
Using fog-radosgw 0.0.5
Using fog-riakcs 0.1.0
Using fog-terremark 0.1.0
Using fog-voxel 0.1.0
Using fog-xenserver 0.3.0
Using activejob 5.0.0.1
Using activerecord 5.0.0.1
Using carrierwave 1.1.0
Using actionview 5.0.0.1
Using bootstrap-sass 3.3.6
Using fog 1.40.0
Using actionpack 5.0.0.1
Using actioncable 5.0.0.1
Using actionmailer 5.0.0.1
Using railties 5.0.0.1
Using sprockets-rails 3.2.0
Using rails-controller-testing 0.1.1
Using coffee-rails 4.2.1
Using jquery-rails 4.1.1
Using web-console 3.1.1
Using rails 5.0.0.1
Using sass-rails 5.0.6
Bundle updated!
Gems in the group production were not installed.
【test】再度テストを実行
テスト結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (following-users) $ rails test
Running via Spring preloader in process 15601
Started with run options --seed 9757
DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only ] 18% Time: 00:00:02, ETA: 00:00:10
the following keyword arguments in future Rails versions:
params, headers, env, xhr, as
Examples:
get '/profile',
params: { id: 1 },
headers: { 'X-Extra-Header' => '123' },
env: { 'action_dispatch.custom' => 'custom' },
xhr: true,
as: :json
(called from block (2 levels) in <class:MicropostsInterfaceTest> at /home/ubuntu/workspace/sample_app/test/integration/microposts_interface_test.rb:27)
75/75: [====================================================================================================================================================================================================] 100% Time: 00:00:05, Time: 00:00:05
Finished in 5.33761s
75 tests, 335 assertions, 0 failures, 0 errors, 0 skips
演習1
リスト 14.44において、現在のユーザー自身の投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?
user.feed
の実行結果は以下の通り。
フォローしているユーザのIDと、自分のIDを渡している。
yokoyan:~/workspace/sample_app (following-users) $ rails console
Running via Spring preloader in process 1248
Loading development environment (Rails 5.0.0.1)
>>
?> 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-08-26 04:58:19", updated_at: "2017-08-26 04:58:19", password_digest: "$2a$10$0FM4wRDzxU.nOpDa5RmxZuyTz/omIcX4Qm4nguWAde0...", remember_digest: nil, admin: true, activation_digest: "$2a$10$gQQiCXRCh4ZDsruNiSv5euZW4Vwt7kcKm6tZkxM1MMg...", activated: true, activated_at: "2017-08-26 04:58:19", reset_digest: nil, reset_sent_at: nil>
>>
?> user.feed
(0.6ms) SELECT "users".id FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? [["follower_id", 1]]
Micropost Load (3.2ms) SELECT "microposts".* FROM "microposts" WHERE (user_id IN (3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51) OR user_id = 1) ORDER BY "microposts"."created_at" DESC
つまり、自分の投稿を含めないようにするには、2つ目の引数を削除する。
def feed
# Micropost.where("user_id = ?", id)
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
Micropost.where("user_id IN (?) ", following_ids)
end
以下のテストで結果がredとなる。
FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.9879938876256347]
test_micropost_interface#MicropostsInterfaceTest (1.99s)
Expected at least 1 element matching "div.pagination", found 0..
Expected 0 to be >= 1.
test/integration/microposts_interface_test.rb:14:in `block in <class:MicropostsInterfaceTest>'
FAIL["test_feed_should_have_the_right_posts", UserTest, 3.517132450826466]
test_feed_should_have_the_right_posts#UserTest (3.52s)
Expected false to be truthy.
test/models/user_test.rb:108:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:107:in `block in <class:UserTest>'
演習2
リスト 14.44において、フォローしているユーザーの投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?
演習1と逆で、1つめの引数を削除する。
(試作フィードの状態に戻すことになる)
def feed
Micropost.where("user_id = ?", id)
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
end
以下のテストがredになることを確認。
FAIL["test_feed_should_have_the_right_posts", UserTest, 2.452872692141682]
test_feed_should_have_the_right_posts#UserTest (2.45s)
Expected false to be truthy.
test/models/user_test.rb:104:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:103:in `block in <class:UserTest>'
演習3
リスト 14.44において、フォローしていないユーザーの投稿を含めるためにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか? ヒント: 自分自身とフォローしているユーザー、そしてそれ以外という集合は、いったいどういった集合を表すのか考えてみてください。
フォローしていないユーザーの投稿を含めるためには、
micropostsテーブルのすべての情報を取得する。
def feed
# Micropost.where("user_id = ?", id)
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
Micropost.all
end
以下のテストがredになることを確認。
フォローしていないユーザが含まれないことが期待値であるためエラーとなっている。
FAIL["test_feed_should_have_the_right_posts", UserTest, 1.5933369509875774]
test_feed_should_have_the_right_posts#UserTest (1.59s)
Expected true to be nil or false
test/models/user_test.rb:112:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:111:in `block in <class:UserTest>'
14.3.3 サブセレクト
本章での学び
今の実装ではパフォーマンスに懸念があるため、サブセレクトを使って解決する。
【model】feedメソッドのリファクタリング
同じ変数を複数の場所に挿入する場合は、whereメソッド内の変数にキーと値のペアにしたほうが便利とのこと。
def feed
# 自分のみ
# Micropost.where("user_id = ?", id)
# フォローしているユーザか自分の投稿
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
Micropost.where("user_id IN (:following_ids) OR user_id = :user_id", following_ids: following_ids, user_id: id)
# フォローしていないユーザも含める
# Micropost.all
end
上記からさらに、サブクエリのSQLを追加する。
def feed
# フォローしているユーザか自分の投稿
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
# Micropost.where("user_id IN (:following_ids) OR user_id = :user_id", following_ids: following_ids, user_id: id)
#最終的な実装
following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)
動作確認
テストがgreenになることを確認。
yokoyan:~/workspace/sample_app (following-users) $ rails test
Running via Spring preloader in process 2913
Started with run options --seed 15753
DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only===== ] 66% Time: 00:00:03, ETA: 00:00:02
the following keyword arguments in future Rails versions:
params, headers, env, xhr, as
Examples:
get '/profile',
params: { id: 1 },
headers: { 'X-Extra-Header' => '123' },
env: { 'action_dispatch.custom' => 'custom' },
xhr: true,
as: :json
(called from block (2 levels) in <class:MicropostsInterfaceTest> at /home/ubuntu/workspace/sample_app/test/integration/microposts_interface_test.rb:27)
75/75: [=================================================================================================================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.46490s
75 tests, 335 assertions, 0 failures, 0 errors, 0 skips
ブラウザからもフォローしているユーザのフィードが表示されていることを確認。
MASTERブランチへのマージ
yokoyan:~/workspace/sample_app (master) $ rails test
yokoyan:~/workspace/sample_app (master) $ git add -A
yokoyan:~/workspace/sample_app (master) $ git status
yokoyan:~/workspace/sample_app (master) $ git commit -m "Add user following"
yokoyan:~/workspace/sample_app (master) $ git checkout master
yokoyan:~/workspace/sample_app (master) $ git merge following-users
Herokuへのデプロイ
yokoyan:~/workspace/sample_app (master) $ git push
yokoyan:~/workspace/sample_app (master) $ git push heroku
HerokuのDBのマイグレーション
本番環境のDBのリセット、マイグレーション、テストデータの登録を行う。
yokoyan:~/workspace/sample_app (master) $ heroku pg:reset DATABASE
yokoyan:~/workspace/sample_app (master) $ heroku run rails db:migrate
yokoyan:~/workspace/sample_app (master) $ heroku run rails db:seed
演習1
Homeページで表示される1ページ目のフィードに対して、統合テストを書いてみましょう。リスト 14.49はそのテンプレートです。
- feed on Home page
- getリクエストを送信する(root_path)
-
@userのフィードを生成し、1ページ目を取得する。micropostの数だけ繰り返す。
- micropost内のcontentをエスケープして、HTML内に表示されること
上記を踏まえて実装する。
test "feed on Home page" do
get root_path
@user.feed.paginate(page: 1).each do |micropost|
assert_match CGI.escapeHTML(micropost.content), response.body
end
end
テスト結果がgreenであることを確認。
yokoyan:~/workspace/sample_app (master) $ rails test
Running via Spring preloader in process 1866
Started with run options --seed 52063
DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only ] 68% Time: 00:00:03, ETA: 00:00:02
the following keyword arguments in future Rails versions:
params, headers, env, xhr, as
Examples:
get '/profile',
params: { id: 1 },
headers: { 'X-Extra-Header' => '123' },
env: { 'action_dispatch.custom' => 'custom' },
xhr: true,
as: :json
(called from block (2 levels) in <class:MicropostsInterfaceTest> at /home/ubuntu/workspace/sample_app/test/integration/microposts_interface_test.rb:27)
76/76: [===================================================================================================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.53767s
76 tests, 395 assertions, 0 failures, 0 errors, 0 skips
演習2
リスト 14.49のコードでは、期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしています (このメソッドは11.2.3で扱ったCGI.escapeと同じ用途です)。このコードでは、なぜHTMLをエスケープさせる必要があったのでしょうか? 考えてみてください。ヒント: 試しにエスケープ処理を外して、得られるHTMLの内容を注意深く調べてください。マイクロポストの内容が何かおかしいはずです。また、ターミナルの検索機能 (Cmd-FもしくはCtrl-F) を使って「sorry」を探すと原因の究明に役立つはずです。
content属性に含まれる改行コードを含め、エスケープするため。
エスケープ処理を外して検証。
test "feed on Home page" do
get root_path
@user.feed.paginate(page: 1).each do |micropost|
# assert_match CGI.escapeHTML(micropost.content), response.body
assert_match micropost.content, response.body
end
end
テスト結果がredになることを確認。
yokoyan:~/workspace/sample_app (master) $ rails test
Running via Spring preloader in process 2044
Started with run options --seed 8292
FAIL["test_feed_on_Home_page", FollowingTest, 2.3300648159347475]
test_feed_on_Home_page#FollowingTest (2.33s)
Expected /I'm\ sorry\.\ Your\ words\ made\ sense,\ but\ your\ sarcastic\ tone\ did\ not\./ to match "<!DOCTYPE html>\n<html>\n <head>\n <title>Ruby on Rails Tutorial Sample App</title>\n \n <link rel=\"stylesheet\" media=\"all\" href=\"/assets/application-10fa38882e9521d0e9f81abaad450c57aa1940c84d7a510d6d14e6ade7210745.css\" data-turbolinks-track=\"reload\" />\n <script src=\"/assets/application-8145d5d9dada575ad93c1a2586a1fff566ccef067d7d39e629336a3561895941.js\" data-turbolinks-track=\"reload\"></script>\n <!--[if lt IE 9]>\n <script scr=\"//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js\">\n </script>\n <![endif]-->\n </head>\n <body>\n <header class=\"navbar navbar-fixed-top navbar-inverse\">\n <div class=\"container\">\n <a id=\"logo\" href=\"/\">sample app</a>\n <nav>\n <ul class=\"nav navbar-nav navbar-right\">\n <li><a href=\"/\">Home</a></li>\n <li><a href=\"/help\">Help</a></li>\n <li><a href=\"/users\">Users</a></li>\n <li class=\"dropdown\">\n <a href=\"#\" class=\"dropdown-toggle\" data-toggle=\"dropdown\">\n Account <b class=\"caret\"></b>\n </a>\n <ul class=\"dropdown-menu\">\n <li><a href=\"/users/762146111\">Profile</a></li>\n <li><a href=\"/users/762146111/edit\">Settings</a></li>\n <li class=\"divider\"></li>\n <li>\n <a rel=\"nofollow\" data-method=\"delete\" href=\"/logout\">Log out</a>\n </li>\n </ul>\n </li>\n </ul>\n </nav>\n </div>\n </header>\n\n <div class=\"container\">\n <div class=\"row\">\n <aside class=\"col-md-4\">\n <section class=\"user_info\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n<h1>Michael Example</h1>\n<span><a href=\"/users/762146111\">view my profile</a></span>\n<span>34 microposts</span>\n </section>\n <section class=\"stats\">\n <div class=\"stats\">\n <a href=\"/users/762146111/following\">\n <strong id=\"following\" class=\"stat\">\n 2\n </strong>\n following\n </a>\n <a href=\"/users/762146111/followers\">\n <strong id=\"followers\" class=\"stat\">\n 2\n </strong>\n followers\n </a>\n</div>\n </section>\n <section>\n <form class=\"new_micropost\" id=\"new_micropost\" enctype=\"multipart/form-data\" action=\"/microposts\" accept-charset=\"UTF-8\" method=\"post\"><input name=\"utf8\" type=\"hidden\" value=\"✓\" />\n \n <div class=\"field\">\n <textarea placeholder=\"Compose new micropost...\" name=\"micropost[content]\" id=\"micropost_content\">\n</textarea>\n </div>\n <input type=\"submit\" name=\"commit\" value=\"Post\" class=\"btn btn-primary\" data-disable-with=\"Post\" />\n <span class=\"picture\">\n <input accept=\"image/jpeg,image/gif,image/png\" type=\"file\" name=\"micropost[picture]\" id=\"micropost_picture\" />\n </span>\n</form>\n<script type=\"text/javascript\">\n $('#micropost_picture').bind('change', function() {\n var size_in_megabytes = this.files[0].size/1024/1024;\n if (size_in_megabytes > 5) {\n alert('Maximum file size is 5MB. Please choose a smaller file.')\n }\n });\n</script>\n </section>\n </aside>\n <div class=\"col-md-8\">\n <h3>Micropost Feed</h3>\n <ol class=\"microposts\">\n <li id=\"micropost-941832919\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Writing a short test\n \n </span>\n <span class=\"timestamp\">\n Posted less than a minute ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/941832919\">delete</a>\n </span>\n</li>\n<li id=\"micropost-3454773\">\n <a href=\"/users/409608538\"><img alt=\"Lana Kane\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/de9a58df9617af487e8b28dbb3aa50de?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/409608538\">Lana Kane</a></span>\n <span class=\"content\">\n I'm sorry. Your words made sense, but your sarcastic tone did not.\n \n </span>\n <span class=\"timestamp\">\n Posted 10 minutes ago.\n </span>\n</li>\n<li id=\"micropost-499495288\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n I just ate an orange!\n \n </span>\n <span class=\"timestamp\">\n Posted 10 minutes ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/499495288\">delete</a>\n </span>\n</li>\n<li id=\"micropost-12348100\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Sad cats are sad: http://youtu.be/PKffm2uI4dk\n \n </span>\n <span class=\"timestamp\">\n Posted about 2 hours ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/12348100\">delete</a>\n </span>\n</li>\n<li id=\"micropost-970054474\">\n <a href=\"/users/409608538\"><img alt=\"Lana Kane\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/de9a58df9617af487e8b28dbb3aa50de?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/409608538\">Lana Kane</a></span>\n <span class=\"content\">\n Dude, this van's, like, rolling probable cause.\n \n </span>\n <span class=\"timestamp\">\n Posted about 4 hours ago.\n </span>\n</li>\n<li id=\"micropost-19959062\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Sunt ad blanditiis totam aut qui repudiandae rerum id sint.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/19959062\">delete</a>\n </span>\n</li>\n<li id=\"micropost-58620899\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Quidem doloremque qui consectetur architecto.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/58620899\">delete</a>\n </span>\n</li>\n<li id=\"micropost-68403196\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Explicabo aliquid voluptatem occaecati consectetur doloremque quia error pariatur id.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/68403196\">delete</a>\n </span>\n</li>\n<li id=\"micropost-71534927\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Velit error et aut non laboriosam dolorem aut.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/71534927\">delete</a>\n </span>\n</li>\n<li id=\"micropost-106776847\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Facilis quas et ut minima.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/106776847\">delete</a>\n </span>\n</li>\n<li id=\"micropost-177734013\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Voluptas consequatur enim aperiam qui.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/177734013\">delete</a>\n </span>\n</li>\n<li id=\"micropost-228979669\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Enim porro quam magni aliquid at dolores.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/228979669\">delete</a>\n </span>\n</li>\n<li id=\"micropost-234210660\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Consequatur tenetur mollitia dolores et.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/234210660\">delete</a>\n </span>\n</li>\n<li id=\"micropost-294621321\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Esse accusantium maxime dolore aut suscipit doloremque soluta ut at.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/294621321\">delete</a>\n </span>\n</li>\n<li id=\"micropost-328290505\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Omnis eius minima est distinctio voluptatem dolor impedit eum.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/328290505\">delete</a>\n </span>\n</li>\n<li id=\"micropost-352097504\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Quia et consectetur similique natus.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/352097504\">delete</a>\n </span>\n</li>\n<li id=\"micropost-406445230\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Et aut veniam aut similique consequatur qui est.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/406445230\">delete</a>\n </span>\n</li>\n<li id=\"micropost-444017243\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Quod sed est atque non dicta qui.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/444017243\">delete</a>\n </span>\n</li>\n<li id=\"micropost-488304196\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Aut consequuntur et et molestiae.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/488304196\">delete</a>\n </span>\n</li>\n<li id=\"micropost-525605047\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Ullam et voluptatem velit enim rerum.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/525605047\">delete</a>\n </span>\n</li>\n<li id=\"micropost-603694155\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Temporibus at omnis rerum voluptatem consectetur est culpa.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/603694155\">delete</a>\n </span>\n</li>\n<li id=\"micropost-613834836\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Reiciendis nam quod voluptates et at sit perspiciatis nihil.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/613834836\">delete</a>\n </span>\n</li>\n<li id=\"micropost-646488084\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Nobis et sunt mollitia natus saepe tempore.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/646488084\">delete</a>\n </span>\n</li>\n<li id=\"micropost-676538406\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Et molestiae quia blanditiis reiciendis illum sit aut enim.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/676538406\">delete</a>\n </span>\n</li>\n<li id=\"micropost-706600661\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Aut aliquam est provident nihil cum quaerat.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/706600661\">delete</a>\n </span>\n</li>\n<li id=\"micropost-762321612\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Sint dolor voluptatum blanditiis eveniet aut temporibus hic.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/762321612\">delete</a>\n </span>\n</li>\n<li id=\"micropost-792652861\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Error quaerat animi nulla nostrum iste nam.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/792652861\">delete</a>\n </span>\n</li>\n<li id=\"micropost-828012954\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Velit iste omnis laudantium voluptatibus.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/828012954\">delete</a>\n </span>\n</li>\n<li id=\"micropost-856985457\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Fugit dolorem quasi et temporibus omnis alias architecto.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/856985457\">delete</a>\n </span>\n</li>\n<li id=\"micropost-860142042\">\n <a href=\"/users/762146111\"><img alt=\"Michael Example\" class=\"gravator\" src=\"https://secure.gravatar.com/avatar/03ea78c0884c9ac0f73e6af7b9649e90?s=50\" /></a>\n <span class=\"user\"><a href=\"/users/762146111\">Michael Example</a></span>\n <span class=\"content\">\n Architecto voluptatem voluptatem dolor sed.\n \n </span>\n <span class=\"timestamp\">\n Posted about 1 month ago.\n <a data-confirm=\"You sure?\" rel=\"nofollow\" data-method=\"delete\" href=\"/microposts/860142042\">delete</a>\n </span>\n</li>\n\n </ol>\n <div class=\"pagination\"><ul class=\"pagination\"><li class=\"prev previous_page disabled\"><a href=\"#\">← Previous</a></li> <li class=\"active\"><a rel=\"start\" href=\"/?page=1\">1</a></li> <li><a rel=\"next\" href=\"/?page=2\">2</a></li> <li class=\"next next_page \"><a rel=\"next\" href=\"/?page=2\">Next →</a></li></ul></div>\n\n </div>\n </div>\n\n\n <footer>\n <small>\n The <a href=\"http://railstutorial.jp\">Ruby on Rails Tutorial</a>\n by <a href=\"http://www.michaelhartl.com\">Michael Hartl</a>\n </small>\n <nav>\n <ul>\n <li><a href=\"/about\">About</a></li>\n <li><a href=\"/contact\">Contact</a></li>\n <li><a href=\"http://news.railstutorial.org/\">News</a></li>\n </ul>\n </nav>\n</footer>\n \n </div>\n </body>\n</html>\n".
test/integration/following_test.rb:65:in `block (2 levels) in <class:FollowingTest>'
test/integration/following_test.rb:63:in `block in <class:FollowingTest>'
DEPRECATION WARNING: ActionDispatch::IntegrationTest HTTP request methods will accept only============ ] 80% Time: 00:00:03, ETA: 00:00:01
the following keyword arguments in future Rails versions:
params, headers, env, xhr, as
Examples:
get '/profile',
params: { id: 1 },
headers: { 'X-Extra-Header' => '123' },
env: { 'action_dispatch.custom' => 'custom' },
xhr: true,
as: :json
(called from block (2 levels) in <class:MicropostsInterfaceTest> at /home/ubuntu/workspace/sample_app/test/integration/microposts_interface_test.rb:27)
76/76: [===================================================================================================================] 100% Time: 00:00:04, Time: 00:00:04
Finished in 4.62458s
76 tests, 339 assertions, 1 failures, 0 errors, 0 skips
おわりに
ついにRailsチュートリアルのアプリケーションが完成しました!
4月からこつこつ続けてきて約4ヶ月で完走です。
RubyもRailsも知識ゼロでしたが、自分の手で作り上げていくのはとても楽しかったです。
新しいことを学ぶのに、年齢は関係ないですね。
素晴らしい教材を提供してくださったMichaelさん、安川さんに感謝です!