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

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

Rails6.0 | 民泊サイトの構築(改良版)| 42 | 通知 | for MacOSX



| 41 | メッセージと会話  <<  [ホーム]


通知機能を実装します。


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


コマンド
rails g migration AddUnreadToUser unread:bigint


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

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



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


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

class User < ApplicationRecord

  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_attached :avatar

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

  validates :full_name, presence: true, length: {maximum: 50}      

  def is_active_host
    !self.merchant_id.blank?
  end

  def self.from_omniauth(auth)
    user = User.where(email: auth.info.email).first
    if user
      if !user.provider
        user.update(uid: auth.uid, provider: auth.provider, image: auth.info.image)
      end      
      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.full_name = auth.info.name   # ユーザーモデルに名前があると仮定
        user.image = auth.info.image # ユーザーモデルに画像があると仮定
        user.uid = auth.uid
        user.provider = auth.provider
      end
    end
  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.6行目に以下の記述を追加します。

after_create_commit :create_notification



2.10行目に以下の記述を追加します。

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



app\models\message.rb

class Message < ApplicationRecord

  belongs_to :user
  belongs_to :conversation

  after_create_commit :create_notification

  validates :content, presence: { message: '空白にはできません' }

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

end



「app\models\reservation.rb」ファイルを以下のように編集します。


1.6行目に以下の記述を追加します

after_create_commit :create_notification



2.13行目に以下の記述を追加します。

  private
  def create_notification
    type = self.status? ? "予約" : "完了処理"
    guest = User.find(self.user_id)
    Notification.create(content: "#{guest.full_name}様の#{type}がありました。", user_id: self.room.user_id)
  end  



app\models\reservation.rb

