---
title: Astro課程 0821 - Rails (Day10)
tags: astro, rails
---
# Action Mailer
```
https://guides.rubyonrails.org/action_mailer_basics.html
```
寄信
把信寄到對方手中很複雜(複雜的部分在於:郵差是否有正確寄到位置)
## SMTP Relay
是指要不要讓別人在外部機器使用你的伺服器幫他送信,
可能會讓不肖使用者拿伺服器來做亂發廣告信的工具
## 當新增文章完成後,寄信到作者的信箱
```
rails g mailer post
Running via Spring preloader in process 61297
create app/mailers/post_mailer.rb
invoke erb
create app/views/post_mailer
invoke test_unit
create test/mailers/post_mailer_test.rb
create test/mailers/previews/post_mailer_preview.rb
```
## 把YML修掉figaro config bug
把變數改為字串
```
WARNING: Use strings for Figaro configuration. 5 was converted to "5".
WARNING: Use strings for Figaro configuration. 20 was converted to "20".
```
```
braintree_merchant_id: 'wpn3n5j7428qnfqp'
braintree_public_key: 'ghcyt9pn2dj5pd5z'
braintree_private_key: '97befac871f6f46225cab9ea7ce492cd'
plan_a_price: "5"
plan_b_price: "20"
```
## 觀察`application_mailer.rb`
信件標題
```
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
end
```
## 觀察`/layouts/mailer.html.erb`
信件樣板
```
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<%= yield %>
</body>
</html>
```
## 定義`poster`方法
```
class PostMailer < ApplicationMailer
def poster
# 這是一個大hash
mail to: 'tingtinghsu@gmail.com', subject: 'test'
end
end
```
## 新增文章後,寄信
```
def create
@post = @board.posts.new(post_params)
if @post.save
# 寄信
# UserMailer.with(user: @user).welcome_email.deliver_later
PostMailer.poster.deliver_later
redirect_to @board, notice: '文章新增成功'
else
render :new
end
end
```
## Action Mailer有自己定義的params
手冊:2.4 Mailer Views
```
class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
def welcome_email
@user = params[:user]
@url = 'http://example.com/login'
mail(to: @user.email,
subject: 'Welcome to My Awesome Site',
template_path: 'notifications',
template_name: 'another')
end
end
```
按照手冊設定的話:

換成實體變數,view就可以把實體變數拿來用

## view下面`poster.html.erb`
```
hello!
<%= @post.content %>
```
把layout改成背景色
`layouts/mailer.html.erb`
```
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
body {
background-color: red;
}
</style>
</head>
<body>
<%= yield %>
</body>/Users/tingtinghsu/Documents/projects/eddie-file/PPT0817/app/views/pages/pricing.html.erb
</html>
```
## 不用gmail,使用其他方案解決mail server的問題
`mailchimp`
> Mailchimp是美國的營銷自動化平台和電子郵件營銷服務。該平台是其運營商Rocket Science Group的商標名,Rocket Science Group是一家由Ben Chestnut和Mark Armstrong於2001年成立的美國公司,稍後Dan Kurzius加盟
`mailgun`
> https://www.mailgun.com/

