学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

【民泊5.1】【MacOSX】通知

コマンド
rails g model Notification content user:references


コマンド
rails g migration AddUnreadToUser unread:bigint


記述追加 db\migrate\20200702023845_add_unread_to_user.rb
3行目末尾に「, default: true」の記述追加

class AddUnreadToUser < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :unread, :bigint, default: true
  end
end



コマンド マイグレーション
rails db:migrate


記述追加 app\models\user.rb
22行目に「has_many :notifications」の記述追加

class User < ApplicationRecord

  # アバター画像表示用
  include Gravtastic
  gravtastic

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :confirmable, :omniauthable

  #長さ50文字以下 入力必須
  validates :fullname, presence: true, length: {maximum: 50}
  
  has_many :rooms
  has_many :reservations

  has_many :guest_reviews, class_name: "GuestReview", foreign_key: "guest_id"
  has_many :host_reviews, class_name: "HostReview", foreign_key: "host_id"

  has_many :notifications

  has_one :setting
  after_create :add_setting

  def add_setting
    Setting.create(user: self, enable_sms: true, enable_email: true)
  end

  def self.from_omniauth(auth)
    user = User.where(email: auth.info.email).first

    if user
      return user
    else
      where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
        user.email = auth.info.email
        user.password = Devise.friendly_token[0,20]
        user.fullname = auth.info.name
        user.image = auth.info.image
        user.uid = auth.uid
        user.provider = auth.provider

        user.skip_confirmation!
      end
    end
  end

  def is_active_host
    !self.merchant_id.blank?
  end

  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: "テストサイトMinpakuです。あなたのPinコードは #{self.pin}"
    )
  end

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

end



記述追加 app\models\notification.rb
2行目に「after_create_commit { NotificationJob.perform_later self }」の記述追加

class Notification < ApplicationRecord
  after_create_commit { NotificationJob.perform_later self }
  belongs_to :user
end



「app\models\message.rb」ファイルを編集します。


1.記述追加 app\models\message.rb(8行目)

after_create_commit :create_notification



2.記述追加 app\models\message.rb(14行目)

  private

  def create_notification
    if self.conversation.sender_id == self.user_id
      sender = User.find(self.conversation.sender_id)
      Notification.create(content: "#{sender.fullname}様から新しいメッセージがあります。", user_id: self.conversation.recipient_id)
    else
      sender = User.find(self.conversation.recipient_id)
      Notification.create(content: "#{sender.fullname}様から新しいメッセージがあります。", user_id: self.conversation.sender_id)
    end
  end  



app\models\message.rb

class Message < ApplicationRecord

  belongs_to :user
  belongs_to :conversation

  validates_presence_of :context, :conversation_id, :user_id

  after_create_commit :create_notification

  def message_time
    self.created_at.strftime('%Y年%-m月%-d日 %-H時%-M分')
  end

  private
  
  def create_notification
    if self.conversation.sender_id == self.user_id
      sender = User.find(self.conversation.sender_id)
      Notification.create(content: "#{sender.fullname}様から新しいメッセージがあります。", user_id: self.conversation.recipient_id)
    else
      sender = User.find(self.conversation.recipient_id)
      Notification.create(content: "#{sender.fullname}様から新しいメッセージがあります。", user_id: self.conversation.sender_id)
    end
  end  

end



「app\models\reservation.rb」ファイルを編集します。


1.記述追加 app\models\reservation.rb(6行目)

after_create_commit :create_notification



2.記述追加 app\models\reservation.rb(17行目)

  private

  def create_notification
    type = self.room.Instant? ? "新規予約" : "承認待ち"
    guest = User.find(self.user_id)

    Notification.create(content: "#{guest.fullname}様から#{type}のリクエストがあります。", user_id: self.room.user_id)
  end



app\models\reservation.rb

