作成したサンプルアプリ
環境
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で行う。