Ruby on Rails チュートリアル 機能拡張3(フォロワーの通知)

#Ruby on Rails チュートリアルについて
Ruby on Railsを勉強したいというと、まず紹介される有名なRailsのチュートリアル。
内容はハードですが、無料でRailsによるWebアプリケーション開発を楽しく学べます。<br>
**Ruby on Rails チュートリアル**
https://railstutorial.jp/

#Sample Appの拡張
チュートリアルの最後には、作成したSampleAppの拡張機能についていくつかのヒントが記載されています。
その中の以下の機能を順に実装していきます(途中で挫折するかも。。。)。
1. ユーザー検索
2. マイクロポスト検索
3. フォロワーの通知
4. 返信機能
5. メッセージ機能

#フォロワーの通知
今回は、3つ目のフォロワーの通知の実装を行います。
フォロワーの通知では、新しく誰かがフォロワーした時とフォロワーの誰かがフォローを解除した時に、対象のユーザーにメールで通知するようにします。
また、ユーザーごとに通知設定を変更できるようにします。

前回(マイクロポスト検索)は[こちら](https://qiita.com/YN6127yn/items/6c839c40371b4ec213bf)

##環境と準備
今回は、特に新たにインストールする必要のあるモジュールはありません。

|モジュール   |  バージョン |  
|:----------|-----------:|
| Rails     |      5.1.2 |
| Ruby      |      2.3.1 |

##実装
まずフォロー時、フォロー解除時のメール通知機能を追加し、その後で通知設定の変更を実装します。

フォロー/フォロー解除時のメールの送信ということで、まずRelationship用のMailerを作成します。
コマンドラインで以下のコマンドを実行します。
`rails g mailer RelationshipMailer`
すると、railsが色々作ってくれるので、まずはMailer、次にメールの本文の作成という順番でいじってきます。

`RelationshipMailer`には、フォロー/フォロー解除時のメールを送るメソッドを作成します。

```ruby:app/mailers/relationship_mailer.rb
class RelationshipMailer < ApplicationMailer

  def follow_notification(user, follower)
    @user = user
    @follower = follower
    mail to: user.email, subject: "#{@follower.name} started following you"
  end

  def unfollow_notification(user, follower)
    @user = user
    @follower = follower
    mail to: user.email, subject: "#{@follower.name} unfollowed you"
  end
end
```

送信先アドレスとメールの題名を指定しています。
次に、メールの本文を作成します。
本文を記載するファイルは、app/views/relationship_mailerの下に作成します。
html形式とtext形式の両方を作成しましたが、今回はhtml形式のもののみご紹介します。

```html+erb:app/views/relationship_mailer/follow_notification.html.erb
<h1>Sample App</h1>

<p>Hi <%= @user.name %>,</p>

<p>
  <%= @follower.name %> started following you!
  Click on the link below to check your followers:
</p>

<%= link_to "Your follower list",
      followers_user_url(@user) %>
```

```html+erb:app/views/relationship_mailer/unfollow_notification.html.erb
<h1>Sample App</h1>

<p>Hi <%= @user.name %>,</p>

<p>
  <%= @follower.name %> unfollowed you.
  Click on the link below to check your followers:
</p>

<%= link_to "Your follower list",
      followers_user_url(@user) %>
```

メールができあがったので、プレビューファイルを更新して、ブラウザでメールの文面を確認できるようにします。

```ruby:test/mailers/previews/relationship_mailer_preview.rb
class RelationshipMailerPreview < ActionMailer::Preview

  def follow_notification
    user = User.first
    follower = User.second
    RelationshipMailer.follow_notification(user, follower)
  end

  def unfollow_notification
    user = User.first
    follower = User.second
    RelationshipMailer.unfollow_notification(user, follower)
  end
end
```

プレビュー結果
![followメール.png](https://qiita-image-store.s3.amazonaws.com/0/229468/aef30865-c8ef-ec0b-b2bf-5f428356e620.png)
![unfollowメール.png](https://qiita-image-store.s3.amazonaws.com/0/229468/23f4c4b8-4fd4-5f14-43e1-1cd18aa44dd8.png)

メールの文面の確認ができたので、実際にメールを送信する処理をRelationshipクラスに追加します。

```ruby:app/models/relationshp.rb
~
省略
~
def Relationship.send_follow_email(user, follower)
  RelationshipMailer.follow_notification(user, follower).deliver_now
end

def Relationship.send_unfollow_email(user, follower)
  RelationshipMailer.unfollow_notification(user, follower).deliver_now
end
~
省略
~
```

そして、最後にフォローとフォロー解除時の処理(follow/unfollow)から上記の処理をコールするようにして、メール通知機能は完了です。

```ruby:app/models/user.rb
def follow(other_user)
  active_relationships.create(followed_id: other_user.id)
  Relationship.send_follow_email(other_user, self)
end

def unfollow(other_user)
  active_relationships.find_by(followed_id: other_user.id).destroy
  Relationship.send_unfollow_email(other_user, self)
end
```

次に、通知設定の切り替えを実装します。
まず、下記のコマンドでusersテーブルに通知設定のON/OFFを設定するためのカラムを追加します。
`rails g migration add_follow_notification_to_users follow_notification:boolean`

migraionファイルを確認すると、以下のようになっています。

```ruby:db/migrate/20180113140506_add_follow_notification_to_user.rb
class AddFollowNotificationToUser < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :follow_notification, :boolean
  end
end
```

デフォルトで値をfalseにしたいので、add_columnの行に以下のようにdefault属性を追加します。
`add_column :users, :follow_notification, :boolean, default:false`
そして、dbにマイグレート/リセットし、シードファイルを再度読み込みます。
`rails db:migrate:reset`
`rails db:seed`

次に、viewの実装をします。
通知設定をユーザーが行う場合、新規ユーザー登録とプロフィール編集時に行えるようにすべきです。
今回は、_form.html.erbを両者からrenderするようにしていたため、修正は一箇所のみです。

```html+erb:app/views/users/_form.html.erb
~
省略
~
<%= f.label :follow_notification, class:"checkbox inline" do %>
    <%= f.check_box :follow_notification %>
    <span>Send email when other users follow or unfollow you</span>
<% end %>

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

見た目はこんな感じになります。
![edit画面.png](https://qiita-image-store.s3.amazonaws.com/0/229468/8c08d0c3-8dfa-1ca6-ecdd-fed960ad1e75.png)
<br>
次にUsersControllerのuser_paramメソッドを以下のように変更します。

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

これは、通知の設定/変更を許可するためです。

最後の最後に、Userクラスのfollow/unfollowメソッドを書き直して完成です。

```ruby:app/models/user.rb
def follow(other_user)
  active_relationships.create(followed_id: other_user.id)
  if other_user.follow_notification
    Relationship.send_follow_email(other_user, self)
  end
end

def unfollow(other_user)
  active_relationships.find_by(followed_id: other_user.id).destroy
  if other_user.follow_notification
    Relationship.send_unfollow_email(other_user, self)
  end
end
```

##テスト
最後にテストについて記載します。
今回は、メールの内容と通知設定の両方をテストします。

まずメールの内容ですが、題名、送信先、送信元、フォローした/フォローされたユーザーのユーザー名をチェックします。
テストは、relationship_mailer.rb作成時に自動作成された、relationship_mailer_test.rbに記載します。

```ruby:test/mailers/relationship_mailer_test.rb
class RelationshipMailerTest < ActionMailer::TestCase
  test "follow_notification" do
    user = users(:hoge)
    follower = users(:fuga)
    mail = RelationshipMailer.follow_notification(user, follower)
    assert_equal "#{follower.name} started following you", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name, mail.body.encoded
    assert_match follower.name, mail.body.encoded
  end

  test "unfollow_notification" do
    user = users(:hoge)
    follower = users(:fuga)
    mail = RelationshipMailer.unfollow_notification(user, follower)
    assert_equal "#{follower.name} unfollowed you", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name, mail.body.encoded
    assert_match follower.name, mail.body.encoded
  end
end
```

次の通知設定は、通知する設定の際にメールが送信されること、通知しない設定の際にメールが送信されないことをチェックします。
テストは、チュートリアル内で作成した、following_test.rbに追記します。

```ruby:test/integration/following_test.rb
~
省略
~
test "should send follow notification email" do
    post relationships_path, params: {followed_id: @other.id}
    assert_equal 1, ActionMailer::Base.deliveries.size
  end

  test "should not send follow notification email" do
    not_notify = users(:hogehoge)
    post relationships_path, params: {followed_id: not_notify.id}
    assert_equal 0, ActionMailer::Base.deliveries.size
  end

  test "should send unfollow notification email" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    delete relationship_path(relationship)
    assert_equal 2, ActionMailer::Base.deliveries.size # follow email and unfollow email
  end

  test "should not send unfollow notification email" do
    not_notify = users(:hogehoge)
    @user.follow(not_notify)
    relationship = @user.active_relationships.find_by(followed_id: not_notify.id)
    delete relationship_path(relationship)
    assert_equal 0, ActionMailer::Base.deliveries.size
  end
~
省略
~
```

上記のテストでは、fixtureで`@other` のみ通知設定ONにし、setupに`ActionMailer::Base.deliveries.clear`を追記し、毎回送信メール数を初期化しています。

両方のテストが通ったことを確認して終了です。

#最後に
私はまだRails歴2週間ほどであり、以上の実装/テストも私の環境で動いたということに過ぎません。
修正点や指摘等ございましたら、ぜひコメントお願いいたします。

#参考記事
フォロワーの通知では、下記のページを参考にさせていただきました。

**Action Mailerの基礎**
https://railsguides.jp/action_mailer_basics.html