ぞえの技術めも

Ruby on Rails勉強中

【122日目】【1日20分のRailsチュートリアル】【第10章】アカウントの有効化トークンやダイジェストを作成する処理を追加する

Ruby on Railsチュートリアル(第3版)

今日は「10.1.1 AccountActivationsリソース」のアカウントの有効化について考えるところから。

10.1.1 AccountActivationsリソース

ユーザーが新しい登録を完了するためには必ずアカウントの有効化が必要になるのですから、有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく必要があります。

before_createコールバックを使うとユーザーオブジェクト作成前に有効化トークンや有効化ダイジェストを作成できるそう。

Userモデルに有効化トークンとダイジェストを作成する処理を追加する。
ついでにメールアドレスを小文字に変換する処理もちょっと書き換える。

app/models/user.rb

class User < ActiveRecord::Base
  attr_accessor :remember_token, :activation_token
  before_save   :downcase_email
  before_create :create_activation_digest
  :
  private

    # メールアドレスをすべて小文字にする
    def downcase_email
      self.email = email.downcase
    end

    # 有効化トークンとダイジェストを作成および代入する
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

先に進む前に、サンプルデータとフィクスチャも更新し、テスト時のサンプルとユーザーを事前に有効化しておきましょう (リスト10.4リスト10.5)。

サンプルデータ更新。

db/seeds.rb

User.create!(name:  "Example User",
              :
             activated: true,
             activated_at: Time.zone.now)

99.times do |n|
              :
              activated: true,
              activated_at: Time.zone.now)
end

フィクスチャの各ユーザーに下記コードを追加。

test/fixtures/users.yml

  :
  activated: true
  activated_at: <%= Time.zone.now %>
  :

いつものようにデータベースを初期化して、サンプルデータを再度生成し直し、リスト10.4の変更を反映します。

下記を実行してサンプルデータを再度生成し直しました。

$ bundle exec rake db:migrate:reset
$ bundle exec rake db:seed

これでようやく準備的なものは終わりなのかな…??

今日の学習時間は【19分】

次は「10.1.2 AccountActivationsメイラーメソッド」から。

【121日目】【1日20分のRailsチュートリアル】【第10章】アカウント有効化のためにUserモデルに属性を追加する

Ruby on Railsチュートリアル(第3版)

今日は「10.1.1 AccountActivationsリソース」のresources行を追加するところから。

10.1.1 AccountActivationsリソース

有効化メールでは以下の形式のURLを使用します。

edit_account_activation_url(activation_token, ...)

これは、editアクションへの名前付きルートが必要になるということです。そのためのresources行を追加します (リスト10.1)。

ほぅ…そうなのか。。。

よく分かってないけど下記を追加。

config/routes.rb

  resources :account_activations, only: [:edit]

続いて、一意の有効化トークンがユーザー有効化に必要です。

パスワードのときと同じように有効化トークンを使えばいいってことかな。。。

以下のマイグレーションコマンドラインで実行して図10.1のデータモデルを追加すると、3つの属性が新しく追加されます。

  • activation_digest
  • activated
  • activated_at

の3つの属性を追加。

$ rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
      invoke  active_record
      create    db/migrate/20170313024742_add_activation_to_users.rb

admin 属性 (リスト9.50) の時と同様に、activated属性のデフォルトの論理値をfalseにします (リスト10.2)。

さっき生成したファイルを下記のように修正してマイグレーション実行。

db/migrate/[timestamp]_add_activation_to_users.rb

    add_column :users, :activated, :boolean, default: false
$ bundle exec rake db:migrate
== 20170313024742 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
   -> 0.0006s
-- add_column(:users, :activated, :boolean, {:default=>false})
   -> 0.0035s
-- add_column(:users, :activated_at, :datetime)
   -> 0.0004s
== 20170313024742 AddActivationToUsers: migrated (0.0049s) ====================

まだ中途半端だけど今日はここまで。

今日の学習時間は【20分】

次は「10.1.1 AccountActivationsリソース」のアカウントの有効化について考えるとこから。

【120日目】【1日20分のRailsチュートリアル】【第10章】AccountActivationsリソースを生成する

Ruby on Railsチュートリアル(第3版)

今日は「第10章 アカウント有効化とパスワード再設定」から。

今日から10章!

第10章 アカウント有効化とパスワード再設定

