Railsチュートリアル4.0版には各章に演習が付いていますが、模範解答みたいなものは示されていないため、この書き方で良いのかと悩むこともありました。
そのため、復習した時のことを考えて、自分の解答をまとめておきます*1。
なお、4章はsample_appと関係無いため、6章はコードが全て示されているため、それらの章の解答は省略します。
第3章
演習3.5.2はコードが示されているため、省略します。
演習3.5.1
spec/requests/static_pages_spec.rb *2。
describe "Static pages" do . . . describe "Contact page" do it "should have the right title" do visit '/static_pages/contact' expect(page).to have_title("Ruby on Rails Tutorial Sample App | Contact") end it "should have the content 'Contact'" do visit '/static_pages/contact' expect(page).to have_content('Contact') end end end
第5章
演習5.6.3はコードが示されているため、省略します。
演習5.6.1
spec/requests/static_pages_spec.rb
describe "Static pages" do . . . describe "Help page" do before { visit help_path } let(:heading) { 'Help' } let(:page_title) { 'Help' } it_should_behave_like "all static pages" end describe "About page" do before { visit about_path } let(:heading) { 'About' } let(:page_title) { 'About Us' } it_should_behave_like "all static pages" end describe "Contact page" do before { visit contact_path } let(:heading) { 'Contact' } let(:page_title) { 'Contact' } it_should_behave_like "all static pages" end end
演習5.6.2
spec/requests/static_pages_spec.rb
describe "Static pages" do . . . it "should have the right links on the layout" do visit root_path click_link "About" expect(page).to have_title(full_title('About Us')) click_link "Help" expect(page).to have_title(full_title('Help')) click_link "Contact" expect(page).to have_title(full_title('Contact')) click_link "Home" click_link "Sign up now!" expect(page).to have_title(full_title('Sign up')) click_link "sample app" expect(page).to have_title(full_title('')) end . . . end
第7章
演習7.6.1, 7.6.3, 7.6.4 はコードが示されているため、省略します。
演習7.6.2
spec/requests/user_pages_spec.rb
describe "User pages" do . . . describe "signup" do . . . describe "after submission" do before { click_button submit } it { should have_title('Sign up') } it { should have_content('error') } it { should have_content("Password can't be blank") } it { should have_content("Password is too short (minimum is 6 characters) ") } it { should have_content("Name can't be blank ") } it { should have_content("Email can't be blank ") } it { should have_content("Email is invalid ") } end . . .
第8章
演習8.5.1
以下の2つを修正しました。
class SessionsController < ApplicationController . . . def create user = User.find_by(email: params[:email].downcase) if user && user.authenticate(params[:password]) sign_in user redirect_to user else flash.now[:error] = 'Invalid email/password combination' render 'new' end end . . . end
<% provide(:title, "Sign in") %> . . . <div class="span6 offset3"> <%= form_tag(sessions_path) do %> <%= label_tag :email %> <%= text_field_tag :email %> <%= label_tag :password %> <%= password_field_tag :password %> <%= submit_tag("Sign in", class: "btn btn-large btn-primary") %> <% end %> . . .
演習8.5.2
今見返したところ、課外活動はやっていなかったため、課外活動以外の内容を記載しておきます。
spec/support/utilities.rb のように、「RSpec::Matchers.define :have_welcome_message」というカスタムマッチャーを追加しました。
. . . RSpec::Matchers.define :have_welcome_message do |message| match do |page| expect(page).to have_selector('div.alert.alert-success', text: message) end end
カスタムマッチャーは、spec/requests/user_pages_spec.rb のように使いました。
- it { should have_selector('div.alert.alert-success', text: 'Welcome') } + it { should have_welcome_message('Welcome') }
第9章
課題9.6.6, 9.6.7, 9.6.8はコードが示されているため、省略します。
課題9.6.1
app/controllers/users_controller.rbのuser_params に 「:admin」を追加することで、最初にRedとなりました。
課題9.6.2
- <a href="http://gravatar.com/emails">change</a> + <%= link_to "change", "http://gravatar.com/emails", target: ["_blank"] %>
課題9.6.3
ユーザーがサインインしていない時というのを、以下のように考えて解答しました。
- 最初にサインインページへ遷移した時
- サインインに失敗した時
spec/requests/authentication_pages_spec.rb
describe "Authentication" do . . . describe "signin page" do . . . it { should_not have_link('Profile') } it { should_not have_link('Settings') } end describe "signin" do . . . describe "with invalid information" do . . . it { should_not have_link('Profile') } it { should_not have_link('Settings') } . . .
課題9.6.4
なるべく多くと書いてありますが、見つけられたのは spec/requests/authentication_pages_spec.rbのこの部分 のみでした。
- fill_in "Email", with: user.email - fill_in "Password", with: user.password - click_button "Sign in" + sign_in user
課題9.6.5
テストの更新は、spec/support/utilities.rb のvalid_signupメソッド に対して行いました。
def valid_signup . . . #fill_in "Confirmation", with: "foobar" fill_in "Confirm Password", with: "foobar" end
課題9.6.9
最初に、 spec/requests/authentication_pages_spec.rb へとテストコードを追加しました。
describe "Authentication" do . . . describe "authorizaiton" do . . . describe "as an admin user" do let(:admin) { FactoryGirl.create(:admin) } before do sign_in admin, no_capybara: true end describe "submitting a DELETE request to the Users#destroy action" do before { delete user_path(admin) } specify { expect(response).to redirect_to(root_path) } end end end end
次に、rails_projects/sample_app/の改良として、管理者が自分自身以外を削除できるよう、app/controllers/users_controller.rb のように、destroyメソッドへsessions_helper#current_user? を使った条件分岐を追加しました。
. . . def destroy user = User.find(params[:id]) if current_user? user redirect_to(root_path) else user.destroy flash[:success] = "User destroyed." redirect_to users_url end end . . .
第10章
課題10.5.7のチャレンジは行っていないため、省略します。
課題10.5.1
spec/requests/micropost_pages_spec.rb
describe "Micropost pages" do . . . describe "micropost creation" do . . . describe "with valid information" do before { fill_in 'micropost_content', with: "Lorem ipsum" } it "should create a micropost" do expect { click_button "Post" }.to change(Micropost, :count).by(1) end describe "and post 1 micropost" do before { click_button "Post" } it { should have_content('1 micropost') } it { should_not have_content('1 microposts') } end describe "and post 2 microposts" do before do click_button "Post" fill_in "micropost_content", with: "hoge" click_button "Post" end it { should have_content('2 microposts') } end end . . .
課題10.5.2
リスト9.33を参考に、spec/requests/micropost_pages_spec.rb のように修正しました。
describe "Micropost pages" do . . . describe "pagination" do before do 40.times { FactoryGirl.create(:micropost, user: user) } visit root_path end after { Micropost.delete_all } it { should have_selector('div.pagination') } it "should list each micropost" do user.microposts.paginate(page: 1).each do |micropost| expect(page).to have_selector('li', text: micropost.content) end end end . . .
課題10.5.3
app/views/static_pages/home.html.erbの<% if signed_in? %>〜<% end %>をパーシャルに分けました。
- app/views/static_pages/home.html.erb (パーシャルを呼び出す部分)
<% if signed_in? %> <%= render 'shared/home_signed_in' %> <% else %> <%= render 'shared/home_no_signed_in' %> <% end %>
- app/views/shared/_home_signed_in.html.erb (サインイン時のパーシャル)
<div class="row"> <aside class="span4"> <section> <%= render 'shared/user_info' %> </section> <section> <%= render 'shared/micropost_form' %> </section> </aside> <div class="span8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div>
- app/views/shared/_home_no_signed_in.html.erb (サインアウト時のパーシャル)
<div class="center hero-unit"> <h1>Welcome to the Sample App</h1> <h2> This is the home page for the <a href="http://railstutorial.jp">Ruby on Rails Tutorial</a> sample application </h2> <%= link_to "Sign up now!", signup_path, class: "btn btn-large btn-primary" %> </div> <%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %>
課題10.5.4
spec/requests/micropost_pages_spec.rb
describe "Micropost pages" do . . . describe "other user's micropost without delete_link" do let(:other_user) { FactoryGirl.create(:user) } before do FactoryGirl.create(:micropost, user: other_user) visit user_path(other_user) end it { should_not have_link('delete') } end end
課題10.5.5
app/views/shared/_micropost_delete_link.html.erb のように、パーシャルを作成しました。
<% if current_user?(object.user) %> <%= link_to "delete", object, method: "delete", data: { confirm: "You sure?" }, title: object.content %> <% end %>
パーシャルを呼び出す側としては、app/views/shared/_feed_item.html.erb のように、パーシャルへ渡すオブジェクトとして、「feed_item」を指定しました。
</span> - <% if current_user?(feed_item.user) %> - <%= link_to "delete", feed_item, method: "delete", - data: { confirm: "You sure?" }, - title: feed_item.content %> - - <% end %> - </li> + <%= render 'shared/micropost_delete_link', object: feed_item %> + </li>
もう一方は、app/views/microposts/_micropost.html.erb のように、パーシャルへ渡すオブジェクトとして、「micropost」を指定しました。
</span> - <% if current_user?(micropost.user) %> - <%= link_to "delete", micropost, method: "delete", - data: { confirm: "You sure?" }, - title: micropost.content %> - <% end %> - </li> + <%= render 'shared/micropost_delete_link', object: micropost %> + </li>
課題10.5.6
リスト10.47のwrapヘルパーを作成後(app/helpers/microposts_helper.rb)、app/views/microposts/_micropost.html.erb のように修正しました。
<li> - <span class="content"><%= micropost.content %></span> + <span class="content"><%= wrap(micropost.content) %></span> . . .
第11章
演習11.5.2はコードが示されているため、省略します。
演習11.5.1
describe User do . . . describe "relationship associations" do let(:other_user) { FactoryGirl.create(:user) } before do @user.save end it "should destroy followed user" do @user.follow!(other_user) relationships = @user.relationships.to_a other_user.destroy expect(relationships).not_to be_empty relationships.each do |relationship| expect(Relationship.where(id: relationship.id)).to be_empty end end it "should destroy follower user" do @user.follow!(other_user) relationships = @user.relationships.to_a @user.destroy expect(relationships).not_to be_empty relationships.each do |relationship| expect(Relationship.where(id: relationship.id)).to be_empty end end end end
演習11.5.3
「具体的には〜」の部分から、対象のviewは以下のとおりと考えました。
- app/views/users/show.html.erb
- app/views/users/show_follow.html.erb
- app/views/shared/_user_info.html.erb
それらで共通して使用している部分は以下と考え、その部分をパーシャル化することにしました(app/views/shared/_user_gravatar.html.erb)。
- アバターの画像
- ユーザー名
また、パーシャル内では userという変数を使っているため、呼ぶときには明示的に
<%= render 'shared/user_gravatar', user: current_user %>
として渡すようにしました。
- <h1> - <%= gravatar_for @user, size: 40 %> - <%= @user.name %> - </h1> + <%= render 'shared/user_gravatar', user: @user %>
- <%= gravatar_for @user %> - <h1><%= @user.name %></h1> + <%= render 'shared/user_gravatar', user: @user %>
-<a href="<% user_path(current_user) %>"> - <%= gravatar_for current_user, size: 52 %> -</a> -<h1> - <%= current_user.name %> -</h1> +<%= render 'shared/user_gravatar', user: current_user %>
<a href="<% user_path(user) %>"> <%= gravatar_for user, size: 52 %> </a> <h1> <%= user.name %> </h1>
演習11.5.4
Homeページのモデルに従ってとのことなので、プロファイルページも同じようなコードでテストを書くことにしました。
プロファイルページは、 users/[id] (UserController#show) と考え、spec/requests/user_pages_spec.rb のように修正しました。
describe "User pages" do . . . describe "profile page" do . . . describe "following/follower statistics" do let(:other_user) { FactoryGirl.create(:user) } before do other_user.follow!(user) visit user_path(user) end it { should have_link("0 following", href: following_user_path(user)) } it { should have_link("1 followers", href: followers_user_path(user)) } end end . . .