【Rails】リアルタイムDM機能作成

#開発環境
`rails` 5.1.7
`ruby`  2.4.0
devise導入済

#作成する
```Gemfile
gem 'jquery-rails'
```

```js:app/assets/javascripts/application.js
...
//
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery ←追加
//= require jquery_ujs ←追加
//= require_tree .
...
```

```terminal
$ bundle
```

## モデル作成
roomモデル、entryモデル、direct_messageモデルを作成

```terminal
$ rails g model room name:string
$ rails g model entry user:references room:references
$ rails g model DirectMessage user:references  room:references name:string
$ rails db:migrate
```

##モデル編集
```ruby:app/models/user.rb
...
has_many :entries
has_many :direct_messages
has_many :room, through: :entries
...
```


```ruby:app/models/room.rb
...
has_many :entries
has_many :direct_messages
has_many :users, through: :entries
...
```

### コントローラ作成
```terminal
$ rails g controllers Rooms show
```

### ルーティングの設定
```ruby:config/routes.rb
root 'users#index'
devise_for :users
resources :users
resources :rooms
```
これで`rails s`してサーバーをたちあげ、http://localhost:3000
 へアクセスしてみる。
成功したらおk

### Usersコントローラを編集
```ruby:app/controllers/users_controller.rb
class UsersController < ApplicationController
    before_action :authenticate_user!
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
    @currentUserEntry = Entry.where(user_id: current_user.id)
    unless @user.id == current_user.id
      @currentUserEntry.each do |cu|
        @userEntry.each do |u|
          if cu.room_id == u.room_id then
            @isRoom = true
            @roomId = cu.room_id
          end
        end
      end
 
      unless @isRoom
        @room  = Room.new
        @entry = Entry.new
      end
     end
    end
   end
```


### users/index/html.erbの作成
```erb:app/views/users/index.html.erb
<h3>ユーザー一覧</h3>
<h2><%= @user.name %></h2>

<% unless @user.id == current_user.id %>
  <% if @isRoom == true %>
    <%= link_to "チャットをする", room_path(@roomId) %>
  <% else %>
    <%= form_for @room do |f| %>
      <%= fields_for @entry do |e| %>
        <%= e.hidden_field :user_id, :value => @user.id %>
      <% end %>
      <%= f.submit 'チャットを始める' %>
    <% end %>
  <% end %>
<% end %>
```

### Roomsコントローラ編集
```ruby:app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
    before_action :authenticate_user!
  def show
    @room = Room.find(params[:id])
    # present?の戻り値はboolean, よってtrueの場合、
    if Entry.where(:user_id => current_user.id, :room_id => @room.id).present?
      @direct_messages = @room.direct_messages
      @entries = @room.entries
    else
      redirect_back(fallback_location: root_path)
    end
  end

  def create
    @room   = Room.create(:name => "DM")
    @entry1 = Entry.create(:room_id => @room.id, :user_id => current_user.id)
    @entry2 = Entry.create(params.require(:entry).permit(:user.id, :room_id).merge
    redirect_to room_path(@room_id) 
  end
end


### rooms/show.html.erbの作成
erb:app/views/rooms/show.html.erb
<h1><%= @room.name %></h1>


<h4>参加者</h4>
<% @entries.each do |e| %>
  <%= link_to e.user.name, user_path(e.user_id) %>
  <br />
<% end %>

<h1>Chat room</h1>
<form>
  <label> Say somethig:</label><br />
  <input type="text" id="chat-input" data-behavior="room_speaker>
</form>

<div id="direct_messages" data-room_id="<%= @room.id %>">
  <%= render @direct_messages %>
</div>
```

#### 📝memo
`<%= render @direct_messages %>`とは
`<% render partial: 'direct_messages/direct_message', collection: @direct_messages %>`の略
この記述をすると、部分テンプレートで繰り返し処理ができる


### direct_messageを表示する
```erb:app/views/direct_messages/_direct_message.html.erb
<%= direct_message.content %> <br />

### roomチャンネルの作成
speakメソッドを持っているroomチャンネルを作成

```terminal
$ rails g channel room speak
```

```coffee:app/assets/javascripts/channels/room.coffee
document.addEventListener 'turbolinks:load', ->
  App.room = App.cable.subscriptions.create {channel: "RoomChannel", room: $('#direct_messages').data('room_id')},
    connected: ->

    disconnected: ->

    received: (data) ->
      $('#direct_messages').append data['direct_message']
    
    speak: (direct_message) ->
      @perform 'speak', direct_message: direct_message
  
  $('#chat-input').on 'keypress',(event) ->
    if event.ketcode is 13 #enter
      App.room.speak event.target.value
      event.target.value = ''
      event.preventDefault()
```

```ruby:app/channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel_#{params['room']}"
  end

  def unsubscribed

  end


  def speak(data)
    DirectMessage.create! message: data['direct_message'],user_id: current_user.id, room_id: params['room']
  end
end
```

### jobの作成
```terminal
$ rails g job DirectMessageBroadcast
```

### job編集
class DirectMessageBroadcastJob < ApplicationJob
  queue_as :default
  
  def perform(direct_message)
    ActionCable.server.broadcast "room_channel_#{direct_message.room_id}", direct_message: render_direct_message(direct_message)
  end


  private
    def render_direct_message(direct_message)
      ApplicationController.renderer.render partial: 'direct_messages/direct_message', locals:{ direct_message: direct_message}
    end
end


```ruby:app/models/direct_message.rb
...
after_create_commit{DirectMessageBroadcastJob.perform_later self}
```

### current_userの情報を取得する
```ruby:app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection:Base
    identified_by :current_user
    
    def connect
      self.current_user = find_verified_user
    end
    
    protected
    def find_verified_user
      verified_user = User.find_by(id: env['warden'].user.id)
      return reject_unauthorized_connection unless verified_user
      verified_user
    end
  end
end

###  終わり
誤字、間違いありましたらご連絡いただけばと思います