アカウントの有効化 (アクティベーション: 新規ユーザーのメールアドレスが有効であることを確認する機能) と、パスワードの再設定 (パスワードを忘れてしまったユーザー向けの機能) を実装することにします。

「アカウントの有効化」ってなんだろう、と思ってた。なるほどね。

10.1 アカウントの有効化

おおよその流れは、有効化トークンやダイジェストをユーザーと関連付け、ユーザーにメールを送信し、そのメールにはトークンを含むリンクを記載しておき、ユーザーがそのリンクをクリックすると有効化できるようになる、というものです。

ほぅほぅ。新規登録するときによくある流れだけどその裏側が理解できるってことか。

それではいつものように、Gitで新機能用のトピックブランチを作成しましょう。
10.3で説明したとおり、アカウントの有効化とパスワードの再設定では、メールの設定部分に共通するところがありますので、その部分を両機能に適用してからGitのmasterにマージします。

10章用にブランチ作成する。

$ git checkout master
$ git checkout -b account-activation-password-reset

10.1.1 AccountActivationsリソース

この作業に必要なデータ (有効化トークンや有効化ステータスなど) をUserモデルに追加することにします。

ひとまずAccountActivationsコントローラを下記コマンドで生成。

$ rails generate controller AccountActivations --no-test-framework
  :
      create  app/controllers/account_activations_controller.rb
      invoke  erb
      create    app/views/account_activations
      invoke  helper
      create    app/helpers/account_activations_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/account_activations.coffee
      invoke    scss
      create      app/assets/stylesheets/account_activations.scss

上のコマンドでは、テストを生成しないというオプションを指定していることにご注目ください。
著者はコントローラのテストよりも統合テスト (10.1.4) の方が望ましいと考えているので、コントローラのテストを生成しないようにしているのです。

ほぅ…。何が違うのか分かってないけど今はいいか。。。

中途半端だけど今日はここまで。

今日の学習時間は【21分】

次は「10.1.1 AccountActivationsリソース」のresources行を追加するところから。

【119日目】【1日20分のRailsチュートリアル】【第9章】演習の4.

Ruby on Railsチュートリアル(第3版)

今日は「9.6 演習」の4.から。

9.6 演習

4.リスト9.60のパーシャルを使用して、new.html.erbビューとedit.html.erbビューをリファクタリングし、コードの重複を取り除いてください。
このとき、リスト9.61のようにフォーム変数fを明示的にローカル変数として渡す必要があることに注意してください。
また、provide関数を使うと、パーシャル化したnewフォームやeditフォームの重複をさらに取り除くことも可能です。

まずリスト9.60のパーシャルを作成する。
ファイルを作成して

$ touch app/views/users/_form.html.erb

下記のように更新。

app/views/users/_form.html.erb

<%= form_for(@user) 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 %>

new.html.erbビューをリスト9.61のようにリファクタリング

<% provide(:button_text, 'Create my account') %>を2行目に追加して、<%= render 'form' %>でパーシャル使う。

app/views/users/new.html.erb

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

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

edit.html.erbビューも同じようにリファクタリング

app/views/users/edit.html.erb

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

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

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

サーバー起動してユーザー登録画面とユーザー情報編集画面表示させてみたけど特に問題なさそう。
リファクタリングできたかな。

$ rails server -b $IP -p $PORT

f:id:kt_zoe:20170308123906p:plain

f:id:kt_zoe:20170308123917p:plain

画面によってボタンの文字列もちゃんと変わってる。

今日の学習時間は【25分】

次は「第10章 アカウントの有効化とパスワードのリセット」から。

やっと10章。まだまだ先は長いね…。

【118日目】【1日20分のRailsチュートリアル】【第9章】演習の3.

Ruby on Railsチュートリアル(第3版)

今日は「9.6 演習」の3.から。

9.6 演習

3.Web経由でadmin属性を変更できないことを確認してください。
リスト9.59に示したように、PATCHリクエストを updateメソッドに直接発行するテストを作成してください。
テストが正しい振る舞いをしているかどうか確信を得るために、まずはadminをuser_paramsメソッド内の許可されたパラメータ一覧に追加するところから始めてみましょう。最初のテストはREDになるはずです。

リスト9.59ベースにPATCHリクエストをupdateメソッドに直接発行するテストを追加する。

assert_not @other_user.reload.admin?はこれでいいのか自信ない。

test/controllers/users_controller_test.rb

  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 :update, id: @other_user, user: { password:              "password",
                                            password_confirmation: "password",
                                            admin: true }
    assert_not @other_user.reload.admin?
  end