class Reservation < ApplicationRecord

   # status: {待ち: 0, 済み: 1}
   enum status: {Waiting: 0, Approved: 1}

   after_create_commit :create_notification

  belongs_to :user
  belongs_to :room

  has_many :reviews

  private
  def create_notification
    type = self.status? ? "予約" : "完了処理"
    guest = User.find(self.user_id)
    Notification.create(content: "#{guest.full_name}様の#{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.order(created_at: "DESC").page(params[:page]).per(5)
    end
  end



コマンド
rails g channel notifications


「app\javascript\channels」フォルダに「notifications.coffee」ファイルを新規作成してください。


app\javascript\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
17行目に「get '/notifications' => 'notifications#index'」の記述追加

Rails.application.routes.draw do

  # ルートを app\views\pages\home.html.erb に設定
  root 'pages#home'

  get 'pages/home'
  get '/dashboard', to: 'users#dashboard'
  get '/users/:id', to: 'users#show', as: 'user'
  get '/your_trips' => 'reservations#your_trips'
  get '/your_reservations' => 'reservations#your_reservations'
  get 'search' => 'pages#search'
  get '/host_calendar' => "calendars#host"
  get 'settings/payment', to: 'users#payment', as: 'settings_payment'
  get 'settings/payout', to: 'users#payout', as: 'settings_payout'
  get '/conversations', to: 'conversations#list', as: "conversations"
  get '/conversations/:id', to: 'conversations#show', as: "conversation_detail"
  get '/notifications' => 'notifications#index'
  
  post '/users/edit', to: 'users#update'
  post '/settings/payment', to: 'users#update_payment', as: "update_payment"
  post 'messages', to: 'messages#create'

  devise_for :users, 
  path: '', 
  path_names: {sign_up: 'register', sign_in: 'login', edit: 'profile', sign_out: 'logout'},
  controllers: {omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations'}

  resources :rooms, except: [:edit] do
    member do
      get 'listing'
      get 'pricing'
      get 'description'
      get 'photo_upload'
      get 'amenities'
      get 'location'
      get 'preload'
      get 'preview'
      delete :delete_photo
      post :upload_photo
    end
    resources :reservations, only: [:create]
  end

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

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

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end



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



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

<div class="card">
    <div class="card-body">
        <div style="margin-left: 3rem; margin-bottom: 30px;">
            <%= link_to 'メッセージを見る', conversations_path, class: "btn btn-primary text-light" %>
        </div>
        <!-- ページネーション -->
        <div style="margin-left: 4rem;"><%= paginate @notifications %></div>

        <div class="card">
        <div class="card-header bg-info text-light">
          <span style="margin-left: 3rem; font-size: 1.3rem;">通知 Notifications</span>
        </div>
        <table class="table table-striped" style="margin-left: 3rem; margin-bottom: 7rem; margin-top: 2rem; width: 80%;">

                <% @notifications.each do |n| %>
                    <tr>
                        <td>
                            <strong><%= n.content %></strong>
                            <span class="pull-right"><%= time_ago_in_words(n.created_at) %>&nbsp;&nbsp;&nbsp;(&nbsp;<%= n.created_at.strftime('%Y年%-m月%-d日 %-H時%-M分') %>&nbsp;)&nbsp;</span>
                        </td>
                    </tr>
                <% end %>

        </table>
      </div>




    </div>
</div>



ダッシュボードビューを更新します。


app\views\users\dashboard.html.erb

<div class="row" style="margin: 20px;">
    <!-- 左側 -->
    <div class="col-4">
        <div class="card">
            <div class="card-body">     
            
                <!--通知 -->
                <div class="mx-auto">
                    <%if current_user.unread > 0 %>                             
                        <div class="content badge bg-danger" style="margin-left: 2rem; margin-top: 1rem;">
                            <%= link_to notifications_path, style: "text-decoration: none;" do %>
                                <div style="color: white; font-size: 1.2rem; padding: 0.3rem;"><span id="num_of_unread">
                                    <%= current_user.unread %></span> 件の通知
                                </div>
                            <% end %>                      
                        </div>
                    <% else %>
                        <div class="content badge bg-secondary" style="margin-left: 2rem; margin-top: 1rem;">
                            <%= link_to notifications_path, style: "text-decoration: none;" do %>
                                <div style="color: white; font-size: 1.2rem; padding: 0.3rem;">
                                    <span id="num_of_unread">
                                        <%= current_user.unread %>
                                    </span> 件の通知
                                </div>
                            <% end %>
                        </div>
                    <% end %>                      
                </div>            
            </div>
     
            <!-- アバター -->
            <%= image_tag avatar_url(current_user), class: "img-fluid img-thumbnail rounded-pill" %>
            <!-- 画像アップロードボタン -->
            <div class="mx-auto" style="text-align: center;">
                <h5 class="card-title btn-block" style="margin-left: 15px; margin-top: 10px;"><%= current_user.full_name %></h5>
                <%= current_user.about %>   
            </div>
            <br/>            
            <a data-toggle="collapse" href="#collapseExample3" role="button" aria-expanded="false" aria-controls="collapseExample3" style="text-decoration: none;">
                <div>
                    <strong>
                        <button type="button" class="btn btn-info btn-block"><i class="fas fa-upload"></i>アバター画像アップロード<br/> Avatar image upload</button>
                    </strong>
            </a>
            <br/>
            <div class="collapse" id="collapseExample3">
                <%= form_for :user, url: users_edit_url(current_user), action: :update, method: :post do |f| %>
                    <%= f.file_field :avatar, class: "input-group-text", onchange: "this.form.submit();" %>
                <% end %>   
            </div>
            <br/>


            <!-- オンラインステータス -->
                
            <a data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false" aria-controls="collapseExample">
                <div style="margin-left: 20px;">
                    <strong>
                        <% if current_user.status %>
                            <span class="btn btn-success btn-sm"><i class="toggle far fa-edit"></i>オンライン online</span>
                        <% else %>
                            <span class="btn btn-danger btn-sm"><i class="toggle far fa-edit"></i>オフライン offline</span>
                        <% end %>
                    </strong>
            </a>
                    <div class="collapse" id="collapseExample">
                        <div class="card card-body" style="width:16rem;">
                            <%= form_for :user, url: users_edit_url(current_user), action: :update, method: :post do |f| %>
                                    <%= f.select(:status, options_for_select([["オンライン online", true], ["オフライン offline", false]]), {}, {class: "custom-select"}) %>
                                <br/>
                                <br/>
                                <%= f.submit "保存", class: "btn btn-outline-info" %>
                            <% end %>
                        </div>
                    </div>
                </div>
                <br/>
                <!-- 自己紹介 -->                                       
                <a data-toggle="collapse" href="#collapseExample2" role="button" aria-expanded="false" aria-controls="collapseExample2">
                    <div style="margin-left: 20px;">
                        
                            <span class="btn btn-primary btn-sm"><i class="toggle far fa-edit"></i>コメント comment</span>
                        
                </a>
                        <div class="collapse" id="collapseExample2">
                            <div class="card card-body" style="width: 16rem;">
                                <%= form_for :user, url: users_edit_url(current_user), action: :update, method: :post do |f| %>
                                    <%= f.text_area :about, autofocus: true, autocomplete: 'form'%>
                                    <br/>
                                    <br/>
                                    <%= f.submit "保存", class: "btn btn-outline-info" %>
                                <% end %>

                            </div>
                        </div>
                    </div>
                            
                    <!-- 電話番号 -->
                    <div class="mx-auto" style="margin-top: 1rem;">
                    <% if !current_user.phone_number.blank? %>
                        <div style="margin-left: 1rem;"><i class="far fa-check-circle" style="color:#528fff;"></i>電話番号登録済</div>
                    <% else %>
                        <span style="margin-left: 1rem;">電話番号を登録していません</span>
                    <% end %>                    
                    </div>
                    <div class="mx-auto" style="margin-top: 1rem;">
                    <span style="margin-left: 1rem;"><%= l(current_user.created_at, format: :long) %></span>
                    </div>                       
            </div>
        </div>
    </div>
    <!-- 右側 -->
    <div class="col-8">
 
        <!-- お知らせ -->
        <div class="card">
            <div class="card-header bg-primary text-light">

                <!-- 通知 -->
                <div>
                    <%= link_to notifications_path, style: "text-decoration: none; color: white;" do %>
                        <i class="fa fa-bell fa-2x icon-babu" style="color: yellow;"></i>
                        <%if current_user.unread > 0 %>
                            <span class="badge" id="navbar_num_of_unread" style="color: white;"><%= current_user.unread %></span>
                        <% end %>
                        お知らせ
                    <% end %>
                </div>           

            </div>
            <div class="card-body">

            <table class="table"”>
                <% @notifications.each do |n| %>
                    <tr>
                        <td>
                            <strong><%= n.content %></strong>
                            <span class="pull-right"><%= time_ago_in_words(n.created_at) %>(&nbsp;<%= n.created_at.strftime('%Y年%-m月%-d日 %-H時%-M分') %>&nbsp;)&nbsp;</span>
                        </td>
                    </tr>
                <% end %>
            </table>
            <!-- ページネーション -->
            <div style="margin-left: 1rem;"><%= paginate @notifications %></div>            

            </div>
        </div>
        <br/>
        <!-- 登録しているお部屋 -->
        <div class="card">
            <div class="card-header bg-dark text-light">
                登録しているお部屋(<%= current_user.rooms.length %>)
            </div>
            <div class="card-body">
                <div class="row">
                    <% current_user.rooms.each do |room| %>
                        <% if room.active? %>
                        
                            <div class="col-6 col-md-4" style="margin-bottom: 20px;">
                                <%= link_to room_path(room), data: { turbolinks: false} do %>
                                    <%= image_tag room_cover(room), style: "width: 100%; height: 180;" %>
                                <% end %>
                                <span class="badge bg-light"><div id="star_<%= room.id %>"></div><i class="fa fa-star fa-1x" style="color: gold;"></i><span style="font-size: 1rem;"><%= pluralize(room.average_rating, "") %></span></span><br/>
                                <span class="badge bg-info text-light"><%= room.listing_name %></span><br/>
                                <span class="badge bg-secondary text-light"><%= room.address %></span><br/>
                                <footer class="badge bg-warning" style="font-size: 0.9rem;"><%= number_to_currency(room.price) %></footer>
                            </div>
 
                        <%  end %>
                    <% end %>
                </div>
            </div>
        </div>
        <!-- レビュー -->
        <div class="card">
            <div class="card-header bg-warning">
            <%= current_user.full_name %>さんへのレビュー
            </div>
            <div class="card-body">
                <%= render "reviews/guest_list" %>
                <%= render "reviews/host_list" %>
            </div>
        </div>
        <br/>        
    </div>
</div>



「app\views\layouts\application.html.erb」ファイルを編集します。


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

    <% 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>民泊予約サイト</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <!-- Googleフォント -->
    <link href="https://fonts.googleapis.com/css2?family=Kosugi+Maru&display=swap" rel="stylesheet">
    <!-- アイコン Font Awesome -->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css">
    <!-- 日付ピッカー デザインsunny-->
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/vader/jquery-ui.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    <!-- 日付ピッカーの日本語化-->
    <script src="https://rawgit.com/jquery/jquery-ui/master/ui/i18n/datepicker-ja.js"></script>
    <!-- Bootstrap5.0.2使用のため -->
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
    </head>

  <body>

  <!-- ナビゲーションバー -->
  <%= render  "shared/navbar" %>
  
  <!-- noty -->
  <%= render 'shared/notification' %>

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

    <%= yield %>
  </body>
</html>



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


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

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



↓↓クリックして頂けると励みになります。


| 41 | メッセージと会話  <<  [ホーム]

YAE C5 CLINIC(札幌美容クリニック)

関連記事(外部サイト)