RubyMine で Rails Tutorial 14章

ようやく最後の14章

ブランチも多くなりましたね。

モデルの関係を作りますが、なかなかややこしいと思います。RubyMine で図を作ってみて、関係の線を見ると把握しやすいこともあります。

14.2.2 では演習がありました。users_profile_test.rb で、統計情報の表示について確認します。

  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 'ul.pagination'
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end

    assert_select 'strong#following'
    assert_match @user.following.count.to_s, response.body
    assert_select 'strong#followers'
    assert_match @user.followers.count.to_s, response.body
  end

Ajax での実装をすると、ログではこのように “as JS” と出てきます。

Started DELETE "/relationships/90" for 127.0.0.1 at 2018-05-30 14:14:23 +0900
Processing by RelationshipsController#destroy as JS
  Parameters: {"utf8"=>"✓", "commit"=>"Unfollow", "id"=>"90"}
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/helpers/sessions_helper.rb:17
  Relationship Load (0.1ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."id" = ? LIMIT ?  [["id", 90], ["LIMIT", 1]]
  ↳ app/controllers/relationships_controller.rb:14
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  ↳ app/controllers/relationships_controller.rb:14
  Relationship Load (0.1ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 1], ["followed_id", 4], ["LIMIT", 1]]
  ↳ app/models/user.rb:95
   (0.1ms)  begin transaction
  ↳ app/models/user.rb:95
  Relationship Destroy (0.3ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 90]]
  ↳ app/models/user.rb:95
   (1.5ms)  commit transaction
  ↳ app/models/user.rb:95
  Rendering relationships/destroy.js.erb
  Rendered users/_follow.html.erb (0.8ms)
   (0.1ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 4]]
  ↳ app/views/relationships/destroy.js.erb:2
  Rendered relationships/destroy.js.erb (3.7ms)
Completed 200 OK in 16ms (Views: 8.4ms | ActiveRecord: 2.4ms)

リスト 14.49 は最後の演習です。何をテストするのがいいのか難しかったのですが、|micropost| でぐるぐる回すということから、投稿の内容が含まれているということを見ればいいのかなと思いましてこのようにしてみました。

  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

ヒントにある CGI.escapeHTML を外してみると、I’m sorry のような文字列が I'm sorry. となるのでマッチしないということになりそうです。

長い長いチュートリアルが終わりました。わたしもいい復習になったと思います。
RubyMine の使い方にも慣れることができて、記録を残してよかったです。
どなたかのお役に立っているとうれしいです。

RubyMine で Rails Tutorial 13章

13章やります。あと少しです。

ActiveRecord の関連付けをすると、図も生成できるようになります。
右クリックして Diagrams → Show Diagram → Rails Model Dependency Diagram で作ることができます。

また、この章でもテストと実装とを行き来することが多いですが、ショートカットキーを使うと便利です。

実装からテストへの移動も、テストから実装(テスト対象)への移動もともに shift + command + T でした。
こういう統合開発環境はメニューからさわって一つずつ覚えていくのが使いこなすためのコツですね。

リスト 13.28 では、div.pagination の要素を確認するテストをしていますが、いまは Bootstrap 4 に対応した will_paginate-bootstrap4 を使っているため、ul.pagination が出力されます。テストもそのように直して実行しました。

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 'ul.pagination'
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
end

13.3.2 の演習では if 〜 else 〜 end のそれぞれにパーシャルを作るというものでした。
RubyMine から簡単に切り出せるはずなのですが、後半はうまくできませんでした。

手で切り出します。

直したら test を流してエラーがないことを確認します。

リスト 13.57 の課題は、投稿数のテストです。fixtures で michael は34個(4個+30個)の投稿をするように設定しているので、その数を確認しています。

require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'ul.pagination'

    # 無効な送信
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "" } }
    end
    assert_select 'div#error_explanation'

    # 有効な送信
    content = "This micropost really ties the room together"
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost: { content: content } }
    end
    assert_redirected_to root_url
    follow_redirect!
    assert_match content, response.body

    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end

    # 違うユーザーのプロフィールにアクセス (削除リンクがないことを確認)
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end

  test "micropost sidebar count" do
    log_in_as(@user)
    get root_path
    assert_match "34 microposts", response.body

    # まだマイクロポストを投稿していないユーザー
    other_user = users(:malory)
    log_in_as(other_user)
    get root_path
    assert_match "0 microposts", response.body
    other_user.microposts.create!(content: "A micropost")

    get root_path
    assert_match "1 micropost", response.body
  end
end

最後は画像のアップロード周りです。Rails 5.2.0 では carrierwave ではなく ActionStorage を使うのがいいようなのですが、ここではチュートリアルに従います。

# section 13
gem 'carrierwave'
gem 'mini_magick'
...
group :production do
  gem 'pg'
  gem 'fog'
end

リスト 13.63 のテストはそのままではうまく通りませんでした。post microposts_path には param の引数が必要なので、そのように括っておきます。

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'ul.pagination'
    assert_select 'input[type=file]'

    # 無効な送信
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "" } }
    end
    assert_select 'div#error_explanation'

    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost: { content: content, picture: picture } }
    end
    assert assigns(:micropost).picture?
    assert_redirected_to root_url
    follow_redirect!
    assert_match content, response.body

    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end

