Rails Tutorial の拡張機能:フォロワーの通知機能を作ってみた

Rails Tutorialの第14章にある、拡張機能を作る件の続きです。

今回はフォロワーの通知機能を作ります。
##要件を定義
チュートリアルに要件が書いてあります。

>ユーザーに新しくフォロワーが増えたときにメールで通知する機能を実装してみましょう。
>続いて、メールでの通知機能をオプションとして選択可能にし、不要な場合は通知をオフにできるようにしてみましょう

Twitterに同じような機能があるか調べます。
・「通知」の画面がありました。「ｘｘｘさんにフォローされました」と表示されます。
・「メール通知」をオンやオフに設定できる画面があります。

要件はこの2点とします。
##仕様を設計
要件から具体的な機能に落とし込みます。

１．フォロワーが増えたときに、メールで通知する

フォローボタンを押したときに、メールを送信することをコントローラーに追加します。
コントローラーに作ることができるか、確認のために見てみます。

```ruby:app/controllers/relationships_controller.rb
  def create
      @user = User.find(params[:followed_id])
      current_user.follow(@user)
      respond_to do |format|
        format.html { redirect_to @user }
        format.js
      end
  end
```

followメソッドでフォローの細かい中身を実行しているようなので、その後の行にメール送信を追加すればよさそうです。

メール送信はチュートリアルの「第11章アカウントの有効化」を参考にします。

２．「メール通知」をオンやオフに設定できる
大きく2つあり、一つは設定を入力できること、もう一つはその設定でメール送信するかしないか動作を変更することです。

２．１　動作の変更機能
後者の動作の変更は、メール送信の際にif文で判定することでできそうです。

２．２　設定できる機能
前者の設定を入力できることを分解すると、一つは設定を入力する画面と、もう1つは入力した値を保持するためのモデルです。

２．２．１　設定画面
設定を入力する画面は、入力後に正しく入力できたか確認したいと思うので、表示する画面も必要です。
Twiiterの例と同様に、ユーザーごとの設定です。
設定は自分だけができるように制限をします。
既存のユーザー変更画面に項目を追加すればできそうです。

２．２．２　設定を保持するモデル
既存のユーザーモデルに項目を追加すればできそうです。

３．その他
Twitterでは通知はメールだけでなく通知の画面でも確認することができます。

作り手の視点では、なくても機能として問題なく作れます。

利用者の視点では、なくても自分のメールボックスで過去のメールを見れば分かるので、困ることはないです。

以上から今回は作らないことにします。

##モデルの追加

現在のユーザーモデルをコンソールで確認します。

```
>> User.first
  User Load (1.0ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-11-11 23:26:45", updated_at: "2020-11-11 23:26:45", password_digest: "$2a$10$7XL.tADLyZ5OdhbHWb6Eh.MVIbc1QpHR.cuu9yqe5or...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ZXWhU/7mCD5JjJXuGHDnPeHIEXVzlmNVAvSj2h3fIXx...", activated: true, activated_at: "2020-11-11 23:26:45", reset_digest: nil, reset_sent_at: nil, unique_name: "Example">
```

最後の列がunique_nameなので、返信機能で変更したのが最新版だと分かります。https://qiita.com/sadao2005/items/cc94d1f8673c14d198f1
を読み、以下のように列を追加します。

|列名|属性|
|:-:|:-:|
|id|integer|
|name   | string  |
|email   |string   |
|..   | ..  |
|unique_name|string |
|follow_notify|boolean|
図．フォロー通知の列を追加したUserモデル

トピックブランチを作ります。

```
ubuntu:~/environment/sample_app (master) $ git checkout -b follow_notify
```

モデルに列を追加するmigrateを生成します。

```
buntu:~/environment/sample_app (reply-micropost) $ rails generate migration add_unique_name_to_users unique_name:string
```

列を追加するmigrateを書きます。

```ruby:db/migrate/20201215115547_add_follow_notify_to_users.rb
class AddFollowNotifyToUsers < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :follow_notify, :boolean, default: false, null: false
  end
end
```

```
ubuntu:~/environment/sample_app (follow_notify) $ rails db:migrate
```

consoleで確認します。

```
> user = User.first
>> user.follow_notify
=> false
```

モデルが出来ました。
##メイラーを作成
次に、メールを送信する方法について、チュートリアルの「11.2 アカウント有効化のメール送信」を読みます。
Notifyメイラーを生成します。

