Ruby on Rails 【Twilio】SMS送信と電話番号確認の実装 | Rails6.0


投稿日:2020年08月29日 (土)
当サイトのプロジェクト「民泊サイトを構築する(Rails5.0)」ではTwilioによるSMS送信と電話番号の確認を実装しています。
Ruby on Rails6.0での実装もほとんど変わらないのですが、プロジェクトで触れていなかったのでここで紹介させていただきます。

前提条件として「Bulma」と「devise」での実装ができている状態で進めます。

まずTwilioでアカウント登録をしてください。

アカウントの登録は以下の手順でお願いします。


まずGemFileに以下の記述を追加します。
# Twilio
gem 'twilio-ruby', '~> 5.40.0'

bundleでインストールします。
bundle


「config\initializers」フォルダに「twilio.rb」ファイルを新規作成してください。

「config\initializers\twilio.rb(新規作成したファイル)」
ご自分のアカウントSIDとAUTHTOKENを入れて下さい。
Twilio.configure do |config|
    config.account_sid = 'AC6c3786554525790e777ea3bca592a70e'
    config.auth_token = 'ae5debedfafff4ddd84634f6d505edd1'
end


ユーザーテーブルに「pin」「phone_verified」のカラムを追加します。
「phone_number」カラムがない場合はここで追加してください。
rails g migration AddPinAndPhoneVerifiedToUser pin phone_verified:boolean

maigrateを適用します。
rails db:migrate


「app\models\user.rb」ファイルに以下の記述を追加します。
「from」にはTwilioで無料取得したご自分のアメリカの電話番号を入力して下さい。
  def generate_pin
    self.pin = SecureRandom.hex(2)
    self.phone_verified = false
    save
  end

  def send_pin
    @client = Twilio::REST::Client.new
    @client.messages.create(
      from: '+12056565281',
      to: self.phone_number,
      body: "テストサイトMinpaku6です。あなたのPinコードは #{self.pin}"
    )
  end

  def verify_pin(entered_pin)
    update(phone_verified: true) if self.pin == entered_pin
  end    


「app\controllers\users_controller.rb」ファイルに以下の記述を追加します。
  def update_phone_number
    current_user.update_attributes(user_params)
    # 日本の電話国番号+81を先頭につける
    japan_number = user_params[:phone_number].sub(/\A./,'+81')
    current_user.update_attributes(phone_number: japan_number)
    current_user.generate_pin
    current_user.send_pin

    redirect_to edit_user_registration_path, notice: "保存しました。"
  rescue Exception => e
    redirect_to edit_user_registration_path, alert: "#{e.message}"
  end

  def verify_phone_number
    current_user.verify_pin(params[:user][:pin])

    if current_user.phone_verified
      flash[:notice] = "電話番号が確認されました。"
    else
      flash[:alert] = "電話番号を確認できません。"
    end

    redirect_to edit_user_registration_path

  rescue Exception => e
    redirect_to edit_user_registration_path, alert: "#{e.message}"
  end


  private

  def user_params
    params.require(:user).permit(:phone_number, :pin)
  end  



ルートを設定します。
「config\routes.rb」ファイルに以下の記述を追加します。
  resources :users, only: [:show] do
    member do
      post '/verify_phone_number' => 'users#verify_phone_number'
      patch '/update_phone_number' => 'users#update_phone_number'
    end
  end


ビューファイルを編集します。
モダールの記述は「bulma」で書いているので、環境が違う方はそれぞれの記述に合わせてください。
「app\views\devise\registrations\edit.html.erb」ファイルに以下の記述を追加します。
          <div class="box">
            <% if !current_user.phone_number.blank? %>
              <div  class="content is-medium">あなたの電話番号</div>
              <h4 class="content is-medium"><strong><%= current_user.phone_number %></strong></h4>

                <% if current_user.phone_verified == false %>
                  <button class="button is-primary is-fullwidth toggle-contact2">PINコード入力</button>
                <% else %>
                  <button class="button is-primary is-fullwidth toggle-contact">電話番号を更新</button>              
                <% end %>

            <% else %>
              <button class="button is-primary is-fullwidth toggle-contact">電話番号を追加</button>          
            <% end %>     
          </div>


