【Rails】条件に応じて日付の表示を判定する
LINEのトーク表示って何時何分とか、何曜日とか、古い物だと2020.xx.xxみたいな感じで表示されます
あの部分を作ります
条件に応じて日付の表示を判定する
今日のメッセージなら xx:xx の表示
昨日〜1週間前までは x曜日 の表示
それ以前は 2020.xx.xx の表示
という形にします
ここはviewにガッツリ書くことにしまいた(本当はあんまりよろしくないのだけど...)
p.latest-talk-show__data-time-notifiction
span#data-time
pとspanを用意
この時点でcreated_atの値を確認
created_at: Sat, 31 Oct 2020 18:33:23 JST +09:00 という値が取れました
コードに記述を加えて分岐させていきます
p.latest-talk-show__data-time-notifiction span#data-time / もしトークが今日の物ならば - if Date.today == talk.talk.last.created_at.to_date = talk.talk.last.created_at.strftime('%H:%M') / もしトークが過去1週間ならば - elsif 1.week.ago.beginning_of_day.to_date..Time.zone.now.end_of_day.to_date == talk.talk.last.created_at.to_date - weeks = ["日","月","火","水","木","金","土"] - num = talk.talk.last.created_at.strftime("%w").to_i - week = weeks[num] = week + "曜日" / それ以降であれば - else = talk.talk.last.created_at.strftime("%Y.%M.%D")
大分冗長です...すっきりさせたいけどそれは別の機会で...
少し読み解きます
=> Sat, 31 Oct 2020
== で今日の日付判定をcreated_atとする時、created_atにto_dateをつけましょう
何もつけないままだと時間の部分まで含めて判定するので今日の日付でもfalseになります
[3] pry(#<#<Class:0x00007f390891b390>>)> Date.today == talk.talk.last.created_at.to_date => true
trueが帰ってきました
= talk.talk.last.created_at.strftime('%H:%M')
created_atの中から時間と分を引っ張って表示させます
次に、今日と一致しない場合
- elsif 1.week.ago.beginning_of_day.to_date..Time.zone.now.end_of_day.to_date == talk.talk.last.created_at.to_date
結構長いです!
まず1.week.ago.beginning_of_day.to_date..Time.zone.now.end_of_day.to_dateの部分
1.week.agoで現在の時間から1週間前を呼ぶことができます
[7] pry(#<#<Class:0x00007f390891b390>>)> 1.week.ago => Sat, 24 Oct 2020 22:19:33 JST +09:00
さらにbegining_of_dayでその日の始まり(00:00)になります
[10] pry(#<#<Class:0x00007f390891b390>>)> 1.week.ago.beginning_of_day => Sat, 24 Oct 2020 00:00:00 JST +09:00
逆にTime.zone.nowで現在の時刻、.end_of_dayで終わり(23:59:59)を呼びます
今日の日付は最初のifで判定されるのでジャスト1週間前までのメッセージを呼べます
上記2つを .. で繋ぐことで1週間前のスタートから今日の終わりまでを指定します
ただやはり時間まではあると判定できないので、.to_dateをつけて日付のみにします
そしてそれを曜日に
- weeks = ["日","月","火","水","木","金","土"] - num = talk.talk.last.created_at.strftime("%w").to_i - week = weeks[num] = week + "曜日"
.strftime("%W")とすることで曜日を0~6の数字で取れます
[1] pry(#<#<Class:0x0000000002fd42e0>>)> talk.talk.last.created_at.strftime("%w") => "6"
つまり、weeksに一旦文字列として値を入れ、numには取得した値を.to_iで数字に
weekにweeks[num]で数字に対応した曜日を入れることで曜日を取得できます
最後の行は割とシンプルですね
= talk.talk.last.created_at.strftime("%Y.%M.%D")
これで判定を行うことができました
実際の画面はこんな感じで
なかなか大変!
次はJavaScriptも触っていきます
またボチボチ更新していきます
【Rails】groupメソッドとorderメソッドを一緒に使う
今回はSQLとのやりとりで沼った、groupとorderを一緒に使う時の注意をつらつらと書いていきます
現在の自身の環境です
- gem 'sqlite3'
あと、binding.pry大好き人間なので
- gem 'pry-byebug', group: :development
を導入
やりたいこと
ユーザーモデルがあり、トークルームモデルがあります
中間テーブルとして、ルームユーザー(どのルームにどのユーザーがいるか)と、トーク(メッセージがどのユーザーのものか)があります
今回やりたいことは、ラインのトークを押すと、最近トークが追加されたトークルーム順で並べる、というものです
実際のラインの表示
ちょっとわかり辛いですが、もしみられる方は自身の物を参考にしてもらえればと思います...
始めに行ったこと
最初に、まずTalkの全てを作成順 (.order(created_at: :desc))で並び替え、重複する talk_room_id でまとめる、ということをシンプルに記述してみました
latest_talk = Talk.order(created_at: :desc).group(:talk_room_id)
一見取得できているように見えたのですが、実はgroupメソッドの方が先に働いてしまうようです
動きとしては、
グループで同じtalk_room_idでまとめる(この時点で一番古いidでまとまる)
⇩
まとめたレコードで、新しい順に並べる
なので、最初に送ったトークを基準にルームを並べるという動きになってしまいます
全く意味がなくなってしまうので色々調べてみました
サブクエリ(from)を使う
fromを使うと、その中の記述が先に動いてくれるようです
実際に書いてみます
Talk.from(Talk.order(created_at: :asc)).group(:talk_room_id)
#<Talk::ActiveRecord_Relation:0x2bff408> という結果が帰ってきました
こいつはなんぞや、という話ですが、他の方の記事を流用させてもらうと
- データベースにクエリを発行する。
- 様々なデータベースとの互換性がある。
- データベースの種類にかかわらず同じ表記を使用できる。
ということらしいです
なるほど?
なので色々と追加してみます
latest_talk = Talk.from(Talk.order(created_at: :asc)) .group(:talk_room_id) .select(:talk_room_id) .order(created_at: :desc)
トークを並び替えてルームidでまとめた後、ルームidを指定しorderで並び替えます
これで一旦は、自分が求めていた「トークが追加されたルーム順で並べる」ということができました
ただこの状態ではただidを引っ張っただけでどうにもならないので、eachで回し配列にデータを追加します
# トークが新しい順にトークルームを並べる latest_talk = Talk.from(Talk.order(created_at: :asc)) .group(:talk_room_id) .select(:talk_room_id) .order(created_at: :desc) @latest_talks = Array.new latest_talk.each do |talk| room = TalkRoom.find_by(id: talk.talk_room_id) @latest_talks.push(room) end
変数に一旦新しく配列を持たせ、その中にidと一致するルームの情報を入れていきます
これでやっとview側でデータを使うことができます。やったね
もうちょっと簡潔にする方法があるとは思いますが、当初の目的は達成できました
まとめ
groupとorderを使うときのまとめです
- groupとorderを使うと、groupの方が優先される
- もし2つを使うのであれば、fromを使うことで解決出来る
- 取得したデータはActiveRecord::Relationという形になる
こんな感じですかね〜
2日ほど沼って解決できたので忘備録意味合いも込めて残しておきます
またボチボチと更新していきます
【RailsでLINEを作る】トーク内容の画面表示速度改善
友だちをクリックすると内容一覧を表示させるのですが、速度が恐ろしくかかる問題が発生しました
結論からすると
- N+1問題の改善
- eachの中のrender
この2つの影響で読み込み速度がかなり遅くなっていました
今回修正した部分をつらつらと書いていきます
トーク内容の画面表示速度改善
まず、何もない状態での速度です
表示するまでに1134msとなっています
1秒でも、体感としては長く感じます
N+1を解決します
ルームに紐づくトークテーブルは現状全て取得できていたのですが、そのトークのユーザーを表示させるために毎回SQLにアクセスしていました
each文を変更します
_user-talk-index.html.slim talks.includes(:user).each do |talk|
これでユーザーの情報も全て取得します
これでviewを読み込みます
533msと、約半分になりました
しかしトーク内容がどんどん増えると時間は伸びます
テストで150件ほどトークを送ってみました
1895msと、ほぼ2秒かかっています
ラインで友達からメッセージが来て開こうとするたびにこれだけ時間がかかるとユーザビリティが悪い...
部分テンプレートを呼びまくっているので、部分テンプレートをやめて直書きにします
= render partial: 'users/attachment', locals: { user: talk.user} .talk = talk.talk .time span = talk.created_at.strftime('%H:%M')
⇩
.user-talk-index__talks--left - if user.profile_image.attached? = image_tag(user.profile_image) - else = image_tag('usagi.jpg') .talk = talk.talk .time span = talk.created_at.strftime('%H:%M')
これで読み込み
198ms!!
each文の中になるべくrenderは書かないようにしようという今日の教訓でした
またボチボチ更新していきます
【RailsでLINEを作る】トークの画面をいい感じにしていく
前回の更新から少し空きました...
というのも逆無限スクロールを実装しようとしてそれが終わったら更新しようと思ったのですが、想像以上にややこしかった
考え方はわかるんだけど、それをコードにする実力がまだない!悔しい!
今回はトークの画面をラインに近づけます
トークの画面をいい感じにしていく
ラインのトークは新しいものが一番下になり、上に増えていきます
コードに起こしていきます
users/_user-talk-index.html.slim .user-talk-index - unless user.blank? .user-talk-index__name p = "#{user.name}" .user-talk-index__talks div id="talk" class="talk-#{@user.id}" data-user_id="#{current_user.id}" - unless talks.blank? - talks.each do |talk| - if talk.user_id == current_user.id .user-talk-index__talks--right .time span = talk.created_at.strftime('%H:%M') .talk = talk.talk - else .user-talk-index__talks--left = render partial: 'users/attachment', locals: { user: talk.user} .talk = talk.talk .time span = talk.created_at.strftime('%H:%M') / = paginate talks .user-talk-index__form textarea[type="text" id="talk-form" data-behavior="room_speaker" data-user="#{current_user.id}" data-room="#{room.id}" placeholder="メッセージを入力"] - else .non-talk-case .non-talk-case__contents img src="https://img.icons8.com/fluent-systems-filled/96/000000/line-me.png" span トークを始めよう! a href="https://icons8.com/icon/i7393ie24LoV/line" Line icon by Icons8
controllerからuserの情報(user)が渡ってこなければ(誰もクリックされていない初期状態)初期画面を表示
友だちの部分はこんな感じ
.talk-rooms__list--friends span.select-friends-list 友だち ul - current_user.followings.each do |follower| li class="follower" = link_to talk_rooms_path(follower: follower.id), class: "user-talk-show", remote: true do = render partial: 'users/attachment', locals: { user: follower} p = follower.name
link_toでindexを呼び、remote: trueでindex.js.erb呼び出し
params[:follower]にidを持たせます
talk_rooms/index.js.erb $('#talks').html("<%= j(render partial: 'users/user-talk-index', locals: { user: @user, room: @room, talks: @talks}) %>"); $(document).ready(function(){ let $talk = $('.user-talk-index__talks') // トーク内容の表示を一番下からに $talk.scrollTop(10000); });
ここはちょっと力技なんですが...部分テンプレートが呼ばれたらトーク一覧部分を一番下まで下げます (ホントは要素の高さを取って一番下を取得したい)
コントローラの中も少し変えました
talk_rooms_controller.rb def index unless params[:follower].blank? @user = User.find_by(id: params[:follower]) @current_room = RoomUser.where(user_id: current_user.id) @another_room = RoomUser.where(user_id: @user.id) unless @user.id == current_user.id @current_room.each do |cr| @another_room.each do |ar| # ルームが存在する時 if cr.talk_room_id == ar.talk_room_id @is_room = true @room = TalkRoom.find_by(id: cr.talk_room_id) @talks = @room.talk end end end # なければ新規作成 unless @is_room @room = TalkRoom.create RoomUser.create(user_id: current_user.id, talk_room_id: @room.id) RoomUser.create(user_id: params[:follower].to_i, talk_room_id: @room.id) end end end @users = User.where.not(id: current_user.id) end
params[:follower]を判定
あれば中間モデルの判定
ルームがあればトークを表示
なければルームと中間モデルのルームユーザーを作成します
これで基本的なことはできたかと思います
次に、ActionCableに変更を加えてトークが送られた時の判定を作っていきます
まずはviewのフォーム
talk_rooms/_user-talk-index.html.slim .user-talk-index__form textarea[type="text" id="talk-form" data-behavior="room_speaker" data-user="#{current_user.id}" data-room="#{room.id}" placeholder="メッセージを入力"]
js側に自身のidとルームのidを送ります
textareaに入力された時の動きは以前作成したものと大差はありませんので割愛... (下で全部載せます)
送られたデータをチャンネルで処理します
channnels/talk_room_channnel.rb 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_#{params['room_id']}", talk: talk['talk'], user_id: talk['user_id'],created_at: talk[:created_at].strftime("%H:%M") end
talkをセーブしたあと、データをjs側に返します
トーク内容、ユーザーid、作成日時です
talk[:created_at].strftime("%H:%M") これは何時何分に送ったかのデータです
js側で、viewに表示させるための処理を行います
javascripts/channels/talk_room.coffee $ -> talkForm = $('#talk-form') App.talk_room = App.cable.subscriptions.create { channel: "TalkRoomChannel", room_id: talkForm.data('room')}, # 通信が確立された時 connected: -> # 通信が切断された時 disconnected: -> # 値を受け取った時 received: (data) -> # サーバーサイドから値を受け取りviewに追加する if data['user_id'] == $('#talk').data('user_id') $('#talk').append('<div class="user-talk-index__talks--right">'+ '<div class="time">'+'<span>'+data["created_at"]+'</span>'+'</div>'+ '<div class="talk">'+ data["talk"]+'</div>'+ '</div>') else $('#talk').append('<div class="user-talk-index__talks--left">'+ '<div class="talk">'+ data["talk"]+'</div>'+ '<div class="time">'+'<span>'+data["created_at"]+'</span>'+'</div>'+ '</div>') scroll = $('.user-talk-index__talks'); scroll.scrollTop(10000); speak: (talk) -> # サーバーサイド(channel)のspeakアクションにtalkパラメータを渡す @perform 'speak', talk: talk $(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()
未熟の者で...appendで直書きする方法しか思いつきませんでした(汗)
view側には#="talk"の部分に自分のidをuser_idとしてあるので、ここでトークのidが自分と同じかどうか判定します
自身のjsに送られてきたトークのuser_idが自分と同じなら右に表示させるclass
異なるのであれば左に表示させるclassとします
そしてここも力技なんですが、高さを無理やり下にしています
実際の画面はこんな感じになります
チャットだけなら大分ラインっぽくなってきた!
ここから画像を添付したり動画を添付したり通話機能つけたりそれらを非同期で行い...ラインって無限に作り込めるな????
そろそろ就職しないと、と思う今日この頃です
またボチボチ更新していきます
【RailsでLINEを作る】インクリメンタルサーチ実装
前回のトークを非同期で書き換えする機能と同じタイミングでインクリメンタルサーチも実装したのでまた書いていきます
インクリメンタルサーチは、検索フォームに入力されたワードで瞬時に検索し表示させる...であってるのかな?そういう機能です
どんな感じになるかの動画
内容は
- 一文字入力する毎に、検索結果を表示
- 検索フォームが空なら友だち一覧を、文字があれば検索された友達を表示
- 検索結果がなければ、その旨を表示
- 検索された友だちをクリックでトーク画面を表示
これを作っていきます
流れとしては
検索フォームを実装 ↓ フォームにワードを入力でjsが動く ↓ 入力されたワードをajax通信でコントローラへ ↓ 受け取ったデータを元に友だちを検索 ↓ データをjsに戻し、そのデータを元にviewを書き換える
それでは作っていきます
参考にした記事
Rails + jQueryでインクリメンタルサーチ(基礎)
インクリメンタルサーチ
まずは検索フォーム。部分テンプレートになっています
talk_rooms/index.html.slim .talk-rooms.tab-content.is-show .talk-rooms__search = render partial: "layouts/search"
layouts/_search.heml.slim i(name="search-icon" id="search-icon" class="fas fa-search") =form_tag('/talk_rooms/search', method: :get) do =text_field_tag :name, :'', id: 'searching-form', placeholder: "名前で検索", autocomplate: 'off' br
iの部分はフォントオーサムです
このフォームをターゲットにjsを動かしていきます
javascripts/talk_rooms.js // 名前で検索機能を作る $(document).ready(function(){ const inputForm = $('#searching-form'); const url = location.href; const searchResult = $('.result'); function builtHTML(data){ if(data.profile_image === null){ let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}"> <img src="${data.profile_image}"> <p>${data.name}</p> ` searchResult.append(html); }else{ let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}"> <img src="/assets/usagi.jpg"> <p>${data.name}</p> ` searchResult.append(html); } } function NoResult(message){ let html = `<li>${message}</li>` searchResult.append(html); } // フォームに入力でイベント発火 inputForm.on('keyup', function(e){ e.preventDefault(); let target = $(this).val(); if(target !== ''){ search(target); // ajax通信はsearch()という関数に $('.talk-rooms-content').addClass("talk-rooms-content-close"); }else{ searchResult.empty(); $('.talk-rooms-content').removeClass("talk-rooms-content-close"); } }); // ajax処理 function search(target){ $.ajax({ type: 'GET', url: 'talkrooms/search', data: {keyword: target}, dataType: 'json' }) .done(function(data){ searchResult.empty(); // 再度検索した際に前のデータを消す処理 if (data.length !== 0){ data.forEach(function(data){ // dataは配列型に格納されているのでeachぶんで回す builtHTML(data) }); }else{ NoResult('検索結果がありません') } }) .fail(function(data){ alert('通信に失敗しました') }) } });
結構長くなってしまいました
検索フォームはこの部分
// フォームに入力でイベント発火 inputForm.on('keyup', function(e){ e.preventDefault(); let target = $(this).val(); if(target !== ''){ search(target); // ajax通信はsearch()という関数に $('.talk-rooms-content').addClass("talk-rooms-content-close"); }else{ searchResult.empty(); $('.talk-rooms-content').removeClass("talk-rooms-content-close"); } });
キーが入力される度に発火させています
targetにはフォームの文字を代入
targetが空でなければ、後のsearch()を起動
かつ、友だち一覧の部分にクラスを付けて、cssで非表示にするようにしてあります
起動するのはこの部分
// ajax処理 function search(target){ $.ajax({ type: 'GET', url: 'talkrooms/search', data: {keyword: target}, dataType: 'json' })
トークルームコントローラのサーチアクションにデータが渡ります
ルーティングを書いておきましょう
routes.rb #検索機能 get 'talkrooms/search', to: 'talk_rooms#search'
ajaxの部分で、'GET'と 'talkrooms/search' があることでコントローラが動きます
次にサーチアクションを記述
talk_rooms_controller.rb def search @users = current_user.followers.where('name LIKE(?)', "%#{params[:keyword]}%") respond_to do |format| format.html #htmlがないとエラーが出る format.json end end
ajax部分のdata: {keyword: target}, がパラメータとして使えるようになっています
次に、views/talk_roomsにsearch.json.jbuilderというファイルを作ります
まだ理解が浅いですが、コントローラから渡された値をjson形式で出力するものらしいです
@usersに代入された友だちをjson形式に変えていきます
@usersには複数データが入るのでeachで回します
talk_rooms/search.json.jbuilder json.array! @users do |user| json.user user json.name user.name json.id user.id json.profile_image user.profile_image end
この左側がkey、右側がvalueになります
通信が行われた場合のajaxの処理がこの部分です
function search(target){ $.ajax({ type: 'GET', url: 'talkrooms/search', data: {keyword: target}, dataType: 'json' }) .done(function(data){ searchResult.empty(); // 再度検索した際に前のデータを消す処理 if (data.length !== 0){ data.forEach(function(data){ // dataは配列型に格納されているのでeachぶんで回す builtHTML(data) }); }else{ NoResult('検索結果がありません') } }) .fail(function(data){ alert('通信に失敗しました') }) }
.doneは通信が成功した時、.failが失敗した時です
json形式にしたデータは(data)に入ります
そのdataを元にhtmlを作成し検索結果をjsで書き換えていきます
function builtHTML(data){ if(data.profile_image === null){ let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}"> <img src="${data.profile_image}"> <p>${data.name}</p> ` searchResult.append(html); }else{ let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}"> <img src="/assets/usagi.jpg"> <p>${data.name}</p> ` searchResult.append(html); } } function NoResult(message){ let html = `<li>${message}</li>` searchResult.append(html); }
あんまり冗長なのでもっといい方法がないか模索中ですがとりあえず上記の形に
ユーザーのプロフィールイメージがある時とないときで表示の方法を変えています
書き換わるindexのviewはこんな感じで
talk_rooms/index.html.slim .talk-room-index__talk-rooms .talk-rooms.tab-content.is-show .talk-rooms__search = render partial: "layouts/search" .result .talk-rooms-content .talk-rooms__my-information .talk-rooms__my-information--image = render partial: "users/attachment", locals: { user: current_user} .talk-rooms__my-information--name p = current_user.name .talk-rooms__list .talk-rooms__list--groups span.select-group-list グループ ul .talk-rooms__list--friends span.select-friends-list 友だち ul - current_user.followings.each do |follower| li class="follower" = link_to talk_rooms_path(follower: follower.id), class: "user-talk-show", remote: true do = render partial: 'users/attachment', locals: { user: follower} p = follower.name
フォームに文字が入力された時点で .result にその内容を付け加え、.talk-rooms-content はjsとcssで非表示にします
stylesheets/talk_rooms.scss .talk-rooms-content-close{ display: none; }
こんな感じで実装できました。
あとはCSSを触ったり、データの受け渡しをしてなんとかLINE風に左右でトークを表示したり、トークをしたから上に表示させたりとやっていこうと思います。
またボチボチ更新していきます。
【RailsでLINEを作る】クリックでトークを表示させる
今日は恐ろしくやる気が出ず、ブログの更新だけになります...
昨日、一昨日で友だちをクリックしたときにトーク内容をJSで更新させるようにしました。
CSSをあまりいじってないので雰囲気だけ...
流れとして
友だち一覧を取得しeachで表示 ↓ 友だち1つ1つにパラメータとしてIDを持たせる ↓ link_toを非同期対応させて、同じページを呼び出す ↓ パラメータを元にコントローラの動きを分ける ↓ 部分テンプレートにしてあるトークを書き換える
まずindexのviewに部分テンプレートを記述します
talk_rooms/index.html.slim .talk-room-index__talk .user-talks #talks.user-talks__talk = render partial: "users/user-talk-index", locals: { user: @user, room: @room, talks: @talks}
id="talks"をターゲットにするのでidはつけておきます
次に、each文で表示する友だちにパラメターを持たせます
talk_rooms/index.html.slim .talk-rooms__list--friends span.select-friends-list 友だち ul - current_user.followings.each do |follower| li class="follower" = link_to talk_rooms_path(follower: follower.id), class: "user-talk-show", remote: true do = render partial: 'users/attachment', locals: { user: follower} p = follower.name
link_toのパスの後に(follower: follower.id)と書くことで、リンク先にfollowerのidをparams[:follower]として渡すことができます
パスは同じviewなのでtalk_tooms_pathにしてあります
次にコントローラー
talk_rooms_controller.rb def index unless params[:follower].blank? @user = User.find_by(id: params[:follower]) @current_room = RoomUser.where(user_id: current_user.id) @another_room = RoomUser.where(user_id: @user.id) unless @user.id == current_user.id @current_room.each do |cr| @another_room.each do |ar| # ルームが存在する時 if cr.talk_room_id == ar.talk_room_id @is_room = true @room = TalkRoom.find_by(id: cr.talk_room_id) @talks = @room.talk end end end # なければ新規作成 unless @is_room @room = TalkRoom.new @room_user = RoomUser.new end end end @users = User.where.not(id: current_user.id) end
普通にindexを表示した時点ではパラメータがないので@users = User.where.not(id: current_user.id)だけを渡します
友だちがクリックされた時に初めて unless params[:follower].blank?を実行し変数をviewに渡します
そして書き換えるjsファイルを作成
talk_rooms/index.js.erb $('#talks').html("<%= j(render partial: 'users/user-talk-index', locals: { user: @user, room: @room, talks: @talks}) %>");
部分テンプレートの中身を作成
users/_user-talk-index.html.slim .user-talk-index - unless user.blank? .user-talk-index__name p = "#{user.name}" .user-talk-index__talks #talk - talks.each do |talk| p = talk.talk .user-talk-index__form input[type="text" id="talk-form" data-behavior="room_speaker" data-user="#{current_user.id}" data-room="#{room.id}" placeholder="メッセージを入力"] - else .non-talk-case p トークをはじめよう!
-unless user.blank?でクリックされてパラメータが渡される前は、elseの部分を表示させるようにしてあります
今回かなり沼ったのは素直にlink_toにパラメータを持たせず、dataを使おうとして混乱してしまった所です
シンプルに部分テンプレートで書き換えるのが間違いなさそうです
次回はインクリメンタルサーチを実装した記事を書いていきます。
【RailsでLINEを作る】ActionCableで特定のユーザーとリアルタイムチャット機能を作る.その2
前回はアクションケーブルを使ったリアルタイムチャット機能を実装しました。
ただ、今の状態だと、送ったチャットが、同じチャンネルに接続されている全てのviewに配信されます。
ユーザーidやルームidは保存されるのでリロードを挟めば消えるのですが、かといってダダ漏れでは大変です。
こんな感じです。
これをちゃんと同じルームの相手にだけリアルタイムで配信するようにします。
前回のコードに追記していきます。
特定のユーザーとリアルタイムチャット機能
今回こちらのかたの記事を参考にさせて頂き実装しました。
Rails 5 Action Cable メッセージとルームを紐付ける。
チャットを送るinputタグにはユーザーidとトークルームidをつけてあります。
この部分ですね。
talk_rooms/show.html.slim input[type="text" data-behavior="room_speaker" data-user="#{current_user.id}" data-room="#{@room.id}"]
このinputにidを持たせます。
talk_rooms/show.html.slim input[type="text" id="talk-form" data-behavior="room_speaker" data-user="#{current_user.id}" data-room="#{@room.id}"]
この値はjs(coffee)に渡され、さらにチャンネルまで渡します。
javascript/channels/talk_room.coffee $ -> talkForm = $('#talk-form') App.talk_room = App.cable.subscriptions.create { channel: "TalkRoomChannel", room_id: talkForm.data('room')}, # 通信が確立された時 connected: -> # 通信が切断された時 disconnected: -> # 値を受け取った時 received: (data) -> # サーバーサイドから値を受け取りviewに追加する $('#talk').append("<p>"+data["talk"]+"</p>"); speak: (talk) -> # サーバーサイド(channel)のspeakアクションにtalkパラメータを渡す @perform 'speak', talk: talk $(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()
少し沼ったのですが、coffeeはインデントで閉じる判定をしている?のか、 App.talk_room = App... の部分をインデント空けずに記述したら変数を渡せていませんでした。インデント注意。
これでchannelにroom_idを流せるので、channelに記述していきます。
channels/talk_room_channnel.rb class TalkRoomChannel < ApplicationCable::Channel # 接続された時 def subscribed # フロントとバックが通信している時(お互いを監視している時)に実行される stream_from "talk_room_channel_#{params['room_id']}" 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_#{params['room_id']}", talk: talk['talk'] end end
talk_room_channelを動的に区別できるようになりました。
これでブラウザを立ち上げてチャットをしてみます。
ちょっと見づらいですが同じルームだけチャットが届くようになりました。
これで一旦action cableを使ったリアルタイムチャット機能が完成しました。
もっと色々と応用がありそうだ・・・jobっていうのはまだ分かっておらずです。
これをLINEの見た目にしていきたいけど今はちょっと頭がそこまで回りそうにないぞ...!
この記事が何かのお役に立てれば幸いです。
またボチボチ更新していきます。