RubyMine で Rails Tutorial 10章

10章

まず、partial に切り出すところはこんな感じになるでしょうか。

new.html.erb にて、form_for 周りを選択します。

右クリックから Refactor → Extract → Partial で切り出します。

名前をつけます。チュートリアルに従って _form.html.erb とします。

これで切り出せました。

あとは少々調整します。new.html.erb と edit.html.erb の差分を見て、共通化できる部品を活かして、違うところは provide で渡します。
このスクリーンキャプチャでは form_for の一つ上も切り出してしまったのですが、gravatar_for があることに気が付いて調整しています。

new.html.erb

<% provide(:title, 'Sign up') %>
<% provide(:url, signup_path) %>
<% provide(:button_text, 'Create my account') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 offset-md-3">
    <%= render 'form' %>
  </div>
</div>

edit.html.erb

<% provide(:title, 'Edit user') %>
<% provide(:url, user_path) %>
<% provide(:button_text, 'Save changes') %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 offset-md-3">
    <%= render 'form' %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="http://gravatar.com/emails" target="_blank">Change</a>
    </div>
  </div>
</div>

_form.html.erb

<%= form_for(@user, url: yield(:url)) do |f| %>
  <%= render 'shared/error_messages', object: @user %>

  <%= 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 yield(:button_text), class: "btn btn-primary" %>
<% end %>

10.1.3 の演習では、テストを追加します。
わたしはブラウザに Chrome を使っているのですが、実際にエラー画面を表示させて右クリックから「検証」をするとこのようになります。

div.alert を指定すればいいということがわかるので、assert_test に追加します。

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
    assert_select "div.alert", "The form contains 4 errors."
  end
end

10.2.3 の演習でもテストを追加するのがありました。

/users/1/edit にアクセスする → ログインが求められる → ログインしたら /users/1/edit に戻って編集できるようになる、という確認で、ログイン前後での session[:forwarding_url] の値をチェックしておきましょうというテストです。
こんな感じにしてみました。

  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    assert_equal edit_user_url(@user), session[:forwarding_url]
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    assert_nil session[:forwarding_url]
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end

リスト 10.39 の CSS では $gray-lighter を使っていますが、Bootstrap 4 にはありません。上の方で定義しておきます。

@import "bootstrap";

/* mixins, variables, etc. */

$gray: #555;
$gray-light: #777;
$gray-medium-light: #eaeaea;
$gray-darker: #222;
$state-danger-text: #f00;
$gray-lighter: #eee;

@mixin box_sizing {
  -moz-box-sizing:    border-box;
  -webkit-box-sizing: border-box;
  box-sizing:         border-box;
}
...

10.3.1 のテストは、右上のメニューのところがログインしていないときとログインしたときで変わるというのを追加してみました。

ログインしていないとき

ログインしたとき

site_layout_test.rb

require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "layout links without login" do
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", users_path, count: 0
    assert_select "a[href=?]", logout_path, count: 0

    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path

    get contact_path
    assert_select "title", full_title("Contact")
  end

  test "layout links with login" do
    log_in_as(@user)
    get root_path
    assert_template 'static_pages/home'
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", users_path
    assert_select "a[href=?]", user_path(@user)
    assert_select "a[href=?]", edit_user_path(@user)
    assert_select "a[href=?]", logout_path

    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
  end
end

10.3.3 では will_paginate を使います。bootstrap-will_paginate では Bootstrap 4 に対応していないようなので、will_paginate-bootstrap4 を使ってみました。

10章で Gemfile に追加した部分

# section 10
gem 'faker'
gem 'will_paginate'
#gem 'bootstrap-will_paginate'
gem 'will_paginate-bootstrap4'

リスト 10.48 では div.pagination の要素があることを確認していますが、will_paginate-bootstrap4 では div ではなく ul になっていました。

require 'test_helper'

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'ul.pagination'
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end

リスト 10.56 のテストはこう書きました。admin 属性を変更しようとしてもできません、というものです。

  test "should not allow the admin attribute to be edited via the web" do
    log_in_as(@other_user)
    assert_not @other_user.admin?
    patch user_path(@other_user), params: {
        user: { password:              "password",
                password_confirmation: "password",
                admin: true } }
    assert_not @other_user.admin?
  end

10章は長い道のりでした。テストを書きながら実際に動かして進めていくと楽しいですね。

RubyMine で Rails Tutorial 9章

9章

特に RubyMine の使い方としてどうこうということもなくなってきました。
淡々と進めていきます。内容は結構難しいと思います。

リスト 9.27

class SessionsController < ApplicationController
  def new
  end

  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end

リスト 9.28

  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end

全般的に難しいセクションだったと思います。

