I'm making a Chat App using ActionCable and websocket.
But I cannot get room_id in asset/javascripts/channels/rooms.coffee
I want to get it like messages.data('room-id')
could you tell me the reason?
asset/javascripts/channels/rooms.coffee
jQuery(document).on 'turbolinks:load', ->
messages = $('#messages')
console.log(messages.data('room-id'))
if $('#messages').length > 0
App.room = App.cable.subscriptions.create {
channel: "RoomChannel"
room_id: messages.data('room-id')
},
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) ->
$('#messages').append data['message']
speak: (message, room_id) ->
#perform 'speak', message: message, room_id: room_id
$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
if event.keyCode is 13 # return/enter = send
App.room.speak event.target.value
event.target.value = ''
event.preventDefault()
/view/rooms/show.html
<h1><%= #room.title %></h1>
<div id="messages", data-room-id="<%= #room.id %>">
<%= render #room.messages %>
</div>
<form>
<label>Say something:</label><br>
<input type="text" data-behavior="room_speaker">
<hidden> fa</hidden>
</form>
/channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_#{params[:room_id]}_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak(data)
current_user.messages.create!(content: data['message'], room_id: data['room-id'])
end
end
I have wrong space in /view/rooms/show.html
<div id="messages", data-room-id="<%= #room.id %>">
<div id="messages", data-room-id="<%= #room.id %>">
this is correct.
Related
I'm using this ActionCable demo as a guideline for setting up user messaging, but I'm changing it so that there can be multiple private rooms between users. The message persists to the db through the speak method like so [ActionCable] [me] MessagesChannel#speak({"body"=>"eee", "conversation_id"=>1}) and then calls a background worker with an after_create_commit. The worker executes, but the broadcast is not broadcasting. What prevents that broadcast from executing?
2020-05-10T04:31:32.216Z pid=32378 tid=ouzv4m5h2 class=MessageBroadcastWorker jid=e8b23ada55bede39b811e770 INFO: start
2020-05-10T04:31:32.499Z pid=32378 tid=ouzv4m5h2 class=MessageBroadcastWorker jid=e8b23ada55bede39b811e770 elapsed=0.283 INFO: done
messages.coffee
if ($("meta[name='current-user']").length > 0)
App.messages = App.cable.subscriptions.create "MessagesChannel",
connected: ->
console.log 'Connected'
disconnected: ->
console.log 'Disconnected'
received: (data) ->
console.log 'Received'
speak: (body, conversation_id) ->
#perform 'speak', body: body, conversation_id: conversation_id
$(document).on 'turbolinks:load', ->
submit_message()
scroll_bottom()
submit_message = () ->
$('#response').on 'keydown', (event) ->
if event.keyCode is 13
conversation_id = $("#messages").data("conversation-id")
App.messages.speak(event.target.value, conversation_id)
event.target.value = ""
event.preventDefault()
scroll_bottom = () ->
if $('#messages').length > 0
$('#messages').scrollTop($('#messages')[0].scrollHeight)
messages_channel.rb
class MessagesChannel < ApplicationCable::Channel
def subscribed
#i have to change this so that the conversation id is a part of the stream but i'll do that later
stream_from "messages_channel"
end
def unsubscribed
stop_all_streams
end
def speak(data)
Message.create! body: data['body'], conversation_id: data['conversation_id'], user_id: current_user.id
end
end
message.rb
class Message < ApplicationRecord
after_create_commit { MessageBroadcastWorker.perform_async self.id }
end
message_broadcast_worker.rb
class MessageBroadcastWorker
include Sidekiq::Worker
def perform(message_id)
message = Message.find message_id
ActionCable.server.broadcast("messages_channel", message: render_message(message))
end
private
def render_message(message)
ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message, username: message.user.username })
end
end
views/messages/index.html.erb
<div id="messages" data-conversation-id="<%= #conversation.id %>">
<%= render #messages %>
</div>
<%= form_for [#conversation, #message], remote: true do |f| %>
<%= f.label :type_a_message %>
<%= f.text_area :body, placeholder: "Say something.", autofocus: true, id:"response" %>
<% end %>
views/messages/_message.html.erb
<% cache message do %>
<div class="message">
<div><strong><%= message.user.username %>:</strong> <%= message.body %></div>
<div class="date"><%= local_time(message.created_at) %></div>
</div>
<% end %>
i had two lines commented out in my config/cable.yml:
development:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
adding those back in brought my app to life
I've seen a lot of tutorials do something like this with the subscribed method:
class MessagesChannel < ApplicationCable::Channel
def subscribed
conversation = Conversation.find(params[:id])
stream_from "conversation_#{conversation.id}"
end
end
The idea being to allow for multiple conversations between multiple users. But I'm not clear on how that id param is sent to the method. If my routes are nested so that the conversation id is in the url, it seems like it should work.
resources :conversations, only: [:index, :create] do
resources :messages, only: [:index, :create]
end
However, the channel code above gives this error:
[ActionCable] [user] Registered connection (Z2lkOi8vZnJhY3Rpb25jbHViL1VzZXIvMQ)
[ActionCable] [user] Could not execute command from ({"command"=>"subscribe", "identifier"=>"{\"channel\":\"MessagesChannel\"}"}) [ActiveRecord::RecordNotFound - Couldn't find Conversation without an ID]: /Users/user/.rvm/gems/ruby-2.5.0/gems/activerecord-5.2.0/lib/active_record/relation/finder_methods.rb:431:in `find_with_ids' | /Users/user/.rvm/gems/ruby-2.5.0/gems/activerecord-5.2.0/lib/active_record/relation/finder_methods.rb:69:in `find' | /Users/user/.rvm/gems/ruby-2.5.0/gems/activerecord-5.2.0/lib/active_record/querying.rb:5:in `find' | /Users/user/.rvm/gems/ruby-2.5.0/gems/activerecord-5.2.0/lib/active_record/core.rb:167:in `find' | /Users/user/code/project/app/channels/messages_channel.rb:3:in `subscribed'
How would I pass the conversation id to the subscribed method so that my users can have multiple private conversations?
Update 1: This is my messages.coffee file
App.messages = App.cable.subscriptions.create
channel: "MessagesChannel"
conversation_id: 1
connected: ->
console.log 'Connected'
disconnected: ->
console.log 'Disconnected'
received: (data) ->
console.log 'Received'
$('#messages').append(data.message)
speak: (message, conversation_id) ->
#perform 'speak', message: message, conversation_id: conversation_id
$(document).on 'turbolinks:load', ->
submit_message()
scroll_bottom()
submit_message = () ->
$('#response').on 'keydown', (event) ->
if event.keyCode is 13
App.messages.speak(event.target.value)
event.target.value = ""
event.preventDefault()
scroll_bottom = () ->
$('#messages').scrollTop($('#messages')[0].scrollHeight)
This is how I am tackling this,
In my view form:
<%= form_for Message.new,remote: true,html: {id: 'new-message',multipart: true} do |f| %>
<%= f.hidden_field :chat_id, value: chat.id,id: 'chat-id' %>
....
Notice the id I have given to form and then chat_id field.
Then in my chat.js:
return App.chat = App.cable.subscriptions.create({
channel: "ChatChannel",
chat_id: $('#new-message').find('#chat-id').val()
}
Now I can use this chat_id param in my ChatChannel like this:
def subscribed
stream_from "chat_#{params['chat_id']}_channel"
end
EDIT:
In your case:
your MessagesChannel subscribed action should be like this:
def subscribed
stream_from "conversation_#{params[:conversation_id]}"
end
I've been trying to show all conversation messages without refreshing page just with an ajax request. With this code i'm able to send message with channel but i'm not able to receive the message.
I would like to pass dynamically ID from frontend. I know it's possible to make that with a data parameter added into the body tag (like this).
I would like make the same but when i click on a conversation that change body data "conversation-id" and subscribe to the new channel id. I hope it's possible to make that with rails 5/jquery(ajax).
Here's my code :
conversation.coffee
App.conversation = App.cable.subscriptions.create channel: "ConversationChannel", id: document.querySelector('#conversation').dataset.conversationId,
connected: ->
$('#new_message').submit (e) ->
$this = $(this)
id = $this.find('#message_conversation_id');
body = $this.find('#message_body')
App.conversation.write id.val(), body.val()
body.val('')
e.preventDefault()
return false
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
$('.box-msg').append(data.data)
write: (id, body) ->
#perform 'write', {id: id, body: body}
conversation_channel.rb
class ConversationChannel < ApplicationCable::Channel
def subscribed
id = "[HOW CAN I PASS DYNAMICALLY ID]"
if id #on stream seulement si id existe
stream_from "conversation_#{id}"
end
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def write(data)
msg = Message.create(
conversation_id: data["id"],
body: data['body'],
user_id: 41,
read: 0
)
if msg.save
html = ApplicationController.render(partial: 'messages/message', locals: { message: msg})
end
ActionCable.server.broadcast "conversation_#{data["id"]}", data: html
end
end
conversations/index.html.erb
<script>
$(document).ready(function() {
$('.card-conversation').click(function() {
$("#conversation-content").html('');
var id = $(this).data("conversation");
$("body").attr("data-conversation-id", id);
$("#message_conversation_id").val(id); //input id
$.ajax({
url: "/conversations/"+id+"/messages",
cache: false,
success: function(html){
$("#conversation-content").append(html);
}
});
$("#conversation-content").fadeIn();
});
});
</script>
<div class="row h-100 no-margin">
<div class="col-md-4 h-100 messages-list" style="padding-right: 2px !important;">
<% #conversations.first(3).each do |conversation| %>
<div class="card card-conversation" data-conversation="<%=conversation.id %>">
<p>see conversation</p>
</div>
<% end %>
</div>
<div class="col-md-8 conversation no-padding" id="conversation">
<div id="conversation-content"></div>
<%= simple_form_for Message.new, url: "#" do |f| %>
<%= f.input :body, label: false, placeholder: "Add msg" %>
<%= f.input :conversation_id, label: false %>
<%= f.submit 'Go' %>
<% end %>
</div>
</div>
I'm getting the above error in my Rails app. I have the following Stripe code in my Booking model -
booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
validates :quantity, presence: true, numericality: { greater_than: 0 }
validates :event, presence: true, numericality: {greater_than_or_equal_to: 0 }
before_save :set_price_to_zero_if_free
def set_price_to_zero_if_free
self.event.price >= 1 unless self.event.is_free
end
def reserve
# Don't process this booking if it isn't valid
#return unless valid?
# We can always set this, even for free events because their price will be 0.
#self.total_amount = booking.quantity * event.price
# Free events don't need to do anything special
if event.is_free?
save!
# Paid events should charge the customer's card
else
begin
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: 'token',
description: "Booking created for amount #{total_amount}")
self.stripe_charge_id = charge.id
save!
rescue Stripe::CardError => e
errors.add(:base, e.message)
false
end
end
end
end
bookings_controller.rb
class BookingsController < ApplicationController
before_action :authenticate_user!
def new
# booking form
# I need to find the event that we're making a booking on
#event = Event.find(params[:event_id])
# and because the event "has_many :bookings"
#booking = #event.bookings.new(quantity: params[:quantity])
# which person is booking the event?
#booking.user = current_user
##booking.quantity = #booking.quantity
##total_amount = #booking.quantity.to_f * #event.price.to_f
end
def create
# actually process the booking
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
if
#booking.reserve
flash[:success] = "Your place on our event has been booked"
redirect_to event_path(#event)
else
flash[:error] = "Booking unsuccessful"
render "new"
end
end
private
def booking_params
params.require(:booking).permit(:stripe_token, :quantity, :event_id, :stripe_charge_id)
end
end
booking.new.html.erb
<div class="col-md-6 col-md-offset-3" id="eventshow">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Confirm Your Booking</h2>
</div>
<div class="calculate-total">
<p>
Confirm number of spaces you wish to book here:
<input type="number" placeholder="1" min="1" value="1" class="num-spaces">
</p>
<p>
Total Amount
£<span class="total" data-unit-cost="<%= #event.price %>">0</span>
</p>
</div>
<%= simple_form_for [#event, #booking], id: "new_booking" do |form| %>
<span class="payment-errors"></span>
<div class="form-row">
<label>
<span>Card Number</span>
<input type="text" size="20" data-stripe="number"/>
</label>
</div>
<div class="form-row">
<label>
<span>CVC</span>
<input type="text" size="4" data-stripe="cvc"/>
</label>
</div>
<div class="form-row">
<label>
<span>Expiration (MM/YYYY)</span>
<input type="text" size="2" data-stripe="exp-month"/>
</label>
<span> / </span>
<input type="text" size="4" data-stripe="exp-year"/>
</div>
</div>
<div class="panel-footer">
<%= form.button :submit %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
<script type="text/javascript">
$('.calculate-total input').on('keyup change', calculateBookingPrice);
function calculateBookingPrice() {
var unitCost = parseFloat($('.calculate-total .total').data('unit-cost')),
numSpaces = parseInt($('.calculate-total .num-spaces').val()),
total = (numSpaces * unitCost).toFixed(2);
if (isNaN(total)) {
total = 0;
}
$('.calculate-total span.total').text(total);
}
$(document).ready(calculateBookingPrice)
</script>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('<%= STRIPE_PUBLIC_KEY %>');
var stripeResponseHandler = function(status, response) {
var $form = $('#new_booking');
if (response.error) {
// Show the errors on the form
$form.find('.payment-errors').text(response.error.message);
$form.find('input[type=submit]').prop('disabled', false);
} else {
// token contains id, last4, and card type
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="booking[stripe_token]" />').val(token));
// and submit
$form.get(0).submit();
}
};
// jQuery(function($) { - changed to the line below
$(document).on("ready page:load", function () {
$('#new_booking').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('input[type=submit]').prop('disabled', true);
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
});
</script>
I don't understand why I'm getting the error when I'm clearly including source within my stripe code. I've expressed it above as self.stripe_token but I've tried just stripe_token but still get the same error. My understanding is I need to provide either source or customer, not both, and that it needs to be source rather than customer.
What am I missing?
If you get "Must provide source or customer" as the error message, that means that self.stripe_token in your code must be either nil or an empty string.
You need to make sure that the source parameter's value is a valid token ID. The token should be created client-side, with Checkout or Stripe.js. Once the token has been created, you need to send it to your server (typically as a POST parameter) so you can use it in your charge creation request.
You can also check your account's logs in your dashboard to see the requests that are sent by your integration, which is very helpful when debugging issues: https://dashboard.stripe.com/test/logs/overview.
My wild guess that stripe_token isn't an attribute in the booking model. That's why when you try to retrieve it's value with self.stripe_token it will give you nil.
Try passing the token from controller params to model instance method:
controller:
if #booking.reserve(booking_params['stripe_token'])
# everything is good
else
# ops
end
model:
def reserve(stripe_token)
if event.is_free?
save!
else
begin
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: stripe_token,
....
)
# rest of method
end
Update:
currently your code has the part where total_amount is set commented out!
usually you will want to make something like:
total_amount = self.booking.event.price * 100 #not really sure how should calculate this?
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: 'token',
description: "Booking created for amount #{total_amount}"
)
Here is code of room.coffee :
App.room = App.cable.subscriptions.create "RoomChannel",
connected: ->
disconnected: ->
received: (data) ->
$('#messages').append "<p>#{data}</p>"
speak: (message) ->
#perform 'speak' , message: message
cable.coffee :
#App ||= {}
App.cable = ActionCable.createConsumer()
rooms.coffee:
$ ->
$messages = $('messages')
$messages.scrollTop $messages.prop('scrollHieght')
$('#message_input').focus()
$(document).on 'keypress','message_input','e'->
if e.keycode == 13 and e.target.value
App.room.speak(e.target.value)
e.target.value = ''
e.preventDefault()
roomchannel:
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def speak(data)
message.create content: data['message']
end
end
Broadcostmessage:
def perform(message)
Actioncable.server.broadcast 'room_channel',render_message(message)
end
private
def render_message(message)
ApplicationController.renderer.render_message
end
when create the new message it will not automatically load all the messages of my browser untill the page in not reload.
I had a similar issue. Try including cable.coffee/js in the room view.
<%= javascript_include_tag 'cable', 'data-turbolinks-track': 'reload' %>
You'll want to add cable.js to /config/initializers/assets.rb as well.
Rails.application.config.assets.precompile += %w( cable.js )
This old question that came up in a Google search, but did anyone mention the typo in:
$messages.scrollTop $messages.prop('scrollHieght')
That should be:
$messages.scrollTop $messages.prop('scrollHeight')