```
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
domain: 'example.com',
user_name: '<username>',
password: '<password>',
authentication: 'plain',
enable_starttls_auto: true }
```
對照mailgun的configue變數
```
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.mailgun.org',
port: 587,
domain: '5xruby.tw',
user_name: ENV["mailgun_user_name"],
password: ENV["mailgun_user_password"],
authentication: 'plain',
enable_starttls_auto: true
}
end
```
```
11:39:54 yoyo.1 | [ActiveJob] [ActionMailer::MailDeliveryJob] [5282e305-1148-4c78-9f8b-e886d334b4ab] Date: Fri, 21 Aug 2020 11:39:52 +0800
11:39:54 yoyo.1 | From: from@example.com
11:39:54 yoyo.1 | To: yourmail@gmail.com
11:39:54 yoyo.1 | Message-ID: <5f3f4208207eb_f75d3fe1f7638a3075971@MacBook-Pro.local.mail>
11:39:54 yoyo.1 | Subject: =?UTF-8?Q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0=EF=BC=9A=E6=88=91=E6=98=AF=E8=B2=93=E8=B2=93=E8=B2=93?=
11:39:54 yoyo.1 | Mime-Version: 1.0
11:39:54 yoyo.1 | Content-Type: text/html;
11:39:54 yoyo.1 | charset=UTF-8
11:39:54 yoyo.1 | Content-Transfer-Encoding: 7bit
11:39:54 yoyo.1 |
11:39:54 yoyo.1 | <!DOCTYPE html>
11:39:54 yoyo.1 | <html>
11:39:54 yoyo.1 | <head>
11:39:54 yoyo.1 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
11:39:54 yoyo.1 | <style>
11:39:54 yoyo.1 | body {
11:39:54 yoyo.1 | background-color: red;
11:39:54 yoyo.1 | }
11:39:54 yoyo.1 | </style>
11:39:54 yoyo.1 | </head>
11:39:54 yoyo.1 |
11:39:54 yoyo.1 | <body>
11:39:54 yoyo.1 | hello!
11:39:54 yoyo.1 |
11:39:54 yoyo.1 | mail gun
11:39:54 yoyo.1 | </body>
11:39:54 yoyo.1 | </html>
11:39:54 yoyo.1 |
11:39:54 yoyo.1 | [ActiveJob] [ActionMailer::MailDeliveryJob] [5282e305-1148-4c78-9f8b-e886d334b4ab] Performed ActionMailer::MailDeliveryJob (Job ID: 5282e305-1148-4c78-9f8b-e886d334b4ab) from Async(mailers) in 2544.9ms
```

## letter Opener
```
file:///Users/tingtinghsu/Documents/projects/eddie-file/PPT0817/tmp/letter_opener/1597982884_0915241_e7a5dc0/rich.html
```
http://localhost:3001/letter_opener

# Active Job
## 寄出去後會畫面會轉個5秒才寄出,把它延遲加入排程
先確認rails guide
https://guides.rubyonrails.org/active_job_basics.html
```
~/Documents/projects/eddie-file/PPT0817 master rails g job sendmail
Running via Spring preloader in process 63535
invoke test_unit
create test/jobs/sendmail_job_test.rb
create app/jobs/sendmail_job.rb
```
`/jobs/sendmail_job.rb`
```
class SendmailJob < ApplicationJob
queue_as :default
# 一顆星:陣列;兩顆星:hash
def perform(*args)
# Do something later
end
end
```
```
$ rails generate job guests_cleanup --queue urgent
```
明明看起來是實體方法,但是用起來是類別方法
```
class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
# Do something later
end
end
```
把`寄信`這件事,抽到`jobs`來做