development ではローカルにアップロードしますが、production では fog を使って AWS S3 にアップロードするコードになっています。せっかくなので使ってみます。

13.4.4 にしたがって AWS で設定することで、無事 production でも画像のアップロードが動作しました。

AWSのコンソールからも確認できます。

RubyMine で Rails Tutorial DB接続について

Heroku (production) 環境でのDB接続について、PostgreSQL を使えていなかったようです。

addon として提供されていまして、無料の hobby-dev プランで作成しました。

$ heroku addons:create heroku-postgresql:hobby-dev

heroku config すると、DATABASE_URL の変数が生成されていることがわかります。
これを config/database.yml に渡します。

production:
  url: <%= ENV['DATABASE_URL'] %>

あとは heroku に push すれば、使えるようになります。

$ git push heroku
$ heroku run db:migrate

チュートリアルのサイトには記載がないようです。Heroku のマニュアルで調べましょう。

RubyMine で Rails Tutorial 12章

12章をやります。

リスト 12.4 では、これまでやってきたように Bootstrap 4 に対応する形で class を書き直します。

<% provide(:title, "Forgot password") %>
<h1>Forgot password</h1>

<div class="row">
  <div class="col-md-6 offset-md-3">
    <%= form_for(:password_reset, url: password_resets_path) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

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

リスト 12.18 はちょっと難しいですね。
get 〜 や post 〜 などでアクセスして assert_xxx で確認、というのを繰り返しているので、区切りながら読むといいと思います。

リスト 12.20 の演習はこんな感じにしました。

  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest: User.digest(reset_token),
                   reset_sent_at: Time.zone.now)
  end

RubyMine だと : を => にするかとアドバイスが出てきますが、好みでいいと思います。

変えた場合はこのような書き方になりますね。わたしは : の書き方のほうに戻しておきました。

  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(:reset_digest => User.digest(reset_token),
                   :reset_sent_at => Time.zone.now)
  end

リスト 12.21 のマッチさせる文言はヒントにあるそのままですね。
password_resets_controller.rb の check_expiration メソッドで flash[:danger] として出しています。

  test "expired token" do
    get new_password_reset_path
    post password_resets_path,
         params: { password_reset: { email: @user.email } }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token),
          params: { email: @user.email,
                    user: { password:              "foobar",
                            password_confirmation: "foobar" } }
    assert_response :redirect
    follow_redirect!
    assert_match /Password reset has expired./i, response.body
  end

演習の4つ目は、パスワードがリセットされたら reset_digest が nil になっていることを確認するというテストを追加します。

  test "password resets" do
...
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    assert is_logged_in?
    assert_not flash.empty?
    assert_nil user.reload.reset_digest
    assert_redirected_to user
  end

なかなか難しかったですね。
一からここまで考えてあれこれ作るのは大変だと思います。とても参考になります。

RubyMine で Rails Tutorial 11章

11章 やります。

メールでアクティベーションするというものですね。

Action Mailer は仕事でも使ったことがあります。監視ツールを作ったことがありまして、メール通知するのに使いました。
ERBで書けるので、これまで進めてきた Web の view と同じような感覚で作れます。

メールのプレビューは知らなかったです。これは便利ですね。

users_login_test.rb のテストが一つ通らなくなりました。

  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end
  1) Error:
UsersLoginTest#test_login_with_remembering:
NoMethodError: undefined method `remember_token' for nil:NilClass
    test/integration/users_login_test.rb:55:in `block in <class:UsersLoginTest>'

リスト 11.32 は 9.3 の課題 でやった assigns() への対応が反映されていないので、そのままだとこのようにエラーになります。

同様にメンバ変数として参照するように修正しておきます。

class SessionsController < ApplicationController
  def new
  end

  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      if @user.activated?
        log_in @user
        params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
        redirect_back_or @user
      else
        message  = "Account not activated. "
        message += "Check your email for the activation link."
        flash[:warning] = message
        redirect_to root_url
      end
    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

リスト 11.39 はこう書きます。

  # アカウントを有効にする
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
  end

11.40 はこんな感じにしました。
activated が true のものだけを扱うということですね。

  def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
  end

最後の SendGrid addon は、Heroku にクレジットカードを登録する必要がありました。

Heroku の Manage Account の画面から登録すれば使えるようになります。

config/environments/production.rb を設定します。
Heroku でのアプリケーションのURLを host に設定する必要がありますが、heroku open するとブラウザが開くのでわかりやすいでしょう。

このURLを production.rb に書いておきます。

  config.action_mailer.perform_caching = false

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  host = 'sleepy-wildwood-37153.herokuapp.com'
  config.action_mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
      :address        => 'smtp.sendgrid.net',
      :port           => '587',
      :authentication => :plain,
      :user_name      => ENV['SENDGRID_USERNAME'],
      :password       => ENV['SENDGRID_PASSWORD'],
      :domain         => 'heroku.com',
      :enable_starttls_auto => true
  }

実際に Heroku に deploy して、ユーザ登録を試してみます。

メールもちゃんと来ました。

リンクを踏んでユーザ登録完了です。ちゃんと動いてますね。