HPなどでユーザーが行うアクションを非同期処理でかっこよく眠らせる

こんにちは
railsでインターンをしているものです。
今回はhpに関する依頼があったので実装していきます。

#依頼
お問い合わせフォーム

- ユーザーがボタンを`連打`すると何重にもメールが届くのでそれを阻止してほしい
- `非同期処理`を行なって、メール送信の動作を裏で行いユーザーには完了のページをすぐに移行できるようにする

---
一つずつ見ていきましょう

# ボタン`連打`を防ぐ

人ってうまくいかないと何回も試行錯誤しますよね？
お問い合わせフォームも然りです。
メールの送信に時間がかかるとその場面でページが止まってしまいます。
これは、プログラムが順番に処理を行うのでメール送信が完了するまで次のページに移行としないんです。(うまい)
後でもう少し詳しくやりますが、このせいでユーザーは送信できているか不安になり、`何度`もボタンを`連打`していきます。
![AP2018FTHG5353_TP_V4.jpg](https://qiita-image-store.s3.amazonaws.com/0/181534/3342d3a6-60e1-0383-bade-e5187929ebe3.jpeg)
> PAKUTASO からの引用

そこで必要になるのが二重投稿防止の処理
しかし何とJSでちょこちょこっと描いてあげるだけでできるのです。
[参考](https://qiita.com/syobochim/items/120109315f671918f28d)
以下を追加し

```javascript:/app/assets/javascripts/confirm.js
$(function () {

  $("form").submit(function () {
    var self = this;
    $(":submit", self).prop("disabled", true);
    setTimeout(function () { //一定時間でボタンをenabledにするようにした方が親切らしい。
      $(":submit", self).prop("disabled", false); 
    }, 10000);
  });

});
```
こちらを記述
これでボタンが一定時間立たないと押せなくなりました。

ちなみに[submit自体にdisableを記述する方法](https://rails.densan-labs.net/form/double_submit_protection.html)などもありましたが、複数のsubmitがある際に適用できる場合を考えこちらを採用しました。

# `非同期処理`でメール送信の動作を裏で行う

先ほど記述した通り、プログラムには順番があり、それが完了しないと次の処理には移りません。
これを`同期処理`と呼びます。

本来ならばそれが普通なのですが、メール送信や画像投稿など時間のかかる処理をする際にはユーザーが待たなくてはなりません。
もう連打はできないはずなのでこれではユーザーが何もすることができませんね。

そこで、`非同期処理`を行なってメール送信の処理を裏に回して次の処理を先に行えるようにします。
[非同期処理をもっと理解したい人はこちら](https://qiita.com/klme_u6/items/ea155f82cbe44d6f5d88)

---
まずはrailsで非同期処理の[リサーチ](https://tech.eviry.com/2018/08/15/rails_asynchronous_processing/)
Railsでは非同期処理を実装するのに便利なgemがいくつかあり、有名どころだと、

- sidekiq
- rescue
- delayed job

が挙げられていました。
ですが、僕は一目でどれにするか決めました。


> sidekiq(恐らくsidekick(相棒))、君こそが僕のパートナーだ！！

素晴らしいネーミングセンスに一目惚れしました。こんなことってあるんですね。

---

あとは[ここ](https://dev.classmethod.jp/server-side/ruby-on-rails/ruby-on-rails_active-job-sidekiq/)や[ここ](https://qiita.com/jiggaman0412/items/4e7f66f7c237f3d1e945),[ここ](https://www.slideshare.net/taichikanako/rails-56292351)ら辺を参考に実装していきます。
### いざ、実食

sidekiqにはredisがバックグラウンド（ジョブ管理）に必要なので[brew](https://qiita.com/omega999/items/6f65217b81ad3fffe7e6)でインストール

```terminal
brew install redis
```
続いてsidekiqのgem登録

```ruby:Gemfile
gem 'sidekiq'
```

```terminal
bundle install
```
続いて、config/sidekiq.ymlに以下を記述

```ruby:/config/sidekiq.yml
:concurrency: 25
:pidfile: ./tmp/pids/sidekiq.pid
:logfile: ./log/sidekiq.log
:queues:
  - default
```

Active Jobがsidekiqを使う設定

```ruby:/config/application.rb
config.active_job.queue_adapter = :sidekiq
```
Jobの作成

```terminal
bundle exec rails g job My
```
MyJobクラスの実装

```ruby:/jobs/my_job.rb
class MyJob < ActiveJob::Base
  queue_as :default

  def perform(@contact)
    # 非同期で実行させたい処理を書く(今回はメール送信)
    Mailer.post_email(@contact).deliver
  end
end
```
ContactMailerクラスの実装

```ruby:/models/mailer.rb
class Mailer < ApplicationMailer
  def post_email(contacts)
    @contacts = contacts
    mail to: 'info@hoge.com',
         bcc: 'hoge@hoge.com',
         subject: "[HP]お問い合わせ"
  end
end
```
コントローラー作成

```terminal
rails generate controller contact
```
メールを送信するアクション実装

```ruby:/app/controllers/mailer.rb
class ContactController < ApplicationController
  def run
    @contact = Contact.new(contact_params) #contactクラスの実装は割愛します

    # 即時実行する場合
    MyJob.perform_later(@contact)
    # 時間を置いて実行する場合
    MyJob.set(wait: 5.second).perform_later(@contact) #5秒後実行
    MyJob.set(wait: 10.second).perform_later(@contact) #10秒後実行
 
    render :nothing => true
  end
end
```

ルーティング

```/config/routes.rb
post 'contact/run'
```

お疲れ様です。ここまでで一旦実装は完了です。

---

続いて
redisの起動

```terminal
redis-server
```
<img width="1036" alt="Screenshot 2019-01-18 at 11.45.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/181534/0758dd7c-d1df-8c27-1539-a3b5aed6ec9a.png">
別のターミナルにて、sidekiqを起動

```terminal
bundle exec sidekiq
```
<img width="348" alt="Screenshot 2019-01-18 at 11.45.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/181534/d721631a-75d4-9a1d-85b2-52b2c8e42251.png">
かっこいい

また別のターミナルにて、Railsを起動

```terminal
bundle exec rails s
```
ログを監視

```terminal
tail -f log/development.log
```
ジョブを実行

```terminal
curl http://localhost:3000/start/run -X POST -d ''
```




お疲れ様でした。
これでかっこよくユーザーの行動を眠らせることができましたね！！