Message username undefined for ActionCable chat app - ruby-on-rails

I am following the Learn Enough Action Cable tutorial. I have gotten to the end of section 6, adding #mentions to create notifications to the mentioned user. One of the exercises is to append the sending user's name to the alert text. So far I am only getting "#undefined". I'm guessing data.mention.username is not the correct call to append. In the console to pull the username from a message I did User.find_by(id: Message.last.user_id).username, but I don't know how to translate that to working coffeescript.
room.coffee
App.room = App.cable.subscriptions.create "RoomChannel",
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) ->
alert("You have a new mention from #" + data.mention.username) if data.mention
if (data.message && !data.message.blank?)
$('#messages-table').append data.message
scroll_bottom()
$(document).on 'turbolinks:load', ->
submit_message()
scroll_bottom()
submit_message = () ->
$('#message_content').on 'keydown', (event) ->
if event.keyCode is 13 && !event.shiftKey
$('input').click()
event.target.value = ""
event.preventDefault()
scroll_bottom = () ->
$('#messages').scrollTop($('#messages')[0].scrollHeight)
messages_controller.rb
class MessagesController < ApplicationController
before_action :logged_in_user
before_action :get_messages
def index
end
def create
message = current_user.messages.build(message_params)
if message.save
ActionCable.server.broadcast 'room_channel',
message: render_message(message)
message.mentions.each do |mention|
ActionCable.server.broadcast "room_channel_user_#{mention.id}",
mention: true
end
end
end
private
def get_messages
#messages = Message.for_display
#message = current_user.messages.build
end
def message_params
params.require(:message).permit(:content)
end
def render_message(message)
render(partial: 'message', locals: { message: message })
end
end
message.rb
class Message < ApplicationRecord
belongs_to :user
validates :content, presence: true
scope :for_display, -> { order(:created_at).last(50) }
# Returns a list of users #mentioned in message content.
def mentions
content.scan(/#(#{User::NAME_REGEX})/).flatten.map do |username|
User.find_by(username: username)
end.compact
end
end
room_channel.rb
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel"
stream_from "room_channel_user_#{message_user.id}"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end

Have to define the username within the create method of messages controller. So the create method in messages_controller.rb looks something like:
def create
message = current_user.messages.build(message_params)
if message.save
ActionCable.server.broadcast 'room_channel',
message: render_message(message)
message.mentions.each do |mention|
ActionCable.server.broadcast "room_channel_user_#{mention.id}",
mention: true,
origin: "##{message.user.username}"
end
end
end
And the room.coffee alert calls data.origin like so:
received: (data) ->
alert("You have a new mention from " + data.origin) if data.mention
if (data.message && !data.message.blank?)
$('#messages-table').append data.message
scroll_bottom()
Thanks to LIUSINING of LearnEnough Society for pointing me in the right direction.

Related

Graphql subscription using Ruby on rails - User Specific notification using action cable connection

I have written mutation for adding users to group.
Here is code file:
# add_users_to_group.rb
# frozen_string_literal: true
module Mutations
module Groups
class AddUsersToGroups < GraphQL::Schema::Mutation
# argument :input, Types::Input::Groups::UserGroupInputType, required: true
argument :user_ids, [ID], required: true
argument :group_ids, [ID], required: true
argument :role_id, ID, required: false
argument :overwrite_roles, Boolean, required: false
field :users, [Types::UserType], null: false
field :groups, [Types::GroupType], null: false
def resolve(user_ids:, group_ids:, role_id: nil, overwrite_roles: false)
# input = Hash input
# raise StandardError, "You don't have the access!" unless context[:current_user].has_capability?(self.class.name.demodulize)
users, groups = context[:tenant].add_users_to_groups(group_ids, user_ids, role_id, overwrite_roles )
{ users: users, groups: groups }
rescue ActiveRecord::RecordInvalid => e
GraphQL::ExecutionError.new("Invalid attributes for #{e.record.class}:"\
" #{e.record.errors.full_messages.join(', ')}")
rescue StandardError => e
GraphQL::ExecutionError.new(e.message)
end
end
end
end`
Here i need to send notification to user 'you are added to group' based on specified user_id, i tried but i am not able to do completely. how can i make it? how can i use graphql subscription?
graphql_channel.rb:
class GraphqlChannel < ApplicationCable::Channel
def subscribed
# Store all GraphQL subscriptions the consumer is listening for on this channel
# binding.pry
#subscription_ids = []
end
def execute(data)
query = data["query"]
variables = ensure_hash(data["variables"])
operation_name = data["operationName"]
context = {
channel: self,
# current_application_context: connection.current_application_context
}
result = LmsApiSchema.execute(
query,
context: context,
variables: variables,
operation_name: operation_name
)
payload = {
result: result.to_h,
more: result.subscription?,
}
# Append the subscription id
#subscription_ids << result.context[:subscription_id] if result.context[:subscription_id]
transmit(payload)
end
def unsubscribed
# Delete all of the consumer's subscriptions from the GraphQL Schema
#subscription_ids.each do |sid|
LmsApiSchema.subscriptions.delete_subscription(sid)
end
end
private
def ensure_hash(ambiguous_param)
case ambiguous_param
when String
if ambiguous_param.present?
ensure_hash(JSON.parse(ambiguous_param))
else
{}
end
when Hash, ActionController::Parameters
ambiguous_param
when nil
{}
else
raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
end
end
end
channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
connection.rb:
app/channels/application_cable/connection.rb
require 'auth_token'
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
begin
token = request.params[:token]
user_d = AuthToken.decode(token)
if (current_user = User.find(user_d['user']))
current_user
else
reject_unauthorized_connection
end
rescue
reject_unauthorized_connection
end
end
end
end
LMSApiSchema.rb
class LmsApiSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)
use GraphQL::Subscriptions::ActionCableSubscriptions
subscription(Types::SubscriptionType)
******
end
subscription_type.rb
module Types
class SubscriptionType < Types::BaseObject
description "The subscription root for the GraphQL schema"
field :notification_posted, subscription: Subscriptions::NotificationPosted, null: false
end
end
NotificationPosted.rb
module Subscriptions
class NotificationPosted < BaseSubscription
# field :notification, Types::NotificationType, null: false
field :notification, Types::NotificationType, null: false
# def authorized?(room:)
# true
# end
def notification
Notification.last
end
def update( _attrs = {} )
debugger
puts 'UPDATE CALLED' # Nope, it's not being called
{
notification: notification
}
end
def subscribe( _attrs = {} )
puts 'SUBSCRIBE CALLED' # Nope, it's not being called
# authorize, etc ...
# Return the room in the initial response
{
notification: notification
}
end
end
end
Updated graphql subscription connection

Rails 5 - How to send notifications on real-time manner?

In rails 5, I am trying to send notifications on real-time manner. I have referred http://jameshuynh.com/rails/react%20js/chat/2017/07/30/build-chat-using-react-js-and-rails-action-cable/
But I have already some methods which will do the same. As per this method, notification should send based on user_id dynamically. Even channel is depends on user_id.
In react, header.jsx file,
import ActionCable from 'actioncable';
export default class Header extends React.Component {
cable = ActionCable.createConsumer('ws://localhost:3000/cable');
componentDidMount() {
HttpService.get('/api/v1/notifications', this.getNotificationsSuccess);
let that = this;
this.cable.subscriptions.create("NotificationsChannel", {
user_id: this.state.currentUser.id,
connected: function () {
// Timeout here is needed to make sure Subscription
// is setup properly, before we do any actions.
setTimeout(() => this.perform('follow', { user_id: this.user_id }),
1000);
},
received: (response) => that.addNotification(response)
});
}
addNotification = (response) => {
if(this.refs.notifications){
this.refs.notifications.addNotification(response.data.notification);
}
}
In rails, connection.rb,
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.full_name
end
protected
def find_verified_user
verified_user = User.find_by(id: cookies.signed['user.id'])
if verified_user
verified_user
else
reject_unauthorized_connection
end
end
end
end
In notification_channel.rb,
class NotificationsChannel < ApplicationCable::Channel
def follow(data)
stop_all_streams
stream_from "notifications:#{data['user_id'].to_i}"
end
def unfollow
stop_all_streams
end
end
In notification_broadcast_job.rb,
class NotificationsBroadcastJob < ApplicationJob
def perform(notification, user_id)
ActionCable.server.broadcast "notifications:#{user_id}",
data: NotificationSerializer.new(notification)
end
end
In model,
after_commit :broadcast_notification
private
def broadcast_notification
users.each do |user|
NotificationsBroadcastJob.perform_later(self, user.id)
end
end
With this existing method how can I check real-time notification is working or not?
Right now there is no error and it is not working too. Please help me to implement this feature.

Action cable not working I have to refresh both the tab to see message

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')

Spree offsite payments Payu.in integration: how to mark an order as paid

I am new to ruby/rails/spree. I am implementing Indian payment gateway with spree-3.0.7.
I am able to process the order but payment status is always at balance_due.
Controller code
def confirm
payment_method = Spree::PaymentMethod.find(payment_method_id)
Spree::LogEntry.create({
source: payment_method,
details: params.to_yaml
})
order = current_order || raise(ActiveRecord::RecordNotFound)
if(address = order.bill_address || order.ship_address)
firstname = address.firstname
end
#confirm for correct hash and order amount requested before marking an payment as 'complete'
checksum_matched = payment_method.checksum_ok?([params[:status], '', '', '', '', '', '', params[:udf4], params[:udf3], params[:udf2], params[:udf1], order.email, firstname, #productinfo, params[:amount], params[:txnid]], params[:hash])
if !checksum_matched
flash.alert = 'Malicious transaction detected.'
redirect_to checkout_state_path(order.state)
return
end
#check for order amount
if !payment_method.amount_ok?(order.total, params[:amount])
flash.alert = 'Malicious transaction detected. Order amount not matched.'
redirect_to checkout_state_path(order.state)
return
end
payment = order.payments.create!({
source_type: 'Spree::Gateway::Payumoney',#could be something generated by system
amount: order.total,
payment_method: payment_method
})
payment.started_processing!
payment.pend!
order.next
order.update_attributes({:state => "complete", :completed_at => Time.now})
if order.complete?
order.update!
flash.notice = Spree.t(:order_processed_successfully)
redirect_to order_path(order)
return
else
redirect_to checkout_state_path(order.state)
return
end
end
Gateway/Model Code
require "offsite_payments"
module Spree
class Gateway::Payumoney < Gateway
preference :merchant_id, :string
preference :secret_key, :string
def provider_class
::OffsitePayments.integration('Payu_In')
end
def provider
#assign payment mode
OffsitePayments.mode = preferred_test_mode == true ? :test : :production
provider_class
end
def checksum(items)
provider_class.checksum(preferred_merchant_id, preferred_secret_key, items)
end
def auto_capture?
true
end
def method_type
"payumoney"
end
def support?(source)
true
end
def authorization
self
end
def purchase(amount, source, gateway_options={})
ActiveMerchant::Billing::Response.new(true, "payumoney success")
end
def success?
true
end
def txnid(order)
order.id.to_s + order.number.to_s
end
def service_provider
"payu_paisa"
end
def checksum_ok?(itms, pg_hash)
Digest::SHA512.hexdigest([preferred_secret_key, *itms, preferred_merchant_id].join("|")) == pg_hash
end
def amount_ok?(order_total, pg_amount)
BigDecimal.new(pg_amount) == order_total
end
end
in spree payment doc https://guides.spreecommerce.com/developer/payments.html they have mentioned if auto_capture? return true then purchase method will be called but purchase method is not getting called.
Can anyone point me to right direction?
You need not call the following commands
payment.started_processing!
payment.pend!
Just leave the payment in its initial state. i.e. checkout state and complete your order.
Because when order is completed process_payments! is called.
This method processes unprocessed payments whose criteria is like below
def unprocessed_payments
payments.select { |payment| payment.checkout? }
end
Hope this solves your case :)
I fixed the issue by marking payment as complete.
remove
payment.started_processing!
payment.pend!
add
payment.complete
above
order.next
I have published my code at github as gem
https://github.com/isantoshsingh/spree_payumoney

Rails 5 action cable with nobrainer not working properly

I tried action cable example from https://github.com/rails/actioncable-examples. It worked well with ActiveRecord. I am getting ActiveJob::SerializationError (Unsupported argument type: Comment) when using with NoBrainer for rethinkDB.
So I just passed id instead of self like below for activejob. But action cable server is not able to listen to the any channels and CommentsChannel methods are not invoked.
comment.rb
after_save { CommentRelayJob.perform_later(self.id) }
*****comment_relay_job.rb*****
def perform(comment_id)
comment = Comment.find(comment_id)
ActionCable.server.broadcast "messages:#{comment.message_id}:comments",
comment: CommentsController.render(partial: 'comments/comment', locals: { comment: comment }
end
comments_channel.rb
module ApplicationCable
class Channel < ActionCable::Channel::Base; end
end
class CommentsChannel < ApplicationCable::Channel
def follow(data)
stop_all_streams
stream_from "messages:#{data['message_id'].to_i}:comments"
end
def unfollow
stop_all_streams
end
end
comments.coffee
App.comments = App.cable.subscriptions.create "CommentsChannel", collection: -> $("[data-channel='comments']")
connected: ->
setTimeout =>
#followCurrentMessage()
#installPageChangeCallback()
, 1000
received: (data) ->
#collection().append(data.comment) unless #userIsCurrentUser(data.comment)
userIsCurrentUser: (comment) ->
$(comment).attr('data-user-id') is $('meta[name=current-user]').attr('id')
followCurrentMessage: ->
if messageId = #collection().data('message-id')
#perform 'follow', message_id: messageId
else
#perform 'unfollow'
installPageChangeCallback: ->
unless #installedPageChangeCallback
#installedPageChangeCallback = true
$(document).on 'page:change', -> App.comments.followCurrentMessage()

Resources