Rails Tutorialの第14章にある、拡張機能を作る件の続きです。
今回はフォロワーの通知機能を作ります。
##要件を定義
チュートリアルに要件が書いてあります。
ユーザーに新しくフォロワーが増えたときにメールで通知する機能を実装してみましょう。
続いて、メールでの通知機能をオプションとして選択可能にし、不要な場合は通知をオフにできるようにしてみましょう
Twitterに同じような機能があるか調べます。
・「通知」の画面がありました。「xxxさんにフォローされました」と表示されます。
・「メール通知」をオンやオフに設定できる画面があります。
要件はこの2点とします。
##仕様を設計
要件から具体的な機能に落とし込みます。
1.フォロワーが増えたときに、メールで通知する
フォローボタンを押したときに、メールを送信することをコントローラーに追加します。
コントローラーに作ることができるか、確認のために見てみます。
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.「メール通知」をオンやオフに設定できる
大きく2つあり、一つは設定を入力できること、もう一つはその設定でメール送信するかしないか動作を変更することです。
2.1 動作の変更機能
後者の動作の変更は、メール送信の際にif文で判定することでできそうです。
2.2 設定できる機能
前者の設定を入力できることを分解すると、一つは設定を入力する画面と、もう1つは入力した値を保持するためのモデルです。
2.2.1 設定画面
設定を入力する画面は、入力後に正しく入力できたか確認したいと思うので、表示する画面も必要です。
Twiiterの例と同様に、ユーザーごとの設定です。
設定は自分だけができるように制限をします。
既存のユーザー変更画面に項目を追加すればできそうです。
2.2.2 設定を保持するモデル
既存のユーザーモデルに項目を追加すればできそうです。
3.その他
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 |
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を書きます。
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を使います。
def follow_notify
mail to: current_user.email, subject: "follow notification"
end
メール本文を作成します。
<h1>You are followed</h1>
<p>Hi <%= current_user.name %>,</p>
<p>You are followed by <%= @user.name %>.</p>
テストのため、メイラーのプレビューファイルを更新します。
設定の必要があるのか分かりませんでしたので変更しません。
プレビューにブラウザーからアクセスします。
エラーになりました。
NameError in Rails::MailersController#preview
undefined local variable or method `current_user' for #<NotifyMailer:0x00007f64b45086e0>
やはりプレビューファイルで値を設定する必要があると分かりました。
current_userとフォローされたユーザーをメソッドの変数で渡すように変更します。
def follow_notify
user = User.first
followed_user = User.second
NotifyMailer.follow_notify(user, followed_user)
end
def follow_notify(user, followed_user)
@user = user
@followed_user = followed_user
mail to: user.email, subject: "follow notification"
end
You are followed
Hi <%= @user.name %>,
You are followed by <%= @followed_user.name %>.
プレビューで表示できました。
###テストを作成
テストを作ります。
「リスト 11.19: Userメイラーのテスト (Railsによる自動生成)」を読みます。
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です。
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に変更します 。
コンソールで行います。
>> user = User.first
>> user.follow_notify = true
>> user.follow_notify
=> true
>> user.save
@userとcurrent_userが逆なことに気が付いたので修正します。
@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を作ります。フォローのテストに追加します。
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に変更します。
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になりました。
###「メール通知」の設定機能を作成
「メール通知」をオンやオフに設定する機能を作ります。
前に調べた通り、既存のユーザー変更画面に項目を追加します。
ユーザー編集のビューを見ます。
<%= render 'form' %>
formを呼び出しているので見ます。
チェックボックスが、ユーザーを記憶する画面にあったことを思い出し読みます。
「9.2 [Remember me] チェックボックス」
<%= 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の中で使うと、第1引数(オブジェクト)が省略できるようです。
とありました。
<%= 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 %>
更新したところ画面ではエラーが出ませんでしたが、ログにはエラーらしきものが出ていました。
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のところではないかとあたりをつけたところ、ありましたので、追加します。
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に更新されていました。
チェックボックスのレイアウトが中央寄せなのを、分かりやすいように、変更します。
<%= 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;
}
<%= f.label :follow_notify %>
<%= f.check_box :follow_notify, class: 'checkbox inline' %>
ラベルとチェックボックスを同じ行にはできませんでしたが、よしとします。
###テストを作成
テストを作ります。
ユーザーの編集のテストに項目を追加します。GREENになりました。
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時間です