<!-- モダール 電話番号追加 -->
<div class="modal" id="contact-form">
    <div class="modal-background"></div>
    <div class="modal-card">
        <header class="modal-card-head">
            <p class="modal-card-title">電話番号を追加</p>
            <button class="delete toggle-contact" aria-label="close"></button>
        </header>
        <section class="modal-card-body">
            <div class="columns">
                    <%= form_for current_user, url: update_phone_number_user_path(current_user) do |f| %>
                        <div class="card-content">
                          <div class="field">
                            <%= f.label :電話番号, class: "label" %>
                            <%= f.text_field :phone_number, autofocus: true, autocomplete: "phone_number", class: "input" %>
                          </div>
                            <%= f.submit "電話番号を追加する", class: "button is-danger is-fullwidth" %>
                        </div>
                    <% end %>
            </div>
        </section>
    </div>
</div>

<!-- モダール PINコード入力 -->
<div class="modal" id="contact-form2">
    <div class="modal-background"></div>
    <div class="modal-card">
        <header class="modal-card-head">
            <p class="modal-card-title">PINコード入力</p>
            <button class="delete toggle-contact2" aria-label="close"></button>
        </header>
        <section class="modal-card-body">
            <div class="columns">
              <%= form_for current_user, url: verify_phone_number_user_path(current_user), method: :post do |f| %>
                <div class="card-content">
                  <div class="field">
                    <%= f.label :PINコード, class: "label" %>
                    <%= f.text_field :pin, class: "input", placeholder: "SMS送信されたPINコード", value: "" %>
                  </div>
                    <%= f.submit "PINコード確認", class: "button is-danger is-fullwidth" %>
                </div>
              <% end %>
            </div>
        </section>
    </div>
</div>

<script>
    <!-- 電話番号追加 -->
    var toggle_modals = $('.toggle-contact');
    if (toggle_modals) {
        toggle_modals.on('click', function(event) {
            event.stopPropagation();
            event.preventDefault();
            var form = document.getElementById('contact-form');
            form.classList.toggle('is-active');
        });
    }

    <!-- PIN入力 -->
    var toggle_modals2 = $('.toggle-contact2');
    if (toggle_modals2) {
        toggle_modals2.on('click', function(event) {
            event.stopPropagation();
            event.preventDefault();
            var form2 = document.getElementById('contact-form2');
            form2.classList.toggle('is-active');
        });
    }    

</script>


「app\views\devise\registrations\edit.html.erb」ファイルの全文を載せておきます。
<section class="hero is-dark is-fullheight">
  <div class="hero-body">
    <div class="container">
      <div class="columns is-centered">
        <div class="column is-6-table is-6-desktop is-6-widescreen">
          <div class="box">
            
            <div class="field has-text-centered">
              <strong>ユーザ登録情報編集</strong>
            </div>
            <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
              <%= render "devise/shared/error_messages", resource: resource %>
              <div class="field">
                <%= f.label :氏名, class: "label" %>
                <%= f.text_field :full_name, autofocus: true, autocomplete: "full_name", class: "input" %>
              </div>
              <div class="field">
                <%= f.label :メールアドレス, class: "label" %>
                <%= f.email_field :email, autofocus: true, autocomplete: "email", class: "input" %>
              </div>
              <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
                <div>現在、確認を待っています: <%= resource.unconfirmed_email %></div>
              <% end %>
              <div class="field">
                <%= f.label :パスワード, class: "label"  %> <i>(変更しない場合は空白のままにします)</i>
                <%= f.password_field :password, autocomplete: "new-password", class: "input" %>
                <% if @minimum_password_length %>
                  <br />
                  <em><%= @minimum_password_length %> 文字以上</em>
                <% end %>
              </div>
              <div class="field">
                <%= f.label :確認, class: "label"  %>
                <%= f.password_field :password_confirmation, autocomplete: "new-password", class: "input" %>
              </div>
              <div class="field">
                <%= f.submit "更新する", class: "button is-danger is-fullwidth" %>
              </div>
            <% end %>
            <%= link_to "戻る", :back, class: "button is-fullwidth m-t-10" %>
          </div>

          <div class="box">
            <% if !current_user.phone_number.blank? %>
              <div  class="content is-medium">あなたの電話番号</div>
              <h4 class="content is-medium"><strong><%= current_user.phone_number %></strong></h4>

                <% if current_user.phone_verified == false %>
                  <button class="button is-primary is-fullwidth toggle-contact2">PINコード入力</button>
                <% else %>
                  <button class="button is-primary is-fullwidth toggle-contact">電話番号を更新</button>              
                <% end %>

            <% else %>
              <button class="button is-primary is-fullwidth toggle-contact">電話番号を追加</button>          
            <% end %>     
          </div>

        </div>
      </div>
    </div>
  </div>
