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.
Related
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
I'm writing some mobile otp validation service and below is my service class
require 'nexmo'
class NexmoServices
def initialize api_key = nil, api_secret = nil, opts = {}
api_key = api_key || Rails.application.secrets.nexmo_api_key
api_secret = api_secret || Rails.application.secrets.nexmo_secret_key
#nexmo_client = Nexmo::Client.new(
api_key: api_key,
api_secret: api_secret,
code_length: 6
)
#brand = 'CryptoShop'
end
def send_verification_code opts
#nexmo_client.verify.request(number: opts[:number], brand: #brand)
end
def check_verification_code opts
#nexmo_client.verify.check(request_id: opts[:request_id], code: opts[:verification_code])
end
def cancel_verification_code opts
#nexmo_client.verify.cancel(opts[:request_id])
end
end
in the controller i'm calling the service methods like below
class NexmoController < ApplicationController
def send_verification_code
response = NexmoServices.new.send_verification_code params[:nexmo]
if response.status == '0'
render json: response.request_id.to_json
else
render json: response.error_text.to_json
end
end
def cancel_verification_code
response = NexmoServices.new.cancel_verification_code params[:nexmo]
if response.status == '0'
render json: response.to_json
else
render json: response.error_text.to_json
end
end
end
I have read that usually there will be call method inside service class and controller will call that. service method call will take care of the rest.
My case im instantiating service objects for all the methods if you see my controller(NexmoService.new).
is it correct??? I want to know the best practise must be followed in this scenario.
Thanks,
Ajith
I am using Zapier to search some information in google sheets. I used Webhocks to send a GET to his server with a JSON information. The response of GET is an "OK" and I can't custom this.
So, they will execute a task, find what a I want and return a value, but the response must be a GET in my server, and I don't know how to intercept this response in my route.
I'm trying to study Rails Rack to intercept de request in my app, but I don't know how to send the response to the event that sent the first GET.
How is my middleware:
class DeltaLogger
def initialize app
#app = app
end
def call env
Rails.logger.debug "#{env['QUERY_STRING']}"
#status, #headers, #response = #app.call(env)
[#status, #headers, #response]
end
end
Thanks!
Example
So, to get the value returned from Zapier, I created two routes and a global class cache.
class Zapier
require 'httparty'
def initialize
#answer = ""
#id = 0
end
def request(uri, task)
last_event = Event.last
puts last_event.inspect
if last_event.nil?
last_id = 0
else
last_id = last_event.event_id
end
event_id = last_id + 1
Event.find_or_create_by(event_id: event_id)
result = HTTParty.post(uri.to_str,
:body => {id: event_id, task: task}.to_json,
:headers => {'content-Type' => 'application/json'})
#answer = ""
#id = event_id
end
def response(event_id, value)
if event_id != #id
#answer = ""
else
#answer = value
end
end
def get_answer
#answer
end
end
And my controller:
class ZapierEventsController < ApplicationController
require 'zapier_class'
before_action :get_task, only: [:get]
before_action :get_response, only: [:set]
##zapier ||= Zapier.new
def get
##zapier.request('https://hooks.zapier.com',#task)
sleep 10 #Wait for response
#value = ##zapier.get_answer
render json: { 'value': #value }, status:
end
def set
##zapier.response(#id, #value)
render json: { 'status': 'ok' }, status: 200
end
def get_task
#task = params["task"]
end
def get_response
#id = Integer(params["id"])
#value = params["value"]
end
end
Now i have to make a Task Mananger
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.
module Asterisk
class Client
include HTTParty
base_uri 'https://asterisk.dev/'
def initialize(session_key = nil)
#session_key = session_key
end
def get_session_key(login, password)
self.class.put('/api/auth', query: { login: login, password: password })
end
def get_contacts
self.class.get("/api/#{#session_key}/contacts")
end
def get_contact(id)
self.class.get("/api/#{#session_key}/contact/#{id}")
end
def create_contact
self.class.put("/api/#{#session_key}/create")
end
def logout
self.class.delete("/api/#{#session_key}/logout")
end
end
end
Right now it works like below
session_key = Asterisk::Client.new.get_session_key('login', 'pass')
client = Asterisk::Client.new(session_key)
client.get_contacts
I would like to get and set session key using singleton. How to do that?
module Asterisk
class Client
include HTTParty
include Singleton
base_uri 'https://asterisk.dev/'
attr_reader :last_session_update
private
def session_key
if !#session_key || session_refresh_needed?
#session_key = set_session_key
#last_session_update = Time.now
else
#session_key
end
end
def set_session_key
self.class.put('/api/auth', query: { login: login, password: password })
end
def password
#the way you get pass
end
def login
#the way you get login (ENV...)
end
def session_refresh_needed?
return true unless last_session_update
( Time.now - last_session_update) > 30.minute
end
end
end
It includes your issue with 30 minutes refresh.
Now call Asterisk::Client.instance