#初めに
Ajaxとか勉強していたらActionCableという自分以外の人にもリアルタイムで投稿を表示することができる機能があることを知ったので勉強してみた。
正直中身に関してはいまいちよくわかってないのでいろいろ間違っているかもしれませんがその時は教えてください…
#とりあえず
とりあえずrails newしてmodelとcontrollerをつくる
rails new action_dm
rails g model user name:string
rails g model room name:string
rails g model comment body:text user_id:integer room_id:integer
rails db:migrate
rails g controller users index
rails g controller rooms show
rails g controller sessions
rails g controller comments
gem "jquery-rails'
bundle install
してそのあと
assets/javascripts/application.jsに
//= require jquery
を
//= require rails-ujs
の上に書く
これでOK
userは名前だけ持っていてログインも名前で行う。
roomはダイレクトメッセージを行うところですべてのユーザー間にroomが作られるようにする
コメントを表示する際に名前も表示したいのでcommentにuser_idをいれた
#モデルにアソシエーションを書いとく
belongs_to :room
has_many :comments
has_many :comments
userとroomはたくさんのcommentを持つのでhas_manyした
commentはとりあえずルームに紐づけていればよい
#User作成できるようにする
userを作成してログインできるようにcontrollerとviewsをいじっていく
まずusers_controller
class UsersController < ApplicationController
before_action :set_current_user
def index
@user = User.new
@rooms = Room.all
@users = User.all
end
def create
@user = User.create(user_params)
if @user.save
redirect_to users_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name)
end
end
indexには@user,@rooms,@usersがあってuser作成やroomへのリンクuser一覧、ログインすべてindex.html.erbで行うため必要。
before_actionのset_current_userはapplication_controller.rbに書いてある
application_controller.rbはこんな感じ
class ApplicationController < ActionController::Base
def set_current_user
if session[:user_id]
@current_user ||= User.find session[:user_id]
end
end
helper_method :set_current_user
end
@current_userがあれば@current_userを代入してなければUser.find session[:user_id]で見つかったuserを@current_userに代入する。
次にviewをつくる
index.html.erbはこんな感じ
<p>ユーザー一覧</p>
<% @users.each do |user| %>
<%= user.name %>
<% end %>
<p>現在のアカ</p>
<%= @current_user&.name %>
<p>アカ作成</p>
<%= form_with model: @user, local: true do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
<p>ログイン</p>
<%= form_with url: sessions_new_path do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
<p>ログアウト</p>
<%= link_to "ログアウト", "sessions/destroy", method: :delete %>
<p>DM</p>
<% @users.each do |user| %>
<% unless user.id == session[:user_id] %>
<%= link_to "#{user.name}", "/rooms/create/#{user.id}", method: :post %>
<% end %>
<% end %>
現在のアカに関しては@current_userがnilの時に@current_user.nameがエラーを出すので&.を使っています。これを使うと@current_userがnilでもエラーが出ない
cssに
p {
border-bottom: 1px solid gray;
}
と書いておく
とりあえずこれでユーザーを作成しログインすることができるようになった
#roomを作る
このroomがDMを行う場所
まずrooms/show.html.erbを
<h1>DM</h1>
<h2>comments</h2>
<div id="comments">
<%= render @room.comments %>
</div>
<%= render 'comments/new', room: @room %>
この<%= render @room.comments %>に関していまいちよくわかってないが
<% @room.comments.each do |comment| %>
<%= render 'comments/comment', comment: comment %>
<% end %>
の省略した書き方?だと思う
comments/_comment.html.erbは
<p><%= username(comment.user_id) %>:<%= comment.body %> -- <%= comment.created_at.to_s(:long) %></p>
コメントを作成するための _new.html.erbは
<%= form_for([ @room, Comment.new ], remote: true) do |form| %>
Your comment:<br>
<%= form.text_field :body, size: '50x20' %>
<%= form.submit %>
<% end %>
次にrooms_controller.rb
class RoomsController < ApplicationController
def show
protect_room(params[:id])
@room = Room.find params[:id]
end
def create
@r = find_room(params[:id])
if @r
redirect_to "/rooms/#{@r.id}}"
else
@room = Room.new(name: "room@#{params[:id]}@#{session[:user_id]}")
@room.save
redirect_to "/rooms/#{@room.id}"
end
end
private
def protect_room(room_id)
@room = Room.find room_id
id = @room.name.split("@")
unless id.find { |i| i.to_i == session[:user_id].to_i }
redirect_to users_path
end
end
def find_room(id)
@rooma = Room.find_by(name: "room@#{id}@#{session[:user_id]}")
@roomb = Room.find_by(name: "room@#{session[:user_id]}@#{id}")
@r = @rooma || @roomb
return @r
end
end
まずroomは最初から作成されているわけではなくメッセージを送るためroomに入ろうとしたときに作成されるようにした。
例えばAとBがいた場合にroomがない状態でAがBにメッセージを送るためroomに入ろうとするとroomが"room@(Bのid)@(Aのid)"という形でroomが作られる。
BがAにメッセージを送ろうとしたときにまたroomが作成されないようにするためにfind_roomという確認メソッドを作った
def find_room(id)
@rooma = Room.find_by(name: "room@#{id}@#{session[:user_id]}")
@roomb = Room.find_by(name: "room@#{session[:user_id]}@#{id}")
@r = @rooma || @roomb
return @r
end
roomの名前
というのを作った
お互いの間にroomがない場合はnilを返す感じ
roomに他人を入れないようにするために
protect_room(room_id)メソッドを作った
def protect_room(room_id)
@room = Room.find room_id
id = @room.name.split("@")
unless id.find { |i| i.to_i == session[:user_id].to_i }
redirect_to users_path
end
end
名前にお互いのidが記入されているのでそれを取得してsession[:user_id:と比較している
ここまででuser作成とroom作成はおわり
#comment作成
action cableを使うのはこのcommentでcommentを作成したときに自動的にお互いに反映されるようにする
まずchannelを作る
rails g channel comments
このあと編集するのは二つのファイルで一つがサーバー用でもう一つがクライアントサイド用(多分..)
/assets/javascript/comments.coffeeがクライアントサイド用で
こんな感じにする
App.comments = App.cable.subscriptions.create "CommentsChannel",
connected: ->
# Called when the subscription is ready for use on the server
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
$('#comments').append data.comment
# Called when there's incoming data on the websocket for this channel
receivedでデータを受け取ったときに$('#comments').append data.commentが行われる。
サーバーサイドのファイルが/channels/comments_channel.rbにあり
class CommentsChannel < ApplicationCable::Channel
def self.broadcast(comment)
broadcast_to comment.room, comment: CommentsController.render(partial: 'comments/comment', locals: { comment: comment })
end
def subscribed
Room.all.each do |room|
stream_for room
end
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
subscribedでaction cableを使うものを指定している。
なので例えばstream_for Room.last とするそれだけaction cableを使える
stream_forとstream_fromの違いとしてはモデルに関連するストリームを作成する場合はstream_forでそうではない場合はstream_fromだそう
stream_forの場合CommentsChannel.broadcast_to(@room,@comment)と書けばブロードキャスト?できるそう
今回の場合はcomments#createでCommentsChannel.broadcast(comment)が行われている
さいごにrouteはこんな感じ
Rails.application.routes.draw do
resources :users
resources :rooms, only: [:show] do
resources :comments
end
post 'sessions/new'
delete 'sessions/destroy'
post "rooms/create/:id", to: "rooms#create"
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
みたいな感じで使える。
#おわりに
正直なんで動いでいるのか全く分らない..
なにか間違っているところがあればご指摘いただけるとありがたいです。
だれもいらないだろうけどGitHubも上げておきます。
https://github.com/Sibakeny/action_dm
#参考文献
Rails Tour
https://www.youtube.com/watch?v=OaDhY_y8WTo&t=1089s
RailsGuides
https://railsguides.jp/action_cable_overview.html