class Reservation < ApplicationRecord

  #    status: {承認待ち: 0, 承認: 1, 不承認: 2}
  enum status: {Waiting: 0, Approved: 1, Declined: 2}

  after_create_commit :create_notification
  
  belongs_to :user
  belongs_to :room

  scope :current_week_revenue, -> (user) {
    joins(:room)
    .where("rooms.user_id = ? AND reservations.updated_at >= ? AND reservations.status = ?", user.id, 1.week.ago, 1)
    .order(updated_at: :asc)
  }

  private

  def create_notification
    type = self.room.Instant? ? "新規予約" : "承認待ち"
    guest = User.find(self.user_id)

    Notification.create(content: "#{guest.fullname}様から#{type}のリクエストがあります。", user_id: self.room.user_id)
  end

end



「app\controllers」フォルダに「notifications_controller.rb」ファイルを新規作成して下さい。


app\controllers\notifications_controller.rb(新規作成したファイル)

class NotificationsController < ApplicationController
    before_action :authenticate_user!
  
    def index
      current_user.unread = 0
      current_user.save
      @notifications = current_user.notifications.reverse
    end
  end



コマンド
rails g channel notifications


書き換え app\assets\javascripts\channels\notifications.coffee

$(() ->
  App.notifications = App.cable.subscriptions.create {channel: "NotificationsChannel", id: $('#user_id').val() },
    received: (data) ->
      $('#num_of_unread').html(data.unread)
      $('#notifications').prepend(data.message)
      $('#navbar_num_of_unread').html(data.unread)

)



書き換え app\channels\notifications_channel.rb

class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_from "notification_#{params[:id]}"
  end
end



コマンド
rails g job notification


書き換え app\jobs\notification_job.rb

class NotificationJob < ApplicationJob
  queue_as :default

  def perform(notification)
    notification.user.increment!(:unread)
    ActionCable.server.broadcast "notification_#{notification.user.id}", message: render_notification(notification), unread: notification.user.unread
  end

  private

    def render_notification(notification)
      ApplicationController.render(partial: 'notifications/notification', locals: { notification: notification})
    end
end



記述追加 config\routes.rb
18行目に「get '/notifications' => 'notifications#index'」の記述追加

