Where can i put this rabbitmq consumer code? - ruby-on-rails

I have this code of a rabbitmq consumer using bunny that should listen to messages published to a rabbitmq queue and display a flash notice on the view whenever a message is consumed. The consumer is running in a different session from the producer though they are in the same application. The application uses a direct exchange which uses the message receiver's email as the routing_key. I would like when a message with a routing_key similar to the current_user's email is published, a flash message is displayed for that user indicating that he has a new message without refreshing the page. I want behavior similar to Facebook notifications.
The producer code looks like this:
class MessagesController < ApplicationController
def create
#message = Message.new(message_params)
#message.creator_id = current_user.id
#message.receiver_id = params[:message][:receiver_id]
if #message.save
email = #message.receiver.email
$message_exchange.publish(#message.content, :routing_key => email)
redirect_to user_path(current_user)
end
end
The consumer code: looks like this:
email = current_user.email
$message_queue.bind($message_exchange, :routing_key => email)
logger.info " [*] Waiting for messages. To exit press CTRL+C"
$message_queue.subscribe(:manual_ack => true) do |delivery_info, properties, body|
logger.info " [x] #{delivery_info.routing_key}:#{body}"
if delivery_info.routing_key == current_user.email
flash[:notice] = "you have new message: #{body}"
end
end
The problem is that i don't know where to put the consumer code. I have tried putting the code as a method in the application controller but this does not seem to work. Any suggestion on how to do this better is highly appreciated.

Related

Noti - desktop notification is not sending

In rails 4 project, I am trying to implement desktop notification feature by referring this http://api.notiapp.com/tutorials/rails. By following these procedure if I click on a link(setup_noti_path) it will leads to login page(Eg: https://notiapp.com/auth/fa574800-dbbb-1a5b-9c05-dfff043c405e). After login from this site it leads to my project page with flash message("Noti has now been configured for your account!"). Installed gem is noti (1.0.2)
Controller is like
class NotiAuthorisationsController < ApplicationController
def new
token = Noti::Token.create_request_token(noti_callback_url)
session[:noti_rt] = token.request_token
redirect_to token.redirect_url
end
def create
token = Noti::Token.get_access_token(session[:noti_rt])
current_hospital_admin.update_attribute(:noti_token, token)
session[:noti_rt] = nil
redirect_to root_path, :flash =>{:ok=>"Noti has now been configured for your account!"}
end
end
Routes are,
get '/noti' => 'noti_authorisations#new', :as => 'setup_noti'
get 'noti/callback' => 'noti_authorisations#create', :as => 'noti_callback'
In rails console, I am trying to send notification by following method but it returns only true value.
notification = Noti::Notification.new
notification.title = "An example notification"
notification.text = "Some further information about this notification"
notification.deliver_to(user.noti_token)
Where can I add this above method inside my project? And this notification related code should check continuously inside rake task. Please help me to achieve the same.

websocket channel for the "current user"

Using websocket-rails, I can use the
following code in my controller to tricker a websocket publish event:
WebsocketRails[:channel_name].trigger('event_name', { foo: "bar" }.to_json)
My goal is to create a pub-sub channel for the current user only. Take for example
the event "new chat message sent". I want to push this event only the receiver's channel.
I'm currently making a unique channel name for each user based on their ID.
I put the following code in my controller (having defined a current_user
method elsewhere:
WebsocketRails[:"user#{current_user.id}"].trigger("event_name", { foo: "bar" }.to_json)
And then in my Javascript, I subscribe the current user to their own channel with the following:
<% if #current_user %>
var dispatcher = new WebSocketRails('localhost:3000/websocket');
channel = dispatcher.subscribe('user<%= #current_user.id %>');
channel.bind('event_name', function(data) {
console.log(data)
});
<% end %>
The gist of it is using string interpolation to make a new channel for each user, i.e.
user12 and user123 channels.
The problem is this is not really secure. Any user can access anyone else's
private channel just by pasting some Javascript in. For example, if user #1 wants
to access user #2's news feed, they could just type dispatcher.subscribe('user2').
How would you solve this issue? Is there another pub-sub library which has this feature
built into it?
Looking on WebsocketRails' wiki entry on the subject,
I tried adding the follolwing code to config/initializers/websockets.rb
WebsocketRails::EventMap.describe do
namespace :websocket_rails do
subscribe :subscribe_private, to: ConnectionsController, with_method: :authorize_channels
end
end
And the following to app/controllers/connections_controller.rb
class ConnectionsController < WebsocketRails::BaseController
def authorize_channels
channel_name = WebsocketRails[message[:channel]]
current_user = User.find_by(id: session["current_user_id"])
if current_user && "user#{current_user.id}".eql?(channel_name)
accept_channel current_user
else
deny_channel({ message: "auth failed" })
end
end
end
And then elsewhere, I'm calling WebsocketRails[:"user#{#current_user.id}"].make_private
This doesn't seem to have any effect though.

Why won't my Twilio SMS send in Sidekiq background worker?

I am having issues sending a text with Twilio through my SideKiq background worker. The worker is supposed to send a text and then send an email (with Mandrill).
The email works fine.
The text never happens.
I haven't had issues with other jobs (including ones that also involve use
of environment variables).
I haven't had issues sending Twilio texts outside of a background worker.
I'm using Foreman to start my application.
In console, running UserNotifier.new.perform(1) works fine - both the email and the text are sent.
Here is some of the code in question:
This is the class that I'm using to send my SMS:
(My Gemfile includes the twilio-ruby gem)
class SendSMS
def initialize
#twilio_client = Twilio::REST::Client.new "#{ENV['TWILIO_SID']}", "#{ENV['TWILIO_TOKEN']}"
end
def send(message, user)
#twilio_client.account.sms.messages.create(
:from => "+1#{ENV['TWILIO_PHONE_NUMBER']}",
:to => user.phone_number,
:body => message
)
end
end
My worker looks like this:
class UserNotifier
include Sidekiq::Worker
sidekiq_options queue: :immediate
def perform(user_id)
user = User.find(user_id)
message = "Hi #{user.name}!"
SendSMS.new.send(message, user)
UserMailer.send(message, user).deliver
end
end
Can anyone see an issue with my code? Please let me know if there is anything else I should post or if there is any clarification that I could make.
My server was displaying the worker logs and it didn't seem to be running into an error.
Any suggestions on how to debug background workers would be appreciated as well.
Try catching REST errors from Twilio and logging some debug info. You can discover errors that might prevent messages from sending, or investigate sent messages in your Twilio account logs via their sid (Twilio's internal id). Also updated your create call to use the messages resource instead of deprecated sms/messages.
begin
message = #twilio_client.account.messages.create(
:from => "+1#{ENV['TWILIO_PHONE_NUMBER']}",
:to => user.phone_number,
:body => message
)
logger.debug "sent #{message.sid}"
rescue Twilio::REST::RequestError => e
logger.debug "error: #{e.message}"
end

