I've been trying to get my head around action cable for what seems like months. Please help.
I have a "Connection" - I can't set the identified_by :current_user
because this endpoint also needs to be consumed by an external API that uses WebSockets. Can't use browser cookies to authenticate the API endpoint.
Connection: /app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
end
end
Channel: /app/channels/application_cable/channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
I have a specific Visits Channel: /app/channels/visits_channel.rb
class VisitChannel < ApplicationCable::Channel
def subscribed
stream_from "visit_#{params[:visit_id]}"
end
end
Then I have my coffeescript channel: /app/assets/javascripts/channels/visit.coffee
App.visit = App.cable.subscriptions.create { channel: 'VisitChannel', visit_id: '42' },
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) ->
console.log data
push: ->
@perform 'push'
Then I have a callback on my visit model: /app/models/visit.rb
class Visit < ApplicationRecord
after_save :push_to_action_cable
**** detail of model removed ****
def push_to_action_cable
ActionCable.server.broadcast("visit_#{self.id}", self)
end
end
This is working perfectly, it puts to the console the object every time and only that object with an ID of 42
Within the coffeescript channel: found at /app/assets/javascripts/channels/visit.coffee
- How do I set the visit_id
so that I can "listen" for the changes on only the visit I want?
App.visit = App.cable.subscriptions.create { channel: 'VisitChannel', visit_id: 'HOW_DO_I_SET_THIS?' },
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) ->
console.log data
push: ->
@perform 'push'
I have tried every variation of things like:
App.visit = App.cable.subscriptions.create { channel: 'VisitChannel', visit_id: <%= @visit.id %> }
results in:
ExecJS::RuntimeError in Visits#action_cable
Showing /Users/johnsalzarulo/code/uvohealth/app/views/layouts/application.html.erb where line #9 raised:
SyntaxError: [stdin]:1:81: unexpected <
and
App.visit = App.cable.subscriptions.create (channel: 'VisitChannel', visit_id: "#{ params[:id] }")
results in:
ExecJS::RuntimeError in Visits#action_cable
Showing /Users/johnsalzarulo/code/uvohealth/app/views/layouts/application.html.erb where line #9 raised:
SyntaxError: [stdin]:1:93: unexpected :
and
App.visit = App.cable.subscriptions.create (channel: 'VisitChannel', visit_id: "#{ @visit.id }")
results in:
visit.self-e04de4513d06884493c48f4065f94d23255be682f915e26766c54bb9d17ef305.js?body=1:4 Uncaught TypeError: Cannot read property 'id' of undefined
at visit.self-e04de4513d06884493c48f4065f94d23255be682f915e26766c54bb9d17ef305.js?body=1:4
at visit.self-e04de4513d06884493c48f4065f94d23255be682f915e26766c54bb9d17ef305.js?body=1:18
(anonymous) @ visit.self-e04de4513d06884493c48f4065f94d23255be682f915e26766c54bb9d17ef305.js?body=1:4
(anonymous) @ visit.self-e04de4513d06884493c48f4065f94d23255be682f915e26766c54bb9d17ef305.js?body=1:18
and
App.visit = App.cable.subscriptions.create (channel: 'VisitChannel', visit_id: "#{ visit.id }")
results in:
visit.self-b636f38376edc085c15c2cfc4d524bafc5c5163a8c136b80ba1dda12813fc0b5.js?body=1:4 Uncaught ReferenceError: visit is not defined
at visit.self-b636f38376edc085c15c2cfc4d524bafc5c5163a8c136b80ba1dda12813fc0b5.js?body=1:4
at visit.self-b636f38376edc085c15c2cfc4d524bafc5c5163a8c136b80ba1dda12813fc0b5.js?body=1:18
(anonymous) @ visit.self-b636f38376edc085c15c2cfc4d524bafc5c5163a8c136b80ba1dda12813fc0b5.js?body=1:4
(anonymous) @ visit.self-b636f38376edc085c15c2cfc4d524bafc5c5163a8c136b80ba1dda12813fc0b5.js?body=1:18
I have tried many many more combinations. The only thing that KIND of works was throwing a <script>
into the view template for that page that explicitly subscribed to the visit, but then I didn't get the benifit of the callbacks, plus I know this isn't the "rails way".
It's been hours of reading these docs and trying to make this work. Can anyone shed some light on what I'm missing here?
A few things to think about here:
That said - Here's the solution that worked for me:
All of the files used in the question asked above are unchanged except for what's below. To get this working you'll need to reference the files above and the files below for the full stack.
The main template for my app: app/views/layouts/application.html.erb
Pay attention to the line within the head tag yield(:head_attributes)
<!DOCTYPE html>
<html>
<head <%= yield(:head_attributes) %> >
<title>Uvo Health</title>
<%= action_cable_meta_tag %>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body <%= yield(:body_attributes) %> >
<%= render 'layouts/navbar' unless @hide_nav %>
<%= render 'shared/flash_messages' %>
<%= yield %>
<%= yield :page_js %>
</body>
</html>
The view for the page in which I am trying to use actioncable. In my case it's: app/views/visits/action_cable.html.erb
- Most times it will probably be your show.html.erb
or index.html.erb
Pay attention to the content_for
<%= content_for(:head_attributes) do %>data-visit-id="<%= @visit.id %>"<% end %>
<div class='container'>
<%= render 'visits/visit_overview' %>
</div>
Then in my visit channel /app/assets/javascripts/channels/visit.coffee
App.visit = App.cable.subscriptions.create { channel: 'VisitChannel', visit_id: document.querySelector('head').dataset.visitId },
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) ->
console.log data
push: ->
@perform 'push'
data-visit-id
to the head of the page. visit.coffee
reads this attribute from the head of the page. visit.coffee
can use this variable to subscribe to the appropriate channel. Other solutions wouldn't work because I was trying to access things in the wrong 'order' meaning, load a variable before it was instantiated. Hope this is helpful for others. This one stumped me for a solid 5 hours. 🙄
Set the visit_id
in a HTML tag, maybe in the body
tag in your layout file.
<body data-visit-id="<%= @visit.id %>">
Now read it from JS like this:
document.querySelector('body').dataset.visitId
Your subscription creation line would look like this:
App.visit = App.cable.subscriptions.create (channel: 'VisitChannel', visit_id: document.querySelector('body').dataset.visitId)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With