Rails.application.routes.draw do

  #ルートをpages#homeに設定
  root 'pages#home'

  # アクションケーブル
  mount ActionCable.server => '/cable'

  get 'pages/home'
  get '/your_trips' => 'reservations#your_trips'
  get '/your_reservations' => 'reservations#your_reservations'
  get 'search' => 'pages#search'
  get 'dashboard' => 'dashboards#index'
  get '/host_calendar' => "calendars#host"
  get '/payment_method' => "users#payment"
  get '/payout_method' => "users#payout"
  get '/notification_settings' => 'settings#edit'
  get '/notifications' => 'notifications#index'

  post '/add_card' => "users#add_card"
  post '/notification_settings' => 'settings#update'

  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

  resources :rooms, except: [:edit] do
    member do
      get 'listing'
      get 'pricing'
      get 'description'
      get 'photo_upload'
      get 'amenities'
      get 'location'
      get 'preload'
      get 'preview'
    end
    resources :photos, only: [:create, :destroy]
    resources :reservations, only: [:create]
    resources :calendars
  end

  resources :guest_reviews, only: [:create, :destroy]
  resources :host_reviews, only: [:create, :destroy]

  resources :revenues, only: [:index]

  resources :reservations, only: [:approve, :decline] do
    member do
      post '/approve' => "reservations#approve"
      post '/decline' => "reservations#decline"
    end
  end

  resources :conversations, only: [:index, :create]  do
    resources :messages, only: [:index, :create]
  end

  devise_for :users,
  path: '',
  path_names: {sign_in: 'login', sign_out: 'logout', edit: 'profile', sign_up: 'registration'},
  controllers: {omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations'}
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end



「app\views」フォルダに「notifications」フォルダを新規作成してください。
作成した「notifications」フォルダに「index.html.erb」ファイルを新規作成します。



app\views\notifications\index.html.erb(新規作成したファイル)

<div class="panel panel-default">
  <div class="panel-body">
    <div class="container" id="notifications">
      <%= render @notifications %>
    </div>
  </div>
</div>



「app\views\notifications」フォルダに「_notification.html.erb」ファイルを新規作成してください。


app\views\notifications\_notification.html.erb(新規作成したファイル)

<div class="panel">
  <div class="panel-body">
    <strong><%= notification.content %></strong>
    <span class="pull-right"><%= notification.created_at.strftime('%Y年%-m月%-d日 %-H時%-M分') %></span>
  </div>
</div>



記述追加 app\views\dashboards\index.html.erb(29行目)

<%= link_to notifications_path do %>
    <span id="num_of_unread"><%= current_user.unread %></span> 件の通知があります。
<% end %>



app\views\dashboards\index.html.erb

<div class="row">
  <div class="col-md-3">

    <div class="center">
       <%= image_tag current_user.gravatar_url, class: "avatar-full" %>
    </div>
    
    <div class="panel panel-default">
      <div class="panel-heading"><%= current_user.fullname %></div>
      <div class="panel-body">
        <ul class="sidebar-list">
          <% if current_user.confirmation_token %>
            <li>メールアドレス<span class="pull-right icon-babu"><i class="fa fa-check-circle-o"></i></span></li>
          <% end %>
          <% if current_user.phone_verified %>
          <li>電話番号<span class="pull-right icon-babu"><i class="fa fa-check-circle-o"></i></span></li>
    <% end %>          
        </ul>
      </div>
    </div>
  </div>

  <div class="col-md-9">
    <!-- お知らせ -->
    <div class="panel panel-default">
      <div class="panel-heading">お知らせ</div>
      <div class="panel-body">

        <%= link_to notifications_path do %>
          <span id="num_of_unread"><%= current_user.unread %></span> 件の通知があります。
        <% end %>
        
      </div>
    </div>

    <!-- 登録している部屋 -->
    <div class="panel panel-default">
      <div class="panel-heading">登録しているお部屋(<%= @rooms.length %>)</div>
      <div class="panel-body">
        <%= render partial: "rooms/rooms_list", locals: {rooms: @rooms} %>
      </div>
    </div>

  </div>
</div>



記述追加 app\views\layouts\application.html.erb(30行目)

    <% if current_user %>
        <input type="hidden" id="user_id" value="<%= current_user.id %>">
    <% end %>



app\views\layouts\application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Minpaku</title>
    <%= csrf_meta_tags %>

    <!-- アクションケーブル -->
    <%= action_cable_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <!-- googleフォント -->
    <link href="https://fonts.googleapis.com/css2?family=Kosugi&display=swap" rel="stylesheet">
    <!-- アイコン -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
    <!-- 日付ピッカー デザインsunny-->
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/sunny/jquery-ui.css">
    <!-- 日付ピッカーの日本語化-->
    <script src="https://rawgit.com/jquery/jquery-ui/master/ui/i18n/datepicker-ja.js"></script>
    <!-- geocomplete -->
    <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBojDcZmScBkIOISjoYREjgid99iZUL2Tk&libraries=places"></script>
  </head>

  <body>

    <!-- _navbar.html.erb をレンダーする -->
    <%= render 'shared/navbar' %>
    <%= render 'shared/message' %>

    <!-- 通知用 -->
    <% if current_user %>
        <input type="hidden" id="user_id" value="<%= current_user.id %>">
    <% end %>

    <!-- ページをコンテナに格納 -->
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>



記述追加 app\views\shared\_navbar.html.erb(23行目)

<li>
  <%= link_to notifications_path do %>
  <i class="fa fa-bell fa-2x icon-babu"></i>
  <span class="badge" id="navbar_num_of_unread"><%= current_user.unread if current_user.unread > 0 %></span>
  <% end %>
</li>



app\views\shared\_navbar.html.erb

<!-- ナビゲーションバー -->
<nav class="navbar navbar-default navbar-static-top"  style ="margin: 0;">

  <div class="container">

    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">ナビゲーション トグル</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <%= link_to "テストサイトMinpaku", root_path, class: "navbar-brand" %>
    </div>

    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav navbar-right">
        <% if (!user_signed_in?) %>
            <li><%= link_to "ログイン", new_user_session_path %></li>
            <li><%= link_to "新規ユーザ登録", new_user_registration_path %></li>
        <% else %>

            <!-- 通知アイコン -->
            <li>
              <%= link_to notifications_path do %>
              <i class="fa fa-bell fa-2x icon-babu"></i>
              <span class="badge" id="navbar_num_of_unread"><%= current_user.unread if current_user.unread > 0 %></span>
              <% end %>
            </li>

            <li class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
                <!-- アバター画像 -->
                <%= image_tag current_user.gravatar_url, class: "img-circle avatar-small" %>&nbsp;
                <!-- 氏名表示に変更 -->
                <%= current_user.fullname %>
                <span class="caret"></span>
              </a>
              <ul class="dropdown-menu">
                <li><%= link_to "ダッシュボード", dashboard_path %></li>
                <li><%= link_to "登録したお部屋の管理", rooms_path %></li>
                <li><%= link_to "お部屋の新規登録", new_room_path %></li>
                <li><%= link_to "受注予約の管理(ホスト用)", your_reservations_path %></li>
                <li><%= link_to "予約履歴(ゲスト用)", your_trips_path %></li>
                <li><%= link_to "ホスト用カレンダー", host_calendar_path %></li>
                <li><%= link_to "収益", revenues_path %></li>
                <li><%= link_to "クレジットカード登録", payment_method_path, data: { turbolinks: false} %></li>
                <li><%= link_to "振り込み口座登録", payout_method_path %></li>
                <li><%= link_to "メッセージを見る", conversations_path %></li>                
                <li role="separator" class="divider"></li>
                <li><%= link_to "ユーザ登録情報修正", edit_user_registration_path %></li>
                <li><%= link_to "予約メール通知設定", notification_settings_path %></li>
                <li><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></li>
              </ul>
            </li>
        <% end %>
      </ul>
    </div>
    
  </div>

</nav>

<% if (user_signed_in?) && !current_page?(root_path) && !current_page?("/rooms/#{params[:id]}") %>
    <nav class="navbar navbar-default" style="background-color: #89dfd7;">
      <div class="container">
        <ul class="nav navbar-nav">
          <li <% if current_page?(dashboard_path) %> class="active" <% end %> > <%= link_to "ダッシュボード", dashboard_path %></li>
          <li <% if current_page?(host_calendar_path) %> class="active" <% end %> ><%= link_to "ホスト用カレンダー", host_calendar_path %></li>
          <li <% if current_page?(revenues_path) %> class="active" <% end %> ><%= link_to "収益", revenues_path %></li>
          <li <% if current_page?(payment_method_path) %> class="active" <% end %> ><%= link_to "クレジットカード登録", payment_method_path, data: { turbolinks: false} %></li>
          <li <% if current_page?(payout_method_path) %> class="active" <% end %> ><%= link_to "振り込み口座登録", payout_method_path %></li>
          <li <% if current_page?(notification_settings_path) %> class="active" <% end %> ><%= link_to "予約メール通知設定", notification_settings_path %></li>
          <li <% if current_page?(conversations_path) %> class="active" <% end %> ><%= link_to "メッセージを見る", conversations_path %></li>
        </ul>
      </div>
    </nav>
<% end %>



ブラウザ確認
http://localhost:3000/dashboard


何かアクションがあると通知がきてわかるようになりました。

通知の表示
通知の表示
通知の一覧
通知の一覧