```
ubuntu:~/environment/sample_app (follow_notify) $ rails generate mailer NotifyMailer follow_notify
Running via Spring preloader in process 2828
      create  app/mailers/notify_mailer.rb
      create    app/views/notify_mailer
      create    app/views/notify_mailer/follow_notify.text.erb
      create    app/views/notify_mailer/follow_notify.html.erb
      create    test/mailers/notify_mailer_test.rb
      create    test/mailers/previews/notify_mailer_preview.rb
```

viewとテストも合わせてファイルが生成されました。

メールのテンプレートを変更します。
送信先と、件名を設定します。コントローラーで使っている変数の@userを使います。

```ruby:app/mailers/notify_mailer.rb
  def follow_notify
    mail to: current_user.email, subject: "follow notification"
  end
```

メール本文を作成します。

```html:app/views/notify_mailer/follow_notify.html.erb
<h1>You are followed</h1>

<p>Hi <%= current_user.name %>,</p>

<p>You are followed by <%= @user.name %>.</p>
```

テストのため、メイラーのプレビューファイルを更新します。
設定の必要があるのか分かりませんでしたので変更しません。

プレビューにブラウザーからアクセスします。

http://localhost:3000/rails/mailers/notify_mailer/follow_notify

エラーになりました。

```
NameError in Rails::MailersController#preview
undefined local variable or method `current_user' for #<NotifyMailer:0x00007f64b45086e0>
```

やはりプレビューファイルで値を設定する必要があると分かりました。


current_userとフォローされたユーザーをメソッドの変数で渡すように変更します。

```ruby:test/mailers/previews/notify_mailer_preview.rb
  def follow_notify
    user = User.first
    followed_user = User.second
    NotifyMailer.follow_notify(user, followed_user)
  end
```

```ruby:app/mailers/notify_mailer.rb
  def follow_notify(user, followed_user)
    @user = user
    @followed_user = followed_user
    mail to: user.email, subject: "follow notification"
  end
```

```html:app/views/notify_mailer/follow_notify.html.erb
You are followed

Hi <%= @user.name %>,

You are followed by <%= @followed_user.name %>.
```

プレビューで表示できました。


![notify1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/b10767e2-2ae5-4934-4adb-9c067eb3dd82.png)

![notify2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/5b3ed884-9edf-b413-8b88-40cf8dac4cdf.png)


###テストを作成
テストを作ります。
「リスト 11.19: Userメイラーのテスト (Railsによる自動生成)」を読みます。

```ruby:　test/mailers/notify_mailer_test.rb
  test "follow_notify" do
    user = users(:michael)
    followed_user = users(:archer)
    mail = NotifyMailer.follow_notify(user, followed_user)
    assert_equal "follow notification", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["app173424233@heroku.com"], mail.from
    assert_match user.name,          mail.body.encoded
    assert_match followed_user.name, mail.body.encoded
  end
```

GREENになりました。

### followするときにメールを送信するようアクションを変更
followするときにメールを送信するようアクションを変更します。
どこに変更をするかは、上記のとおりrelationships_controller.rbです。

```ruby:app/controllers/relationships_controller.rb
  def create
      @user = User.find(params[:followed_id])
      current_user.follow(@user)
      if @user.follow_notify 
        NotifyMailer.follow_notify(current_user, @user).deliver.now
      end
      respond_to do |format|
        format.html { redirect_to @user }
        format.js
      end
  end
```

rails serverでフォローしてみて、メールが送られたかログを見ます。
テストのためにユーザーのfollow_notifyをtrueに変更します 。
コンソールで行います。

```ruby:
>> user = User.first
>> user.follow_notify = true
>> user.follow_notify
=> true
>> user.save
```

@userとcurrent_userが逆なことに気が付いたので修正します。

```ruby:app/controllers/relationships_controller.rb
      @user = User.find(params[:followed_id])
      current_user.follow(@user)
      if @user.follow_notify 
        NotifyMailer.follow_notify(@user, current_user).deliver_now
      end
```

メールが作成されました。

```
Sent mail to example@railstutorial.org (9.1ms)
Date: Sat, 19 Dec 2020 02:26:38 +0000
From: appXXXX3@heroku.com
To: example@railstutorial.org
Subject: follow notification

You are followed

Hi Example User,

You are followed by Vivianne Sporer.
```

integration testを作ります。フォローのテストに追加します。

```ruby:test/integration/following_test.rb
  def setup
    @user  = users(:michael)
    @other = users(:archer)
    log_in_as(@user)
    ActionMailer::Base.deliveries.clear

  test "should follow a user the standard way" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, params: { followed_id: @other.id }
    end
    assert_equal 1, ActionMailer::Base.deliveries.size
  end
```


```
 test_should_follow_a_user_the_standard_way#FollowingTest (0.82s)
        Expected: 1
          Actual: 0
        test/integration/following_test.rb:34:in `block in <class:FollowingTest>'
```

