【RailsでLINEを作る】ActionCableで特定のユーザーとリアルタイムチャット機能を作る
ルーム機能を作ったのでいよいよ作っていきます。
色々な記事を参考にして、大量に記事を未過ぎてどの記事を参考にしたかもうわからない程になりました...
つらつらと掲載させていただきます。他にもaction cableと書いてあれば見るみたいな感じでやりました。
- ActionCableを使って簡易チャット機能を作ろう
- ActionCableを用いてリアルタイムチャットの実装
- Rails 5 Action Cable メッセージとルームを紐付ける。
- Action Cableでリアルタイムチャットアプリの作成方法 (Rails 5.1.4にて)(その1) herokuで動かす!
Action cableを実装する
自分の使用しているのはrails 5 です。
なんかrails 6 だと所々違うっぽい。
前回ルーム機能の時にモデルの記述をブログで書いてなかったです。throughを使います
前準備
user.rb # ユーザーは沢山のトークルームを持つ。それは他のユーザーを通してである。 has_many :talk_room, dependent: :destroy, through: :room_users
talk_room.rb has_many :users, through: :room_users
トークルームに遷移する前に、相手方のユーザーidをパラメータに持たせます。
users/show.html.slim p = @user.name - unless @user.id == current_user.id - if @is_room == true = link_to "チャット", talk_room_path(@room_id ,user_id: @user.id) - else = form_with model: @room, url: talk_rooms_path, local: true do |f| = fields_for @room_user do |ru| = ru.hidden_field :user_id, value: @user.id = f.submit 'チャット'
talk_room_controllerのshowを記述
talk_room_controller def show @user = User.find(params[:user_id]) @room = TalkRoom.find(params[:id]) @talks = @room.talks end
viewの記述
talk_rooms/show.html.slim .talk-room-show h2 = "#{@user.name} さんとのトーク" .talk-room-show__form input type="text" data-behavior="room_speaker" .talk-room-show__talks #talk - @talks.each do |talk| p = talk.talk
Action cable の導入
チャネルを作成します。
$ rails g channel talk_room speak
これをターミナルで入力すると、
この2つのファイルができるかと思います。
channelはサーバー側の処理、coffeeはフロント側の処理を行います。
この2つを行き来してリアルタイムの動きがされます。
jQueryのメソッドをcoffee内で使うため、予めjQueryを導入しておきましょう。
次に、coffeeを書いていきます。
javascripts/channels/talk_room.coffee App.talk_room = App.cable.subscriptions.create "TalkRoomChannel", # 通信が確立された時 connected: -> console.log("connect"); # 通信が切断された時 disconnected: -> # 値を受け取った時 received: (data) -> speak: (talk) -> # サーバーサイド(channel)のspeakアクションにtalkパラメータを渡す @perform 'speak',talk: talk jQuery(document).on 'keypress', '[data-behavior~=room_speaker]', (event) -> # return(Enter)が押された時 if event.keyCode is 13 # channel speakへ, event.target.valueを引数に App.talk_room.speak event.target.value event.target.value = '' alert() event.preventDefault()
connected: で connect と表示するようにしています。接続が確立されていれば、ブラウザの検証ツールで、consoleにconnectと出ます。
jQueryで、inputに対するアクションを記述。
今の状態で、return(Enter)キーを押し、アラートが出れば正しく動いています。
次にchannelを記述していき、js(coffee)から送られてきたデータを保存し送り返します。
channels/talk_room_channel.rb class TalkRoomChannel < ApplicationCable::Channel # 接続された時 def subscribed # フロントとバックが通信している時(お互いを監視している時)に実行される stream_from "talk_room_channel" end # 切断された時 def unsubscribed end def speak(talk) # Talkモデル内、talkカラムに、(talk)に渡されたvalue['文字列']が保存される Talk.create(talk: talk['talk']) end end
Talkモデルにレコードを保存するために、user_idとtalk_room_idを引っ張ります。
showページのinputにパラメータを持たせます。ここではdataメソッドを使います。
talk_rooms/show.html.slim .talk-room-show h2 = "#{@user.name} さんとのトーク" .talk-room-show__form input[type="text" data-behavior="room_speaker" data-user="#{current_user.id}" data-room="#{@room.id}"] .talk-room-show__talks #talk - @talks.each do |talk| p = talk.talk
この data をcoffeeの方で受け取り、配列として channel のアクションに渡します。
javascripts/channels/talk_room.coffee App.talk_room = App.cable.subscriptions.create "TalkRoomChannel", # 通信が確立された時 connected: -> # 通信が切断された時 disconnected: -> # 値を受け取った時 received: (data) -> speak: (talk) -> # サーバーサイド(channel)のspeakアクションにtalkパラメータを渡す @perform 'speak', talk: talk jQuery(document).on 'keypress', '[data-behavior~=room_speaker]', (event) -> # return(Enter)が押された時 if event.keyCode is 13 # channel speakへ, event.target.valueを引数に App.talk_room.speak [event.target.value, $('[data-user]').attr('data-user'), $('[data-room]').attr('data-room')] event.target.value = '' event.preventDefault()
js(coffee)から送られてきたデータを、channel内のspeakアクションに渡し、それを元にTalkを保存します。
channels/talk_room_channel.rb class TalkRoomChannel < ApplicationCable::Channel # 接続された時 def subscribed # フロントとバックが通信している時(お互いを監視している時)に実行される stream_from "talk_room_channel" end # 切断された時 def unsubscribed end def speak(talk) # Talkモデル内、talkカラムに、(talk)に渡されたvalue['文字列']が保存される talk = Talk.new(talk: talk['talk'][0], user_id: talk['talk'][1].to_i, talk_room_id: talk['talk'][2].to_i) talk.save # 同じchannel名の全てにインプットタグに入力された文字を配信する ActionCable.server.broadcast 'talk_room_channel', talk: talk['talk'][0] end end
ここで一度、speakアクションのすぐ下にbinding.pryをつけて値を見てみます。
talk内に、内容とユーザーid、トークルームidが入っていることが確認できました。
これらをTalk.newで、それぞれのカラムに入れていくことができます。
他の方の記事を参考にしながら、ホェーーそんなことができるか...すげぇ...と思わず声に出しながらコードを書いていたのを思い出します...
話が逸れました。
ActionCable.server.broadcast 'talk_room_channel', talk: talk['talk'][0]で、channelに接続しているcoffeeにtalkの内容が帰ります。
coffeeで受け取った内容を、viewのidをターゲットにして、pタグで追加します。
javascripts/channels/talk_room.coffee App.talk_room = App.cable.subscriptions.create "TalkRoomChannel", # 通信が確立された時 connected: -> # 通信が切断された時 disconnected: -> # 値を受け取った時 received: (data) -> # サーバーサイドから値を受け取りviewに追加する $('#talk').append("<p>"+data["talk"]+"</p>"); speak: (talk) -> # サーバーサイド(channel)のspeakアクションにtalkパラメータを渡す @perform 'speak', talk: talk jQuery(document).on 'keypress', '[data-behavior~=room_speaker]', (event) -> # return(Enter)が押された時 if event.keyCode is 13 # channel speakへ, event.target.valueを引数に App.talk_room.speak [event.target.value, $('[data-user]').attr('data-user'), $('[data-room]').attr('data-room')] event.target.value = '' event.preventDefault()
もし自分の記述に矛盾がなければ、これで一旦 TalkRoomChannel に接続されているviewに対しては、リアルタイムのチャット機能を付けれるようになっているはずです。
実際の動きはこちらです。
タブを複製して、複数ブラウザで試してみても全てのブラウザで入力された内容が反映されているはずです。
ただし、今のままだと他の人とのトークルームにも、自分の入力した内容が反映されてしまいます。(TalkRoomというチャネルが1つしかないため)
次回はこのTalkRoomチャネルとTalkRoomのidを紐付けて、他のユーザーのルームに内容を反映しないようにしていきます。
ここまで書くのすごく大変だった...こんなに書くのは初めてです。
はてなブログなのでコードが白黒でみづらい点もあるかと思いますが。同じような初学者の方に参考になれば幸いです。
またボチボチ更新していきます。