ひとまずテストが通ることは確認。

$ bundle exec rake test
42 tests, 168 assertions, 0 failures, 0 errors, 0 skips

テストが正しい振る舞いをしているかどうかを確認するために、adminをuser_paramsメソッド内の許可されたパラメータ一覧に追加してみる。

app/controllers/users_controller.rb

    :
    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation, :admin)
    end
    :

これでテスト実行すると失敗するはず。

$ bundle exec rake test
  :
 FAIL["test_should_not_allow_the_admin_attribute_to_be_edited_via_the_web", UsersControllerTest, 2017-02-17 04:27:57 +0000]
 test_should_not_allow_the_admin_attribute_to_be_edited_via_the_web#UsersControllerTest (1487305677.59s)
        Expected true to be nil or false
        test/controllers/users_controller_test.rb:52:in `block in <class:UsersControllerTest>'
  :
42 tests, 168 assertions, 1 failures, 0 errors, 0 skips

今回追加したテストのassert_not @other_user.reload.admin?でエラーになることを確認。@other_userのadmin属性が変更できてしまっている、ってことですね。
テストは上手く動いてるよう。

許可されたパラメータ一覧に追加したadmin属性を削除してテスト終わり。

今日の学習時間は【24分】

次は「9.6 演習」の4.から。

【117日目】【1日20分のRailsチュートリアル】【第9章】演習の2.

Ruby on Railsチュートリアル(第3版)

今日は「9.6 演習」の2.から。

9.6 演習

2.レイアウトにあるすべてのリンクに対して統合テストを書いてみましょう。
ログイン済みユーザーとそうでないユーザーのそれぞれに対して、正しい振る舞いを考えてください。
ヒント: log_in_asヘルパーを使ってリスト5.25にテストを追加してみましょう。

リスト5.25でテストしているのはHome、Help、About、Contactへのリンクについて。
Log inのリンクとかはログインの有無によってリンクが変わるのでその辺のテストを書けばいいのかな。

test/integration/site_layout_test.rb

class SiteLayoutTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "layout links" do
    :
    assert_select "a[href=?]", login_path
    :
  end

  test "layout links at 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
  :

元々あったテストにログインのリンクを確認するコードを追加して、
ログイン時のリンクを確認するテストも追加した。

$ bundle exec rake test
41 tests, 166 assertions, 0 failures, 0 errors, 0 skips

とりあえずテストは通ったし大丈夫かな。。。なにか足りてないかもしれないけど。

今日の学習時間は【23分】

次は「9.6 演習」の3.から。

【116日目】【1日20分のRailsチュートリアル】【第9章】演習の1.

Ruby on Railsチュートリアル(第3版)

今日は「9.6 演習」から。

9.6 演習

なお、演習とチュートリアル本編の食い違いを避ける方法については、演習用のトピックブランチに追加したメモ (3.6) を参考にしてください。

まず演習用にブランチ分けます。

$ git checkout updating-users
$ git checkout -b updating-users-exercises

1.フレンドリーフォワーディングで、最初に与えられたURLにのみ確実に転送されていることを確認するテストを作成してください。
続けてログインを行った後、転送先のURLはデフォルト (プロフィール画面) に戻る必要もありますので、これもテストで確認してください。
ヒント: リスト9.26にsession[:forwarding_url]の正しい値を確認するテストを追加してください。

リスト9.26のテストに4行追加してみた。(# addってつけてるとこ)

test/integration/users_edit_test.rb

  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    assert session[:forwarding_url].include?(edit_user_path(@user)) # add
    log_in_as(@user)
    assert_redirected_to edit_user_path(@user)
    assert_nil session[:forwarding_url] # add
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), 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
    
    log_in_as(@user) # add
    assert_redirected_to user_path(@user) # add
  end

assert session[:forwarding_url].include?(edit_user_path(@user))は間違ってる気しかしない。。。
assert_equal session[:forwarding_url], edit_user_path(@user)だとセッションに保存してるURLがフルパス、edit_user_path(@user)が相対パス、でテストNGになった。
なので含まれているかどうかをテストするようにしたけど…もっといい書き方あるんじゃないかな。。

最後の2行もこれでいいのかよく分からない。でもテストは通った。

難しいなぁ…。

今日の学習時間は【35分】

次は「9.6 演習」の2.から。