How does one use delayed_job to make an Rails 3.0 ActionMailer run asynchronously? Encountering ArgumentErrors

I'm trying to delay a notification email to be sent to users upon signing up to my app. The emails are sent using an ActionMailer which I call InitMailer. The way I am trying to delay the jobs is using collectiveidea's delayed_job https://github.com/collectiveidea/delayed_job. To do this you can see that i specify handle_asynchronously after defining the method initial_email:
class InitMailer < ActionMailer::Base
default :from => "info#blahblahblah.com"
def initial_email(user)
#user = user
#url = "http://www.blahblahblah.com"
mail(:to => user.email,
:subject => "Welcome to my website!"
)
end
handle_asynchronously :initial_email
end
However, I encounter an argument error in my log file "delayed_job.log":
Class#initial_email failed with ArgumentError: wrong number of arguments (1 for 0) - 5
failed attempts
For your information, the email is sent in a controller using the line:
#user = InitUser.new(params[:init_user])
InitMailer.delay.initial_email(#user)
Additionally, when I set up my code without the delay, the emails were sent out without problem (except for the fact that it slowed down my app waiting for gmail servers)
Where is causing the errors here? How can I get the delayed mail to send properly?
Due to the way that Rails3 implements mailers, there are some unusual workarounds for delayed_jobs. For instance, you have seen that to delay the mailing, you write
ExampleMailer.delay.example(user)
While typically you would have to write handle_asynchronously after the method definition, in the case of mailers this declaration (for some reason) prevents that delayed job from working.
So in this code, drop the declaration entirely:
class InitMailer < ActionMailer::Base
default :from => "info#blahblahblah.com"
def initial_email(user)
#user = user
#url = "http://www.blahblahblah.com"
mail(:to => user.email,
:subject => "Welcome to my website!"
)
end
#No handle_asynchronously needed here
end

ActionMailer execution timeout

When trying to send an email to the user for reseting their password, I keep getting an execution timed out error. Other mailer functions work, so I know that the config settings are correct. The header reads: "Timeout::Error in Password resetsController#create"
Here is the password_resets_controller:
def create
#user = User.find_by_email(params[:email])
if #user
User.deliver_password_reset_instructions(#user.id)
flash[:notice] = "Instructions to reset your password have been emailed to you. " +
"Please check your email."
redirect_to '/'
else
flash[:notice] = "No user was found with that email address"
render :action => :new
end
end
Here is the method inside of User.rb
def self.deliver_password_reset_instructions(user_id)
user = User.find(user_id)
user.reset_perishable_token!
Emailer.deliver_password_reset_instructions(user)
end
Finally, here is the actual method inside of emailer.rb:
default_url_options[:host] = "http://0.0.0.0:3000" #development
def password_reset_instructions(user)
#subject = "Application Password Reset"
#from = 'Notice#myApp.com'
#recipients = user.email
#sent_on = Time.now
#body["edit_password_reset_url"] = edit_password_reset_url(user.perishable_token)
#headers["X-SMTPAPI"] = "{\"category\" : \"Password Recovery\"}"#send grid category header
end
Why is "Password" in the error message referred to causing a timeout::error
Sending email (or other long running processes) from the main controller request thread is not a good idea. The sending of the email can time out for a variety of reasons that are not under your control (e.g. the outbound email delivery server being down) and you don't want your application server and users to suffer due to that.
A better approach is to use a queuing mechanism like Delayed Job (DJ) to queue these email tasks, and have them be processed outside of your controller threads.
See https://github.com/collectiveidea/delayed_job
Integration of this (or another queuing system) into your rails app is fairly simple. And rails 4 is said to have built in queuing services (which I'm yet to use) http://blog.remarkablelabs.com/2012/12/asynchronous-action-mailer-rails-4-countdown-to-2013.
For instance, if you use DJ in your app, the new code will look like below
def self.deliver_password_reset_instructions(user_id)
user = User.find(user_id)
user.reset_perishable_token!
# this is the only line that changes
Emailer.delay.deliver_password_reset_instructions(user)
end
The jobs are stored in the database, and re-tried when errors like time outs happen.
You can read more about DJ on the github page.

Resources