@otherであるarcherのfollow_notifyがfalseのため、REDになります。
fixtureのfollow_notifyをtrueに変更します。

```test/fixtures/users.yml
archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>  
  unique_name: Archer1
  follow_notify: true
```

GREENになりました。

###「メール通知」の設定機能を作成
「メール通知」をオンやオフに設定する機能を作ります。
前に調べた通り、既存のユーザー変更画面に項目を追加します。
ユーザー編集のビューを見ます。

```ruby:app/views/users/edit.html.erb
    <%= render 'form' %>  
```

formを呼び出しているので見ます。

チェックボックスが、ユーザーを記憶する画面にあったことを思い出し読みます。
「9.2 [Remember me] チェックボックス」

```ruby:
      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>
```

form_forでbooleanの列はどう指定するのかネットで調べます。
https://qiita.com/tanutanu/items/b86c4adc26ae464c71fd

関連するモデルがあるときは、check_boxメソッドを使うと分かりました。

>ちなみに、check_boxではなく、f.check_boxの形にすると（つまり、form_withやform_forの中で使うと、第１引数（オブジェクト）が省略できるようです。

とありました。

```ruby:app/views/users/_form.html.erb
   <%= form_for(@user, url: yield(:url_path) ) do |f|%>
    
...
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation, class: 'form-control' %>

    <%= f.label :follow_notify %>
    <%= f.check_box :follow_notify, class: 'form-control' %>


    <%= f.submit yield(:button_text), class: "btn btn-primary" %>
    <% end %>    
```


画面に表示してみます。
![notify3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/3e8de01e-7c22-8b20-ca31-4c832a2522c5.png)

更新したところ画面ではエラーが出ませんでしたが、ログにはエラーらしきものが出ていました。

```
Started PATCH "/users/1" for 133.106.33.48 at 2020-12-20 01:34:53 +0000
Processing by UsersController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"m2dZ6rvG6WmcX4kasgUW6qZzSngJNHZpszjdEOyaIfUXp5xpihm7OhBWm9USAUmA1t2VfXn9JSixF5MO26lHYg==", "user"=>{"name"=>"Example User", "email"=>"example@railstutorial.org", "unique_name"=>"Example", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "follow_notify"=>"1"}, "commit"=>"Save changes", "id"=>"1"}
Unpermitted parameter: :follow_notify
```

permitをどこかで設定したことを思い出したので、調べます。ユーザーのupdateのところではないかとあたりをつけたところ、ありましたので、追加します。

```ruby:app/controllers/users_controller.rb
    def user_params
        params.require(:user).permit(:name, :email, :password, 
                                     :password_confirmation, :unique_name, :follow_notify)
    end
```

更新のため、チェックボックスを外します。無事成功しました。

更新できたか、データをコンソールで確認します。

```
>> User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-11-11 23:26:45", updated_at: "2020-12-20 02:04:03", password_digest: "$2a$10$7XL.tADLyZ5OdhbHWb6Eh.MVIbc1QpHR.cuu9yqe5or...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ZXWhU/7mCD5JjJXuGHDnPeHIEXVzlmNVAvSj2h3fIXx...", activated: true, activated_at: "2020-11-11 23:26:45", reset_digest: nil, reset_sent_at: nil, unique_name: "Example", follow_notify: false>
```
falseに更新されていました。

チェックボックスのレイアウトが中央寄せなのを、分かりやすいように、変更します。

```css:app/assets/stylesheets/custom.scss
   <%= f.label :follow_notify %>
    <%= f.check_box :follow_notify, class: 'checkbox inline' %>


  #user_follow_notify {
    width: auto;
    margin-left: 0;
  }
  
  
  #session_remember_me {
    width: auto;
    margin-left: 0;
  }
```

```ruby:app/views/users/_form.html.erb
    <%= f.label :follow_notify %>
    <%= f.check_box :follow_notify, class: 'checkbox inline' %>
```
![notify4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/639093/dd8ecbbe-469e-765f-b643-c156958ec8e5.png)

ラベルとチェックボックスを同じ行にはできませんでしたが、よしとします。

###テストを作成
テストを作ります。
ユーザーの編集のテストに項目を追加します。GREENになりました。

```ruby:test/integration/users_edit_test.rb
 test "successful edit" do
... 
   follow_notify = false
    patch user_path(@user), params: { user: { name: name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "",
                                              unique_name: unique_name,
                                              follow_notify: follow_notify } }
```  

これで「フォロワーの通知」機能は完成です。

#所要時間

12/13から12/20までの8.0時間です