LoginSignup
4
1

More than 3 years have passed since last update.

railsのAction Cableでチャットアプリを作る

Last updated at Posted at 2019-08-07

作成したサンプルアプリ

環境

Ruby 2.6.3
Rails 5.2.3

プロジェクトの作成と下準備

Herokuへデプロイする前提なので、DBMSにPostgresqlを指定。
また、coffee Scriptを使いたく無いため、skipしてます。

$ rails new chat_app -d postgresql ---coffee

jqueryとBootstrap(バージョン4)をyarnを使って導入

$ yarn add jquery
$ yarn add bootstrap

マニフェストファイルの修正

/app/assets/javascripts/application.js
//= require rails-ujs
//= require jquery
//= require jquery/dist/jquery.js
//= require bootstrap/dist/js/bootstrap.min
//= require activestorage
//= require turbolinks
//= require_tree .
/app/assets/stylesheets/application.css
*= require bootstrap/dist/css/bootstrap.min
*= require_tree .
*= require_self
DBの作成
$ rails db:create
チャンネルの作成
$ rails g channel Room speak

最終的な各ファイルのコードと構成

model/music.rb
class Music < ApplicationRecord
  has_many :posts, dependent: :destroy
end
model/post.rb
class Post < ApplicationRecord
  belongs_to :music

  validates :content, presence: true

  def created_time
    created_at.strftime("%Y年%m月%d日 %-H時%-M分")
  end
end
controllers/musics_controller.rb
class MusicsController < ApplicationController
  def index
    @musics = Music.all
  end

  def new
    @music = Music.new
  end

  def create
    @music = Music.new(set_params)
    if @music.save!
      redirect_to musics_path
    else
      render 'new'
    end
  end

  def show
    @title = params[:title]
    @url = params[:url]

    @music = Music.find(params[:id])
    @posts = @music.posts.order(created_at: :desc).page(params[:page]).per(5)
    @post = @music.posts.build
    respond_to do |format|
      format.js{render :show} 
      format.html
    end    
  end

  def destroy
    @music = Music.find(params[:id])

  end

  private

  def set_params
    params.require(:music).permit(:title, :url, :image)
  end

end
views/musics/index.html.erb
<h1 class="text-center">どの<span>OMOIDE IN IN MY HEAD</span>について語る?</h1>

<div class="music-index">
  <div class="row">
    <% @musics.each do |music| %>
      <div class="col-sm-6 col-md-4 music-col">
        <%= link_to music_path(music, title: music.title, url: music.url) do %>
          <div>
            <%= image_tag(music.image) %>
          </div>
          <div>
            <%= music.title %>
          </div>
          <hr>
        <% end %>
      </div>
    <% end %>
  </div>
</div>
views/musics/new.html.erb
<h1 class="text-center">動画を登録</h1>

<div class="row justify-content-center">
  <div class="col-md-8 col-xs-8">
    <%= form_with(model: @music, local:true) do |f| %>
      <div class="form-group">
        <%= f.label :title, "タイトル" %>
        <%= f.text_field :title, class: "form-control" %>
      </div>
      <div class="form-group">
        <%= f.label :url, "動画のURL(embedの記載がある埋め込み用のURLを記載)" %>
        <%= f.text_field :url, class: "form-control" %>
      </div>
      <div class="form-group">
        <%= f.label :image, "画像のURL" %>
        <%= f.text_field :image, class: "form-control" %>
      </div>
      <div class="form-group text-center">
        <%= f.submit "登録", class: "btn btn-primary" %>
      </div>
    <% end %>
  </div>
</div>
views/musics/show.html.erb
<h1 class="text-center">この動画どう?</h1>

<div class="text-center">
  <iframe class="embed-responsive-item" id='iframe' width="560" height="315" 
    src="<%= @url %>"
    frameborder="0" 
    allowfullscreen>
  </iframe>
</div>

<form>
  <div class="form-group">
    <input type="text" class="form-control" id="chat-input" placeholder="何を思う?">
  </div>
  <div class="text-right">
    <button class="btn btn-primary btn-lg" id="chat-button">送信</button>
  </div>
</form>

<div hidden>
  <div id="music-id"><%= @music.id %></div>
</div>

<div id="posts" class="posts-block">
  <ul class="list-group">
    <%= render @posts %>
  </ul>
</div>

<div class="text-center loading-btn">
  <%= link_to_next_page @posts, 'もっと見る', remote: true, id: 'more_link' %>
</div>
views/musics/show.js.erb
<% @posts.each do |post| %>
  $("#posts").append("<%= escape_javascript(render partial: 'posts/post', locals: { post: post }) %>");
<% end %>
$("#more_link").replaceWith("<%= escape_javascript(link_to_next_page(@posts, 'もっと見る', remote: true, id: 'more_link')) %>");
views/posts/_post.html.erb
<% cache post do %>
  <li class="list-group-item">
    <h4><%= post.content %></h4>
    <div class="post-time">
      <%= post.created_time %>
    </div> 
  </li>
<% end %>
routes.rb
Rails.application.routes.draw do
  root to: 'musics#index'

  resources :musics do
    resources :posts
  end

  mount ActionCable.server => '/cable'
end
channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    post = Post.create!(content: data['content'], music_id: data['music_id'])
    template = ApplicationController.renderer.render(partial: 'posts/post', locals: {post: post})
    ActionCable.server.broadcast 'room_channel' ,template
  end
end
assets/javascripts/channels/room.js
App.room = App.cable.subscriptions.create("RoomChannel", {
  connected: function() {
  },

  disconnected: function() {
  },

  received: function(post) {
    const posts = document.getElementById('posts')
    posts.insertAdjacentHTML('afterbegin', post);
    // posts.innerHTML += post

  },

  speak: function(content, music_id) {
    return this.perform('speak', {content: content, music_id: music_id});
  }
});

document.addEventListener('turbolinks:load', function(){
  const input = document.getElementById('chat-input');
  const button = document.getElementById('chat-button');
  button.addEventListener('click', function(e){
    e.preventDefault();
    const content = input.value;
    const music = document.getElementById('music-id')
    const music_id = music.textContent;
    App.room.speak(content, music_id);
    input.value = '';
  })
});

Herokuデプロイ時の設定

producton.rb(JSでconstを使ったため)
# Compress JavaScripts and CSS.
config.assets.js_compressor = Uglifier.new(harmony: true)
config/environment/production.rb
 config.action_cable.allowed_request_origins = [ /http:\/\/.*/ ]
config/cable.yml
production:
  # adapter: redis
  # url: redis://localhost:6379/1
  # channel_prefix: ac_test_production
  adapter: async
$ heroku buildpacks:add --index 1 heroku/nodejs
$ heroku buildpacks:add --index 2 heroku/ruby

あとは、

$ heroku run rails db:migrate

また、validationの設定は、個々にModelで行う。

参考サイト

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1