if (user_id = session[:user_id]) … というコードはわたしはあまり好きではないです。バグを産みやすいです。

RubyMine で Rails Tutorial 8章

8章 はログインについて。

もうだんだん RubyMine ではというものはなくなってきました。
淡々とチュートリアルをこなしていきます。

リスト 8.19 の _header.html.erb では、Bootstrap 4 だとドロップダウンのリンクがうまく出ません。
このように直してみました。

<header class="navbar navbar-fixed-top navbar-dark bg-dark">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav justify-content-end">
        <li class="nav-item"><%= link_to "Home", root_path, :class => "nav-link" %></li>
        <li class="nav-item"><%= link_to "Help", help_path, :class => "nav-link" %></li>
        <% if logged_in? %>
          <li class="nav-item"><%= link_to "Users", '#', :class => "nav-link" %></li>
          <li class="nav-item dropdown">
            <a href="#" class="dropdown-toggle nav-link" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li class="dropdown-item"><%= link_to "Profile", current_user %></li>
              <li class="dropdown-item"><%= link_to "Settings", '#' %></li>
              <li class="dropdown-divider"></li>
              <li class="dropdown-item">
                <%= link_to "Log out", logout_path, method: :delete %>
              </li>
            </ul>
          </li>
        <% else %>
          <li class="nav-item"><%= link_to "Log in", login_path, :class => "nav-link" %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>

リスト 8.20 の app/assets/javascripts/application.js については、特に記述不要です。

こんな感じで表示されるようになりました。

RubyMine で Rails Tutorial 7章

7章

RubyMine で開きたいファイルを探すときは、shift を2回押すのが便利です。
ファイルに限らず、だいたいのものは探せます。

debug は RubyMine にもあるのですが、まだ使い方がよくわかっていません。
チュートリアルにあるように、byebug を使って debugger を埋め込むやり方のほうが手軽のようです。

new.html.erb を動かしたときに、フォームが真ん中に来ませんでした。

Bootstrap 4 のマニュアル を見ますと、offset-md-3 となっています。そのように修正します。

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 offset-md-3">
    <%= form_for(@user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

今度は真ん中に来ましたね。

Bootstrap のグリッドシステムでは、ブラウザの幅に応じてレイアウトを変えてくれるようになっています。
ここでは md だけ指定していますが、同時に複数の種類の class を指定することもできます。

col-md-6 offset-md-3 というのは、全体の 12 分割をどう使うかという指定です。
幅が6つ分の要素を作って、左に3つ開けるということで真ん中にくるようにしています。

エラー画面を作るところでは、.has-error がないと怒られました。

これも Bootstrap のマニュアルを見ると、:invalid に置き換わったようですね。そのように修正します。
. でなくて : で始まるクラス名になっているのは要注意です。

.field_with_errors {
  @extend :invalid;
  .form-control {
    color: $state-danger-text;
  }
}

リスト 7.25 のテストを直すところは、こんな感じにしておきました。
_error_messages.html.erb に対応して、エラーとなったときに出てくるということです。

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.alert'
    assert_select 'div.alert-danger'
  end
end

リスト 7.34 のテストは、ヒントに従って空文字列かどうかをチェックするようにしてみました。
成功していれば何か入ってますよということです。

  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert_not flash[:success].blank?
  end

最後に、production 環境で SSL 化して終わりです。
Webサーバは Rails 5.2.0 では Puma になっています。

RubyMine で Rails Tutorial 6章

6章やりますか。

Model を作ります。

db:migrate も右クリックからのメニューで実行できます。

チュートリアルでは DB Browser for SQLite が紹介されていますが、RubyMine でも右のほうにある Database メニューから DB を見ることができます。

+ボタンから Data Source → Sqlite を選択します。

ダイアログで右のほうにあるファイル選択ダイアログを開いて、DB のファイルを選択し、
RubymineProjects/sample_app/db/development.sqlite3 を選びます。

Test Connection を押して接続も確認しましょう。

先ほど作った User モデルに対応する users テーブルが見えるようになっています。
ダブルクリックすると select 文が実行されて中身が表示されますが、まだデータを入れていないので空になっています。

User モデルのテストを書きながらモデルも直していくという形で進めていきますが、RubyMine 上で簡単に行き来することができます。

テスト側からは、コマンドキーを押しながら User をクリックすると user.rb に移動できます。
user.rb からは、Goto → Test です。shift + command + T みたいですね。

ここまでは GUI でできるだけ操作するようにしてきましたが、Terminal でやっても同じです。
キーボードを打つほうが早いことも多いですし、そのときに慣れた方法を選んで使えばいいと思います。

最後はユーザを一つ作って終わりです。rails console も RubyMine の中から実行できます。

DB を確認すると正しく一つ入っていることが確認できます。