posts_controller.rb
```ruby
def create
@post = @board.posts.new(post_params)
if @post.save
# 寄信
#UserMailer.with(user: @user).welcome_email.deliver_later
#PostMailer.with(post: @post).poster.deliver_later
# 待會才寄信
#SendmailJob.perform_later(@post)
# 等十秒才寄信
SendmailJob.set(wait: 10.seconds).perform_later(@post)
redirect_to @board, notice: '文章新增成功'
else
render :new
end
end
```
`sendmail_job.rb`
```
class SendmailJob < ApplicationJob
queue_as :default
def perform(post)
puts "------------------------------"
puts "寄信囉!!"
puts "------------------------------"
PostMailer.with(post: post).poster.deliver_now
end
end
```
新增文章後,10秒鐘後寄一封信
```
12:20:22 yoyo.1 | Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 9], ["LIMIT", 1]]
12:20:22 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] Performing SendmailJob (Job ID: 9aa7f9ab-96fa-42db-9e43-2eb1b47a434d) from Async(default) enqueued at 2020-08-21T04:20:12Z with arguments: #<GlobalID:0x00007ff101d08c30 @uri=#<URI::GID gid://ppt/Post/9>>
12:20:22 yoyo.1 | ------------------------------
12:20:22 yoyo.1 | 寄信囉!!
12:20:22 yoyo.1 | ------------------------------
12:20:23 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
12:20:23 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] ↳ app/mailers/post_mailer.rb:4:in `poster'
12:20:23 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] Rendering post_mailer/poster.html.erb within layouts/mailer
12:20:23 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] Rendered post_mailer/poster.html.erb within layouts/mailer (Duration: 0.9ms | Allocations: 102)
12:20:23 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] PostMailer#poster: processed outbound mail in 8.5ms
12:20:27 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] Delivered mail 5f3f4b87d406_1015f3ff87fece3a4636ab@MacBook-Pro.local.mail (4159.8ms)
12:20:27 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] Date: Fri, 21 Aug 2020 12:20:23 +0800
12:20:27 yoyo.1 | From: from@example.com
12:20:27 yoyo.1 | To: tingtinghsu.tw@gmail.com
12:20:27 yoyo.1 | Message-ID: <5f3f4b87d406_1015f3ff87fece3a4636ab@MacBook-Pro.local.mail>
12:20:27 yoyo.1 | Subject: =?UTF-8?Q?=E6=96=B0=E5=A2=9E=E6=96=87=E7=AB=A0=EF=BC=9A=E9=87=8D=E5=AF=84=E4=B8=80=E5=B0=81?=
12:20:27 yoyo.1 | Mime-Version: 1.0
12:20:27 yoyo.1 | Content-Type: text/html;
12:20:27 yoyo.1 | charset=UTF-8
12:20:27 yoyo.1 | Content-Transfer-Encoding: quoted-printable
12:20:27 yoyo.1 |
12:20:27 yoyo.1 | <!DOCTYPE html>=0D
12:20:27 yoyo.1 | <html>=0D
12:20:27 yoyo.1 | <head>=0D
12:20:27 yoyo.1 | <meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf=
12:20:27 yoyo.1 | -8" />=0D
12:20:27 yoyo.1 | <style>=0D
12:20:27 yoyo.1 | body {=0D
12:20:27 yoyo.1 | background-color: red;=0D
12:20:27 yoyo.1 | }=0D
12:20:27 yoyo.1 | </style>=0D
12:20:27 yoyo.1 | </head>=0D
12:20:27 yoyo.1 | =0D
12:20:27 yoyo.1 | <body>=0D
12:20:27 yoyo.1 | hello!=0D
12:20:27 yoyo.1 | =0D
12:20:27 yoyo.1 | 10=E7=A7=92=0D
12:20:27 yoyo.1 | </body>=0D
12:20:27 yoyo.1 | </html>=0D
12:20:27 yoyo.1 |
12:20:27 yoyo.1 | [ActiveJob] [SendmailJob] [9aa7f9ab-96fa-42db-9e43-2eb1b47a434d] Performed SendmailJob (Job ID: 9aa7f9ab-96fa-42db-9e43-2eb1b47a434d) from Async(default) in 4217.21ms
```
## delayed job gem: 把工作存放在資料表
這樣暫存在記憶體的工作就不會不見
```[](https://)
https://github.com/collectiveidea/delayed_job
```
```
gem 'delayed_job_active_record'
```
其他常見的工作排程gem [Ref](https://rails-hosting.com/2020/)

# Action Storage
檔案上傳功能有很多種解決方案
paperclip
carrierwave
[[Ting's筆記Day6] 活用套件carrierwave gem: (1)在Rails實現圖片上傳功能](https://ithelp.ithome.com.tw/articles/10199035)
```
https://github.com/carrierwaveuploader/carrierwave
```
來使用Action Storage
多對多model
(缺點:容易產生n+1問題)
```
https://guides.rubyonrails.org/active_storage_overview.html
```
```
rails active_storage:install
Copied migration 20200821055430_create_active_storage_tables.active_storage.rb from active_storage
rails db:migrate == 20200821055430 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {})
-> 0.0044s
-- create_table(:active_storage_attachments, {})
-> 0.0039s
== 20200821055430 CreateActiveStorageTables: migrated (0.0085s) ===============
```
參考手冊 3. Attaching Files to Records
```
class User < ApplicationRecord
has_one_attached :avatar
end
```
model
在post.rb增加我們要的虛擬欄位
```
has_one_attached :photo
```
修改view
```
<div class="fields">
<%= form.label :photo, "附件" %>
<%= form.file_field :photo %>
</div>
```

記得在controller permit photo欄位
```
def post_params
params.require(:post)
.permit(:title, :content, :photo)
.merge(user: current_user)
end
```
show.html.erb
上傳好了後找地方放照片
```
<p>
<%= @post.content %>
<%= @post.photo %>
</p>
```
結果顯示:
```
上傳 #<ActiveStorage::Attached::One:0x00007fb3b0f0fcb0>
```
## `image helper`
把剛剛的
```
<%= @post.photo %>
```
改為
`if @post.photo.attached? `有圖片才顯示(不然沒有傳檔案的貼文會壞)
```
<%= image_tag @post.photo if @post.photo.attached? %>
#硬改顯示的大小,但原圖size不變
<%= image_tag @post.photo, width: 200 if @post.photo.attached? %>
```

## 圖片被放在哪裡?

剛剛的migrate做了兩張表,`attachement`屬於`blob`
```
create_table :active_storage_blobs do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [ :key ], unique: true
create_table :active_storage_attachments do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false
t.references :blob, null: false
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
```
這兩個表的關聯可能是一對一或一對多
```
class User < ApplicationRecord
has_one_attached :avatar, service: :s3
end
```
如果要多圖上傳的話
```
class Message < ApplicationRecord
has_many_attached :images
end
```
## 把圖片壓縮 `variant`
gemfile
```
# Use Active Storage variant
gem 'image_processing', '~> 1.2'
```
## 包裝器
包裝器像是轉接頭一樣
`image_processing`只是包裝器,後面還用到其他套件
`imagemagick` 、 `mini_magick`
```
pg gem -> 是PostgreSQL的包裝器
sqlite gem -> 是SQLite的包裝器
```
carrierwave 可以在上傳的時候就把照片壓成三張。
[[Ting's筆記Day9] 活用套件Carrierwave gem: (4) 使用Imagemagick修改圖片大小](https://ithelp.ithome.com.tw/articles/10199131)
## 安裝`imagemagick`
```
# (mac) 這個指令會跑的有點久
brew install imagemagick
# (wsl) 這個指令會跑的更久
sudo apt install imagemagick (大部分的電腦)
sudo apt-get install imagemagick
sudo apt-get update
```
> apt-get 通常是對某些套件進行操作,,可能是安裝或移除等等行為。[Ref](https://b9532026.wordpress.com/2010/03/30/apt-get-%E6%8C%87%E4%BB%A4%E4%B8%80%E8%A6%BD-2/)
```
<%= image_tag @post.photo.variant(resize_to_limit: [300, 300]) if @post.photo.attached? %>
# 圖片框框設定300*300,等比例壓縮,長或寬任一邊先撞到邊框就停止壓縮
```
如果是很大張的圖片,就會變成`resize`後的大小

## 第三方登入
```
# google
https://github.com/zquestz/omniauth-google-oauth2
# FB
https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview
```
# Action Text
`所見即所得 WYSIWYG`編輯器也有很多選擇:
[CKEditor](https://ckeditor.com/)
[Redactor](https://imperavi.com/redactor)
action text 整合[Trix編輯器](https://trix-editor.org/)
```
https://guides.rubyonrails.org/action_text_overview.html
https://trix-editor.org/
https://github.com/basecamp/trix
```
```
rails action_text:install
// application.js
require("trix")
require("@rails/actiontext")
```
post.rb model
```
has_rich_text :hello
```
posts/new.html.erb
```
<div class="fields">
<%= form.label :hello, "編輯器" %>
<%= form.rich_text_area :hello %>
</div>
```
記得要讓post controller可以允許這個欄位通過
```
def post_params
params.require(:post)
.permit(:title, :content, :photo, :hello)
.merge(user: current_user)
end
```
輸出畫面也要設計
posts/show.html.erb
```
<br>
<%= @post.hello %>
<br>
```
檢視`html原始碼`多了一個`trix-content`tag

## 讓text出現 markdown語法
```
https://github.com/vmg/redcarpet
```
board_controller
```
def show
@posts = @board.posts.includes(:user)
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
@intro = markdown.render(@board.intro)
end
```
board show.html.erb
```
# 不要做任何跳脫
<div>
<%= raw @intro %>
# 或是.html_safe
<%= @intro.html_safe %>
</div>
```
## 語法高亮
[pygments](https://github.com/tmm1/pygments.rb)
[作者](https://github.com/tmm1?language=objective-c&tab=stars)
## 把script標籤移掉
[How to sanitize data (remove html tags) before saving a record?
](https://stackoverflow.com/questions/6954258/how-to-sanitize-data-remove-html-tags-before-saving-a-record)
## 拖拉效果
```
https://github.com/SortableJS/Sortable
https://github.com/SortableJS/Vue.Draggable
```
# Action Cable
Action Cable實作`即時更新`的方式,會把WebSocket包起來
WebSocket: 一直處於連線狀態(eg: 抓keydown事件)
> WebSocket是一種網路傳輸協定,可在單個TCP連接上進行全雙工通訊,位於OSI模型的應用層。WebSocket協定在2011年由IETF標準化為RFC 6455,後由RFC 7936補充規範。Web IDL中的WebSocket API由W3C標準化。
```
https://guides.rubyonrails.org/action_cable_overview.html
```
以投票網的`即時更新`為例

```
https://datastore.thenewslens.com/infographic/kao-vote-0815/kao-vote-live/live-format-data/intro-data/detail_data.json?12845
```
```
Access to fetch at 'https://google.com/' from origin 'chrome-search://local-ntp' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```
前端拉不回來,可以用ruby拉
## 連線設定 Connection Setup

連線的時候如何找到user?
```
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
```
把手冊換掉成自己設定的session
```
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
private
def find_verified_user
if verified_user = User.find_by(id: session[:user_token])
verified_user
else
reject_unauthorized_connection
end
end
end
end
```
## 建立頻道 channel
手冊
```
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
end
# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
end
```
## Client-Side Components
連接consumer
```
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
```
在`font-end`建立一個自己的chat_channel
```
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
# 也可以建立很多房間
// app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
```
## 回到server side
chat_channel.rb
```
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
```
Eg. 手冊裡的這段
```
# 針對某篇文章做互動留言
class CommentsChannel < ApplicationCable::Channel
def subscribed
post = Post.find(params[:id])
stream_for post
end
end
```
## Subscriptions
參考手冊
```javascript
// app/javascript/channels/chat_channel.js
// Assumes you've already requested the right to send web notifications
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
// 收到data
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
```
把received(data)這個function加到自己的`chat_channel.js`
```javascript
import consumer from "./consumer"
consumer.subscriptions.create(
{
channel: "ChatChannel",
room: "Best Room"
},
{
received(data) {
console.log(data);
}
}
)
// consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
// consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
```
# Reboardcasting
收到什麼就廣播出去
```
class ChatChannel < ApplicationCable::Channel
def subscribed
puts "----------------------------"
puts "hi"
puts "----------------------------"
stream_from "chat_#{params[:room]}"
end
# 為了檢查sever有沒有收到訊息
def receive(data)
puts "----------------------------"
puts data
puts "----------------------------"
ActionCable.server.broadcast("chat_#{params[:room]}", data)
end
end
```

```
16:48:14 yoyo.1 | ChatChannel is transmitting the subscription confirmation
16:48:14 yoyo.1 | ChatChannel is streaming from chat_Room1
16:48:14 yoyo.1 | ChatChannel#receive({"sent_by"=>"Paul", "body"=>"This is a cool chat app."})
16:48:14 yoyo.1 | ----------------------------
16:48:14 yoyo.1 | {"sent_by"=>"Paul", "body"=>"This is a cool chat app."}
16:48:14 yoyo.1 | ----------------------------
16:48:14 yoyo.1 | [ActionCable] Broadcasting to chat_Room1: {"sent_by"=>"Paul", "body"=>"This is a cool chat app."}
16:48:14 yoyo.1 | ChatChannel transmitting {"sent_by"=>"Paul", "body"=>"This is a cool chat app."} (via streamed from chat_Room1)
16:48:14 yoyo.1 | Started GET "/cable" for ::1 at 2020-08-21 16:48:14 +0800
16:48:14 yoyo.1 | Started GET "/cable/" [WebSocket] for ::1 at 2020-08-21 16:48:14 +0800
16:48:14 yoyo.1 | Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
16:48:14 yoyo.1 | ChatChannel is transmitting the subscription confirmation
16:48:14 yoyo.1 | ChatChannel is streaming from chat_Room1
16:48:14 yoyo.1 | ChatChannel#receive({"sent_by"=>"Paul", "body"=>"This is a cool chat app."})
16:48:14 yoyo.1 | ----------------------------
16:48:14 yoyo.1 | {"sent_by"=>"Paul", "body"=>"This is a cool chat app."}
16:48:14 yoyo.1 | ----------------------------
16:48:14 yoyo.1 | [ActionCable] Broadcasting to chat_Room1: {"sent_by"=>"Paul", "body"=>"This is a cool chat app."}
16:48:14 yoyo.1 | ChatChannel transmitting {"sent_by"=>"Paul", "body"=>"This is a cool chat app."} (via streamed from chat_Room1)
16:48:14 yoyo.1 | ChatChannel transmitting {"sent_by"=>"Paul", "body"=>"This is a cool chat app."} (via streamed from chat_Room1)
```
不同的瀏覽器都會收到同個廣播
