Using Private-pub with channels that have segment keys and coffeescript variant - ruby-on-rails

I'm trying to replicate a push notification system similar to facebook's using private_pub. Ideally I would want to link this to show notifications using a gem such as gritter (other gem suggestions are welcome)
Whenever a certain action from a controller is called, I want to send a notification to all subscribers that are part of a specific id. As long you are logged in, you are subscribed to the channel, achieved by putting the subscribe_to in the layouts.
in the view:
<%= subscribe_to "/messages/#{#group_id}" %>
in the controller
PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')")
this works just fine, however I would like to have something more sophisticated than an alert as a response (such as a gritter notification), so instead:
PrivatePub.publish_to("/messages/#{#group_id}", data: #some_data)
Following the tutorial, they use coffeescript for this. However, I cannot get the simple alert going (probably due to the id in the channel)
In this question, the OP was able to solve this using a js.erb view. But I can't get it to work.
disclaimer: my js and coffeescript knowledge is almost zero.
Any help is appreciated :)
EDIT
Some more info: I've a method in a controller that's part of a public API, and expects POST request. If everything is ok it sends out a JSON success response. Aside from this, the same method sends a notification to all users of a specific group.
I've actually managed to get this working, putting this in the controller:
callback method:
respond_to do |format|
format.js #-> calls callback.js.erb
#format.json { render json: {"success" => true}.to_json }
end
and putting the gritter stuff in my_api_controller/callback.js.erb:
<% publish_to "/messages/#{#group_id}" do %>
<%= add_gritter(
"Nova " + link_to("reserva", reservation_path(#r)) + " de #{#channel} para " +
link_to(#hostel_name, hostel_path(#hostel_id)),
:title => "Nova reserva!",
:sticky => true,
:image => :notice
) %>
<% end %>
note: since the subscription to the channel is done in every view (through the layout), you can receive a notification on any page/view
My problem at the momento is, as you can guess, the JSON response. Since I cant render two responses, only the js.erb is called, but the JSON response is never sent

Although I've not got much experience with this gem, here's something which may help:
JS
Client-side, your JS is basically running an eventlistener on the private_pub object (defined when you include the private_pub / gritter JS on your page), which you can use to perform other actions (call alerts, append data to page, etc)
It seems your back-end is working, it's just the receipt & processing of the data from the server you're struggling with. To remedy this, you can do 2 things: 1) run a standard JS call from application.js or run a js file from your controller action:
Controller
According to the private_pub documentation, you should do this to create a JS file:
#app/controllers/your_controller.rb
def create
#message = "Hello"
respond_to do |format|
format.html { PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')") }
format.js #-> calls create.js.erb
end
end
#app/views/your_controller/create.js.erb
<% publish_to "/messages/new" do %>
$("#chat").append("<%= j render(#messages) %>");
<% end %>
Browser
#app/assets/javascripts/application.js.coffee
PrivatePub.subscribe("/messages/new", (data, channel) ->
alert data.message.content

I was able to accomplish this by directly adding the gritter script in the publish_to method of Privat pub.
In my controller:
PrivatePub.publish_to
"/some/URI/#{entity.id}"
,"jQuery.gritter.add({
image: '#{ActionController::Base.helpers.asset_path('notice.png')}'
, sticky: true
,title:'#{t('some_title')}'
, text: '#{t('some text'}'
});"
render json: {"error"=>{"code"=>20,"msg"=>e.message},"success" => false}.to_json
Basically, I was able to publish to PrivatePub witouth resorting to the html response, wich enabled me to return a JSON response as intended.

I guess, you can handle your problem with the help of gon gem like below:
In view
<%= subscribe_to "/messages/#{#group_id}" %>
In controller
gon.group_id = #group_id
PrivatePub.publish_to("/messages/#{#group_id}", message: #message)
In messages.coffee
if gon.group_id
PrivatePub.subscribe "/messages/#{gon.group_id}", (data, channel) ->
jQuery.gritter.add
image: '/assets/notice.png'
title: 'Notification!'
text: data.message.content
But, gon.group_id can make trouble sometimes so you need to take care of that.
So, I recommend to use js.erb which is easy and we can access to controller's variable easily in js.erb file.
Hope that answer your problem.

Related

Send SMS using nexmo gem from input field in rails

I am trying to send an sms to a number entered in an input field using Nexmo gem
This is what I have so far and it doesn't seem to work
pages/test.html.erb
<%= form_tag "/pages/send_sms" do -%>
<%= text_field_tag "number" %>
<%= submit_tag "Send" %>
<% end -%>
routes.rb
Rails.application.routes.draw do
get 'pages/home'
post '/pages/send_sms', as: 'send_sms'
get 'test', to: 'pages#test'
root 'pages#home'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
pages_controller.rb
def send_sms
#number = params[:number]
nexmo = Nexmo::Client.new(
key: ENV['NEXMO_API_KEY'],
secret: ENV['NEXMO_API_SECRET']
)
notification = "Download the app through this link"
response = nexmo.send_message(
from: "GLAM360",
to: params['number'],
text: notification
)
if response['messages'].first['status'] == '0'
redirect_to root_path
end
end
This is what I see in the terminal
Started POST "/pages/send_sms" for 127.0.0.1 at 2017-10-08 00:35:45 +0400
Processing by PagesController#send_sms as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"zPj5PcZrD+uNYxvvfDio8B5uNWitg0vMw+3Vm8KbvQumbNWzsgN4sBJDKsi2srx0rSatiOISegWHQFE860
JxcA==", "number"=>"+971585959698", "commit"=>"Send"}
No template found for PagesController#send_sms, rendering head :no_content
Completed 204 No Content in 803ms
Any help will be highly appreciated here
The error itself tells you what to do. You have few options.
First for your case, just add another redirect if the last check
condition fails. In your case, its failing and so asking for default template. If you specify an else clause, in which you
describe where should it go to (say to the form again with an alert message), rails will itself take care of that.
if response['messages'].first['status'] == '0'
redirect_to root_path
else
redirect_to test_path
end
I used your script and added a send_sms.html.erb with a status
variable being passed from the controller according to the response
of the nexmo.send_message function. Like "Success" or failure and
everything works fine, on the webpage it informs me about the status and I received a text message as well. This is what I will prefer for a better UX.
.
If I have to do it, there would be a lot of changes I would do to the script. A suggestion would be to never rely on an external API, always wrap the interactions you do to an external API in an interface. I will wrap the Nextio scripts, take it out of the controller and place them in an interface (Have a class in either lib or a wrapper function in helper and use interface to interact with it) and will interact with it through my controller. Try not to put logic in your controller. I would also use begin rescue block for interactions with external API and will read about all the errors and think about how to handle them. Lastly for the flow, I would give users more information and not keep them hanging. I would redirect them to another page like send_sms and give them status or use alert messages to tell them whats the status.
Lastly, I would use background tasks for these jobs (which can take sometime depending upon external servers). Use something like sidekiq.

rails form post action without changing path

I've got a view that renders a contact form. This contact form is rendered through javascript. There is also a javascript filter that the user can set viewing options in. (Depending on the settings, different markers are shown on a google map. The user can then click on the markers and in the viewbox click on a view button that renders some info and the contact form below the map)
If I were to make a normal form and use the post method with a #contact and contact routes, I would have to rerender the entire page after the #contact#create method was called. Which would mean all of the current users filter options would be unset. I could obviously save the settings, but feel like this is a hassle.
What I would like is for the contact form to call a method upon submit without actually changing paths, but I have no idea if this is even possible. (i'm using simple form so an answer for that would be preferable)
Since your question is quite broad, I'll have to answer as such:
if this is even possible
Yes it's possible.
You'll have to use ajax to send an asynchronous request to your server.
Ajax (Asynchronous Javascript And Xml) sends requests out of scope of typical HTTP; you can send/receive "hidden" data without reloading (this is what you want):
Don't worry - ajax is really simple once you understand it.
There's a great Railscast about it here:
Implementation
For you, you will just have to get your form to submit over ajax (javascript). There are two ways to do this:
Standard JS (JQuery)
Rails UJS (unobtrusive Javascript)
Basically, javascript acts as a mini browser, opening a url on your server, handling the returned data & doing what you tell it on the path:
<% form_tag contact_path, remote: true %>
<%= text_field_tag "name %>
<%= email_field_tag "email" %>
<%= submit_tag %>
<% end %>
You'll then be able to back this up with the corresponding controller action on your server:
#app/controllers/contact_forms_controller.rb
class ContactFormsController < ApplicationController
def create
... #-> your own logic here
respond_to do |format|
format.js #-> when receiving a pure xml request, this will fire
format.html
end
end
end
#app/views/contact_forms/create.js.erb
$("body").append("<%=j #variable %>");
Without going into too much detail, this is the most efficient way to achieve what you want. I can elaborate if required.

Ajax calls - send_data and render a partial - two render methods in a single action

Users can download reports via a link, and next to the link is a text flag indicating whether or not they have downloaded the file already - an 'unread' alert. I hava a partial which shows a single item in an index of reports:
<p id="chapter_report_index_item">
<%= link_to "#{report.chapter_report_original_filename}", chapter_report_path(report), remote: true %>
<%unless check_read_status?(report) %>Unread
<%end%>
<p>
In chapter_reports_controller.rb I have -
def show
set_chapter_report
set_read_status_true(#chapter_report)
send_data #chapter_report.chapter_report.read, filename: #chapter_report.chapter_report_original_filename
respond_to do |format|
format.js
end
end
And in show.js, a call to re-render this single item partial -
$("#chapter_report_index_item").html( "<%= j (render(partial: 'index_item', locals: {report: #chapter_report})) %>" );
You'll note the check_read_status method in the view - essentially the idea is that when the partial is displayed, the unread flag is displayed if the user has not clicked on the download link.
My problem is, I'm trying to render two things in the action: the download, and the partial. What's the technique for avoiding this?
Essentially, I want a download button that changes when the file has been downloaded.
Update
I'm trying to solve this though a javascript to a helper (as per comments below). My problem is that send_file and it's ilk are not available as methods in application_helper.rb
Essentially I've got a jQuery call to a partial which just contains a call to the download method -
<%= report.class.name %>
<%= report.chapter_report_original_filename %>
<%= asset_download(report) %>
In the helper -
def asset_download(object)
object.chapter_report_original_filename
#send_file(object.chapter_report.read, filename:object.chapter_report_original_filename)
end
I've put the calls to .class.name etc to see what's getting through. When I uncomment the send_file call, I get the error -
ActionView::Template::Error (undefined method `send_file' for #<#<Class:0x0...
I'm guessing cos the method is available to the ActionController not ActionView.
So the question is - how do I make controller methods available in this context? Or do I find another way to do this?
Update 2 as per the answer below, you can call the send_file method by rendering a partial which contains the call through javascript. You need to make the method available in the helper as per this question.
You could split your action in two seperate actions (the download and the ajax call) and write some frontend (javascript) code that listens for the download click and triggers a second ajax call to the update_read_status action (for example via jquery).

Add a "Thank you for your order" page that is not directly visitable

When the user purchases an item we want to display a thank you page.
Then again we want this page not to be directly visitable.
In other words, if the page was to be /orders/thankyou, the user could directly navigate to it which would be rather ugly and fail aas it would have nothing to show.
how can we show this page only when in context and forbid direct navigation to it?
You can create a partial form and append it to the DOM after the purchase event is fired. In the partial view, be sure to add a redirect action if it was accessed without the purchase event firing.
For just displaying short text (and not also e.g. the order data) you could just use a flash notice. For example:
redirect_to #page, notice: "Thank you for your order!"
Ajax
Sounds like you'll be best using ajax:
#app/views/cart/index.html.erb
<%= form_tag cart_order_path, remote: true do |f| %>
... (cart form)
<%= f.submit %>
<% end %>
This (obviously very simple) form will send a "remote" (Ajax) form submission to your controller. The reason why this is important is because you will then handle the response directly in that page you just sent the request from:
#app/assets/javascripts/application.js
$(document).on("ajax:success", "#your_form_id", function(status, data, xhr) {
$("your_page_div").html(data);
});
The trick here will be to render your thank you view without a layout -
#app/controllers/cart_controller.rb
class CartController < ApplicationController
respond_to :js, only: :create
def create
... business logic here
render "thank_you", layout: false
end
end
This will render the view you want without any of the supporting "layout" HTML - giving you the chance to append that to your current view. This means that if you wanted to show the "Thank You" view without letting the user browse to it directly - that's what you'll do
How It Works
Ajax (Asynchronous Javascript and XML) is a javascript technology which basically allows you to send "pseudo requests" to your Rails backend:
Basically the same as a standard HTTP request, except handled with Javascript, Ajax gives you the ability to create the appearance of "no refresh" functionality in your app. It does this by sending requests on your behalf, through Javascript.
Ajax is typically used for small pieces of functionality on web interfaces - whereby you'll have the ability to send simple requests to the server, gaining a comparable response that you can then work into the DOM.
This is the functionality I have been proposing - whereby you'll be able to send a request to your controller (albeit using the Rails UJS engine), to which you'll then receive a response. This response can then be worked into your page, thus providing you with the ability to show the "Thank You" page without the user refreshing.
You can use any solution from the following:
Using ActionDispatch::Flash:
flash[:notice] = "Thank you for your order!"
redirect_to #page
or
redirect_to #page, notice: "Thank you for your order!"
Using alert, in show.js.haml file (assuming you use action show in orders_controller.rb):
alert("Thank you for your order!");
and then add respond_to :js, only: :show, and format.js in action show for orders_controller.rb

rails, how to reference action mailer in jquery

i wanted to perform an action mailer method after an ajax method completes. im building a twitter app essentially, and wanted an email notification to be sent after someone clicks 'follow', which is done asynchronously.
i gave the follow button an id
<%= f.submit "Follow", :class => "btn btn-large btn-primary",
:id => "follow_button"%>
and then used jquery
$("#follow_button").bind('ajax:success', function() {
});
however, im really sure how i can reference my UserMailer in jquery. ultimately im trying to perform this line after my ajax is complete
UserMailer.is_now_following(#user, current_user).deliver
thanks!
hmmm i tried adding that line of code in my create function (to create the relationship of the follow) but it lags my ajax quite a lot. the ajax is to render the 'unfollow' button after the 'follow' is clicked btw.
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
respond_to do |format|
format.html {redirect_to #user}
format.js
end
#UserMailer.is_now_following(#user, current_user).deliver
end
i commented it out. is this what you meant for adding it after my ajax call is successful?
also, how do you put a job on queue? thanks!
The better solution is doing that only in your server not in your client side.
If you do like you want you need doing 2 requests. 1 to follow people and 1 to launch mail. If you user stop this application between this 2 requests, no email is send.
The better solution is to launch your Mailer directly in your follow action. In your controller, you know if the request is a success or not. If the request is a success launch the email.
If you want more reactivity and avoid doing this job directly in your action, you can push the mailer action to a job queue.

Resources