</section>

<!-- モダール 電話番号追加 -->
<div class="modal" id="contact-form">
    <div class="modal-background"></div>
    <div class="modal-card">
        <header class="modal-card-head">
            <p class="modal-card-title">電話番号を追加</p>
            <button class="delete toggle-contact" aria-label="close"></button>
        </header>
        <section class="modal-card-body">
            <div class="columns">
                    <%= form_for current_user, url: update_phone_number_user_path(current_user) do |f| %>
                        <div class="card-content">
                          <div class="field">
                            <%= f.label :電話番号, class: "label" %>
                            <%= f.text_field :phone_number, autofocus: true, autocomplete: "phone_number", class: "input" %>
                          </div>
                            <%= f.submit "電話番号を追加する", class: "button is-danger is-fullwidth" %>
                        </div>
                    <% end %>
            </div>
        </section>
    </div>
</div>

<!-- モダール PINコード入力 -->
<div class="modal" id="contact-form2">
    <div class="modal-background"></div>
    <div class="modal-card">
        <header class="modal-card-head">
            <p class="modal-card-title">PINコード入力</p>
            <button class="delete toggle-contact2" aria-label="close"></button>
        </header>
        <section class="modal-card-body">
            <div class="columns">
              <%= form_for current_user, url: verify_phone_number_user_path(current_user), method: :post do |f| %>
                <div class="card-content">
                  <div class="field">
                    <%= f.label :PINコード, class: "label" %>
                    <%= f.text_field :pin, class: "input", placeholder: "SMS送信されたPINコード", value: "" %>
                  </div>
                    <%= f.submit "PINコード確認", class: "button is-danger is-fullwidth" %>
                </div>
              <% end %>
            </div>
        </section>
    </div>
</div>

<script>
    <!-- 電話番号追加 -->
    var toggle_modals = $('.toggle-contact');
    if (toggle_modals) {
        toggle_modals.on('click', function(event) {
            event.stopPropagation();
            event.preventDefault();
            var form = document.getElementById('contact-form');
            form.classList.toggle('is-active');
        });
    }

    <!-- PIN入力 -->
    var toggle_modals2 = $('.toggle-contact2');
    if (toggle_modals2) {
        toggle_modals2.on('click', function(event) {
            event.stopPropagation();
            event.preventDefault();
            var form2 = document.getElementById('contact-form2');
            form2.classList.toggle('is-active');
        });
    }    

</script>


ブラウザを確認します。

電話番号を追加をクリックします。
電話番号を追加.png 106KB



モダールが開きますので電話番号を追加します。
モダール.png 54.2KB




SMSが届きますので書かれているPINコードを入力します。
PIN入力.png 32.9KB

PIN確認.png 43.4KB




これで電話番号が登録され、「phone_verified」のカラムが「true」になります。
true.png 137KB





Rails6.0 Windows MacOSX

動画学習サイトの構築 Rails6.0

  0 (0)

タスク   128

4,000円

    サンプルサイトは初期起動に15秒ほどかかります。

 
Rails6.0 Windows MacOSX

民泊サイトの構築 Rails6.0

  0 (0)

タスク   128

5,000円

    サンプルサイトは初期起動に15秒ほどかかります。

 
Rails5.0 Windows MacOSX

動画学習サイトの構築 Rails5.0

  0 (0)

タスク   100

3,000円

    サンプルサイトは初期起動に15秒ほどかかります。

 
Rails6.0 Windows MacOSX

お仕事売買サイトの構築 Rails6.0

  0 (0)

タスク   146

5,000円

    サンプルサイトは初期起動に15秒ほどかかります。

 
Rails5.0 Windows MacOSX

民泊サイトの構築 Rails5.0

  0 (0)

タスク   136

4,000円

    サンプルサイトは初期起動に15秒ほどかかります。